Bug 1087233 - Create about:downloads to migrate to Downloads.jsm. r=mconley
This commit is contained in:
Родитель
e5d6d6f6d9
Коммит
5392ec3a79
|
@ -508,9 +508,10 @@ function openIMAccountWizard()
|
|||
|
||||
function openSavedFilesWnd()
|
||||
{
|
||||
Components.classes['@mozilla.org/download-manager-ui;1']
|
||||
.getService(Components.interfaces.nsIDownloadManagerUI)
|
||||
.show(window);
|
||||
let tabmail = document.getElementById("tabmail");
|
||||
tabmail.openTab("chromeTab",
|
||||
{ chromePage: "about:downloads",
|
||||
clickHandler: "specialTabs.aboutClickHandler(event);" });
|
||||
}
|
||||
|
||||
function SetBusyCursor(window, enable)
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* This component enables the JavaScript API for downloads at startup. This
|
||||
* will eventually be removed when nsIDownloadManager will not be available
|
||||
* anymore (bug 851471).
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Globals
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DownloadIntegration",
|
||||
"resource://gre/modules/DownloadIntegration.jsm");
|
||||
|
||||
/**
|
||||
* CID and Contract ID of the JavaScript implementation of nsITransfer.
|
||||
*/
|
||||
const kTransferCid = Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}");
|
||||
const kTransferContractId = "@mozilla.org/transfer;1";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// DownloadsStartup
|
||||
|
||||
function DownloadsStartup() { }
|
||||
|
||||
DownloadsStartup.prototype = {
|
||||
classID: Components.ID("{a93f0d6f-02a3-4486-a662-8f49b8c1de48}"),
|
||||
|
||||
_xpcom_factory: XPCOMUtils.generateSingletonFactory(DownloadsStartup),
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// nsISupports
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// nsIObserver
|
||||
|
||||
observe: function DS_observe(aSubject, aTopic, aData)
|
||||
{
|
||||
if (aTopic != "profile-after-change") {
|
||||
Cu.reportError("Unexpected observer notification.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Override Toolkit's nsITransfer implementation with the one from the
|
||||
// JavaScript API for downloads.
|
||||
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
|
||||
.registerFactory(kTransferCid, "",
|
||||
kTransferContractId, null);
|
||||
|
||||
// To preserve download list across sessions.
|
||||
DownloadIntegration.shouldPersistDownload = function(aDownload) {
|
||||
return true;
|
||||
};
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Module
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DownloadsStartup]);
|
|
@ -24,6 +24,8 @@ AboutRedirector.prototype = {
|
|||
flags: Ci.nsIAboutModule.ALLOW_SCRIPT},
|
||||
"preferences": {url: "chrome://messenger/content/preferences/aboutPreferences.xul",
|
||||
flags: Ci.nsIAboutModule.ALLOW_SCRIPT},
|
||||
"downloads": {url: "chrome://messenger/content/downloads/aboutDownloads.xul",
|
||||
flags: Ci.nsIAboutModule.ALLOW_SCRIPT},
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/* 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.download {
|
||||
-moz-binding: url('chrome://messenger/content/downloads/download.xml#download');
|
||||
}
|
||||
|
||||
#msgDownloadsRichListBox {
|
||||
/** The default listbox appearance comes with an unwanted margin. **/
|
||||
margin: 0;
|
||||
display: -moz-box;
|
||||
}
|
||||
|
||||
#msgDownloadsRichListBox > richlistitem.download {
|
||||
height: 5em;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
#msgDownloadsListEmptyDescription {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#msgDownloadsRichListBox:empty + #msgDownloadsListEmptyDescription {
|
||||
display: -moz-box;
|
||||
}
|
|
@ -0,0 +1,325 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
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, "OS", "resource://gre/modules/osfile.jsm");
|
||||
|
||||
const 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))
|
||||
.then(null, Cu.reportError);
|
||||
|
||||
window.addEventListener("unload", aEvent => {
|
||||
Downloads.getList(Downloads.ALL)
|
||||
.then(list => list.removeView(this))
|
||||
.then(null, Cu.reportError);
|
||||
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.firstChild;
|
||||
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) {
|
||||
Cu.reportError("No DownloadItem found for download");
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.stateChanged) {
|
||||
this.insertOrMoveItem(item);
|
||||
}
|
||||
|
||||
item.onDownloadChanged();
|
||||
},
|
||||
|
||||
onDownloadRemoved(aDownload) {
|
||||
let item = this.items.get(aDownload);
|
||||
if (!item) {
|
||||
Cu.reportError("No DownloadItem found for download");
|
||||
return;
|
||||
}
|
||||
|
||||
this.items.delete(aDownload);
|
||||
this.listElement.removeChild(item.element);
|
||||
},
|
||||
|
||||
onDownloadContextMenu(aEvent) {
|
||||
let element = this.listElement.selectedItem;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateCommands();
|
||||
},
|
||||
|
||||
clearDownloads() {
|
||||
Downloads.getList(Downloads.ALL)
|
||||
.then(list => list.removeFinished())
|
||||
.then(null, Cu.reportError);
|
||||
},
|
||||
|
||||
supportsCommand(aCommand) {
|
||||
if (!(this.commands.some(command => command == aCommand)) &&
|
||||
!(DownloadItem.prototype.supportsCommand(aCommand))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
isCommandEnabled(aCommand) {
|
||||
if (aCommand == "msgDownloadsCmd_clearDownloads") {
|
||||
return true;
|
||||
}
|
||||
|
||||
let element = this.listElement.selectedItem;
|
||||
if (element) {
|
||||
return element.downloadItem.isCommandEnabled(aCommand);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
doCommand(aCommand) {
|
||||
if (aCommand == "msgDownloadsCmd_clearDownloads") {
|
||||
this.clearDownloads();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.listElement.selectedItems.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let [, element] in Iterator(this.listElement.selectedItems)) {
|
||||
element.downloadItem.doCommand(aCommand);
|
||||
}
|
||||
},
|
||||
|
||||
onEvent() { },
|
||||
|
||||
updateCommands() {
|
||||
this.commands.forEach(goUpdateCommand);
|
||||
DownloadItem.prototype.commands.forEach(goUpdateCommand);
|
||||
},
|
||||
|
||||
commands: [
|
||||
"msgDownloadsCmd_clearDownloads",
|
||||
]
|
||||
};
|
||||
|
||||
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(OS.Path.basename(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;
|
||||
}
|
||||
|
||||
const 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() this._download,
|
||||
|
||||
get element() {
|
||||
if (!this._element) {
|
||||
this._element = this.createElement();
|
||||
}
|
||||
|
||||
return this._element;
|
||||
},
|
||||
|
||||
createElement() {
|
||||
let element = document.createElement("richlistitem");
|
||||
element.classList.add("download");
|
||||
|
||||
// launch the download if double clicked
|
||||
element.addEventListener("dblclick", aEvent => this.launch());
|
||||
|
||||
// set download as an expando property for the context menu
|
||||
element.download = this.download;
|
||||
element.downloadItem = this;
|
||||
|
||||
this.updateElement(element);
|
||||
|
||||
return element;
|
||||
},
|
||||
|
||||
updateElement(aElement) {
|
||||
aElement.setAttribute("image", this.iconUrl);
|
||||
aElement.setAttribute("size", this.size);
|
||||
aElement.setAttribute("displayName", this.fileName);
|
||||
aElement.setAttribute("sender", this.sender);
|
||||
aElement.setAttribute("startDate", this.startDate);
|
||||
},
|
||||
|
||||
launch() {
|
||||
if (this.download.succeeded) {
|
||||
this.download.launch().then(null, Cu.reportError);
|
||||
}
|
||||
},
|
||||
|
||||
remove() {
|
||||
Downloads.getList(Downloads.ALL)
|
||||
.then(list => list.remove(this.download))
|
||||
.then(() => this.download.finalize(true))
|
||||
.then(null, Cu.reportError);
|
||||
},
|
||||
|
||||
show() {
|
||||
if (this.download.succeeded) {
|
||||
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
||||
file.initWithPath(this._filePath);
|
||||
file.reveal();
|
||||
}
|
||||
},
|
||||
|
||||
onDownloadChanged() {
|
||||
this._updateFromDownload();
|
||||
this.updateElement(this.element);
|
||||
},
|
||||
|
||||
get fileName() this._fileName,
|
||||
|
||||
get iconUrl() this._iconUrl,
|
||||
|
||||
get sender() 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() this._startDate,
|
||||
|
||||
supportsCommand(aCommand) {
|
||||
return this.commands.some(command => command == 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",
|
||||
]
|
||||
};
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/"?>
|
||||
<?xml-stylesheet href="chrome://messenger/content/downloads/aboutDownloads.css" type="text/css"?>
|
||||
<!-- 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 window [
|
||||
<!ENTITY % aboutDownloadsDTD SYSTEM "chrome://messenger/locale/aboutDownloads.dtd">
|
||||
%aboutDownloadsDTD;
|
||||
]>
|
||||
|
||||
<window id="aboutDownloads"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="&aboutDownloads.title;"
|
||||
onload="DownloadsView.init();">
|
||||
<script type="application/javascript"
|
||||
src="chrome://global/content/globalOverlay.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://messenger/content/downloads/aboutDownloads.js"/>
|
||||
|
||||
<stack flex="1">
|
||||
<richlistbox id="msgDownloadsRichListBox"
|
||||
flex="1"
|
||||
seltype="multiple"
|
||||
context="msgDownloadsContextMenu"
|
||||
oncontextmenu="DownloadsView.onDownloadContextMenu(event);"/>
|
||||
<description id="msgDownloadsListEmptyDescription"
|
||||
value="&aboutDownloads.empty;"
|
||||
mousethrough="always"/>
|
||||
</stack>
|
||||
|
||||
<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')"/>
|
||||
</commandset>
|
||||
|
||||
<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.clearDownloads.label;"
|
||||
accesskey="&cmd.clearDownloads.accesskey;"/>
|
||||
</menupopup>
|
||||
</window>
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- -->
|
||||
<!-- vim: set ts=2 et sw=2 tw=80: -->
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<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"
|
||||
extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
|
||||
|
||||
<content orient="horizontal" align="center">
|
||||
<xul:image class="fileTypeIcon"
|
||||
validate="always"
|
||||
xbl:inherits="src=image"/>
|
||||
<xul:vbox pack="center" flex="1">
|
||||
<xul:description class="fileName"
|
||||
crop="center"
|
||||
xbl:inherits="value=displayName,tooltiptext=displayName"/>
|
||||
<xul:description class="size"
|
||||
xbl:inherits="value=size,tooltiptext=size"/>
|
||||
<xul:description class="startDate"
|
||||
crop="end"
|
||||
xbl:inherits="value=startDate,tooltiptext=startDate"/>
|
||||
</xul:vbox>
|
||||
<xul:description class="sender"
|
||||
xbl:inherits="value=sender,tooltiptext=sender"/>
|
||||
</content>
|
||||
</binding>
|
||||
|
||||
</bindings>
|
|
@ -0,0 +1,9 @@
|
|||
# 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/download.xml (content/download.xml)
|
||||
content/messenger/downloads/aboutDownloads.js (content/aboutDownloads.js)
|
||||
* content/messenger/downloads/aboutDownloads.xul (content/aboutDownloads.xul)
|
||||
content/messenger/downloads/aboutDownloads.css (content/aboutDownloads.css)
|
|
@ -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']
|
|
@ -4,6 +4,7 @@ component {8cc51368-6aa0-43e8-b762-bde9b9fd828c} aboutRedirector.js
|
|||
contract @mozilla.org/network/protocol/about;1?what=rights {8cc51368-6aa0-43e8-b762-bde9b9fd828c}
|
||||
contract @mozilla.org/network/protocol/about;1?what=support {8cc51368-6aa0-43e8-b762-bde9b9fd828c}
|
||||
contract @mozilla.org/network/protocol/about;1?what=preferences {8cc51368-6aa0-43e8-b762-bde9b9fd828c}
|
||||
contract @mozilla.org/network/protocol/about;1?what=downloads {8cc51368-6aa0-43e8-b762-bde9b9fd828c}
|
||||
|
||||
component {44346520-c5d2-44e5-a1ec-034e04d7fac4} nsMailDefaultHandler.js
|
||||
contract @mozilla.org/mail/clh;1 {44346520-c5d2-44e5-a1ec-034e04d7fac4}
|
||||
|
@ -17,3 +18,7 @@ contract @mozilla.org/uriloader/content-handler;1?type=text/plain {1c73f03a-b817
|
|||
component {eb239c82-fac9-431e-98d7-11cacd0f71b8} mailGlue.js
|
||||
contract @mozilla.org/mail/mailglue;1 {eb239c82-fac9-431e-98d7-11cacd0f71b8}
|
||||
category app-startup MailGlue service,@mozilla.org/mail/mailglue;1
|
||||
|
||||
component {a93f0d6f-02a3-4486-a662-8f49b8c1de48} DownloadsStartup.js
|
||||
contract @mozilla.org/mail/downloadsstartup;1 {a93f0d6f-02a3-4486-a662-8f49b8c1de48}
|
||||
category profile-after-change DownloadsStartup @mozilla.org/mail/downloadsstartup;1
|
||||
|
|
|
@ -9,6 +9,7 @@ DIRS += [
|
|||
'compose',
|
||||
'cloudfile',
|
||||
'devtools',
|
||||
'downloads',
|
||||
'preferences',
|
||||
'addrbook',
|
||||
'migration',
|
||||
|
@ -35,6 +36,7 @@ XPIDL_MODULE = 'mailcompsbase'
|
|||
|
||||
EXTRA_COMPONENTS += [
|
||||
'aboutRedirector.js',
|
||||
'DownloadsStartup.js',
|
||||
'mailComponents.manifest',
|
||||
'mailContentHandler.js',
|
||||
'mailGlue.js',
|
||||
|
|
|
@ -43,3 +43,4 @@ MAR_CHANNEL_ID=thunderbird-comm-central
|
|||
# Enable generational GC on desktop.
|
||||
JSGC_GENERATIONAL=1
|
||||
MOZ_PROFILE_MIGRATOR=1
|
||||
MOZ_JSDOWNLOADS=1
|
||||
|
|
|
@ -371,11 +371,12 @@
|
|||
@RESPATH@/components/storage-json.js
|
||||
@RESPATH@/components/crypto-SDR.js
|
||||
|
||||
; download progress for jsdownloads
|
||||
@RESPATH@/components/DownloadsStartup.js
|
||||
|
||||
; download progress
|
||||
@RESPATH@/components/nsHelperAppDlg.js
|
||||
@RESPATH@/components/nsHelperAppDlg.manifest
|
||||
@RESPATH@/components/nsDownloadManagerUI.js
|
||||
@RESPATH@/components/nsDownloadManagerUI.manifest
|
||||
@RESPATH@/components/downloads.xpt
|
||||
|
||||
; Protocol/Content handling
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<!-- 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/. -->
|
||||
|
||||
<!ENTITY aboutDownloads.title "Saved Files">
|
||||
<!ENTITY aboutDownloads.empty "No Saved Files">
|
||||
<!-- LOCALIZATION NOTE (cmd.show.label, cmd.show.accesskey, cmd.showMac.label,
|
||||
cmd.showMac.accesskey):
|
||||
The show and showMac commands are never shown together, thus they can share
|
||||
the same access key (though the two access keys can also be different).
|
||||
-->
|
||||
<!ENTITY cmd.show.label "Open Containing Folder">
|
||||
<!ENTITY cmd.show.accesskey "F">
|
||||
<!ENTITY cmd.showMac.label "Show In Finder">
|
||||
<!ENTITY cmd.showMac.accesskey "F">
|
||||
<!ENTITY cmd.open.label "Open">
|
||||
<!ENTITY cmd.open.accesskey "O">
|
||||
<!ENTITY cmd.removeFromHistory.label "Remove From History">
|
||||
<!ENTITY cmd.removeFromHistory.accesskey "e">
|
||||
<!ENTITY cmd.clearDownloads.label "Clear Downloads">
|
||||
<!ENTITY cmd.clearDownloads.accesskey "D">
|
|
@ -10,6 +10,7 @@
|
|||
% override chrome://mozapps/locale/downloads/settingsChange.dtd chrome://messenger/locale/downloads/settingsChange.dtd
|
||||
% override chrome://global/locale/netError.dtd chrome://messenger/locale/netError.dtd
|
||||
locale/@AB_CD@/messenger/aboutDialog.dtd (%chrome/messenger/aboutDialog.dtd)
|
||||
locale/@AB_CD@/messenger/aboutDownloads.dtd (%chrome/messenger/aboutDownloads.dtd)
|
||||
locale/@AB_CD@/messenger/aboutRights.dtd (%chrome/messenger/aboutRights.dtd)
|
||||
locale/@AB_CD@/messenger/aboutRights.properties (%chrome/messenger/aboutRights.properties)
|
||||
locale/@AB_CD@/messenger/aboutSupportMail.dtd (%chrome/messenger/aboutSupportMail.dtd)
|
||||
|
|
|
@ -0,0 +1,296 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Test about:downloads.
|
||||
*/
|
||||
|
||||
var MODULE_NAME = 'test-about-download';
|
||||
|
||||
var RELATIVE_ROOT = "../shared-modules";
|
||||
var MODULE_REQUIRES = [ 'attachment-helpers',
|
||||
'content-tab-helpers',
|
||||
'dom-helpers',
|
||||
'folder-display-helpers',
|
||||
'prompt-helpers',
|
||||
'window-helpers' ];
|
||||
|
||||
var mozmill = {}; Components.utils.import('resource://mozmill/modules/mozmill.js', mozmill);
|
||||
var elementslib = {}; Components.utils.import('resource://mozmill/modules/elementslib.js', elementslib);
|
||||
var downloads = {}; Components.utils.import("resource://gre/modules/Downloads.jsm", downloads);
|
||||
|
||||
var ah;
|
||||
|
||||
var downloadsTab;
|
||||
|
||||
var attachmentFileNames = [
|
||||
"Attachment#1.txt",
|
||||
"Attachment#2.txt",
|
||||
"Attachment#3.txt",
|
||||
];
|
||||
|
||||
const downloadsView = {
|
||||
init() {
|
||||
this.items = new Map();
|
||||
this.removedItems = [];
|
||||
},
|
||||
|
||||
get count() {
|
||||
return this.items.size;
|
||||
},
|
||||
|
||||
onDownloadAdded(aDownload) {
|
||||
this.items.set(aDownload, aDownload.target.path);
|
||||
},
|
||||
|
||||
onDownloadChanged(aDownload) {
|
||||
},
|
||||
|
||||
onDownloadRemoved(aDownload) {
|
||||
this.removedItems.push(aDownload.target.path);
|
||||
this.items.delete(aDownload);
|
||||
},
|
||||
|
||||
waitForFinish() {
|
||||
for (let download of this.items.keys()) {
|
||||
let succeededPromise = download.whenSucceeded();
|
||||
yield succeededPromise;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function prepare_messages() {
|
||||
let folder = create_folder("about:downloads");
|
||||
let msgSet = make_new_sets_in_folder(folder, [
|
||||
{
|
||||
count: 1,
|
||||
attachments: [{
|
||||
filename: attachmentFileNames[0],
|
||||
body: "Body"
|
||||
}]
|
||||
},
|
||||
{
|
||||
count: 1,
|
||||
attachments: [{
|
||||
filename: attachmentFileNames[1],
|
||||
body: "Body"
|
||||
}]
|
||||
},
|
||||
{
|
||||
count: 1,
|
||||
attachments: [{
|
||||
filename: attachmentFileNames[2],
|
||||
body: "Body"
|
||||
}]
|
||||
}
|
||||
]);
|
||||
be_in_folder(folder);
|
||||
}
|
||||
|
||||
function prepare_downloads_view() {
|
||||
let success = false;
|
||||
downloads.Downloads.getList(downloads.Downloads.ALL)
|
||||
.then(list => list.addView(downloadsView))
|
||||
.then(() => success = true, Cu.reportError);
|
||||
mc.waitFor(() => success, "Timeout waiting for attaching our download view.");
|
||||
}
|
||||
|
||||
function setupModule(module) {
|
||||
let fdh = collector.getModule("folder-display-helpers");
|
||||
fdh.installInto(module);
|
||||
let wh = collector.getModule('window-helpers');
|
||||
wh.installInto(module);
|
||||
let cth = collector.getModule("content-tab-helpers");
|
||||
cth.installInto(module);
|
||||
let dh = collector.getModule('dom-helpers');
|
||||
dh.installInto(module);
|
||||
ah = collector.getModule('attachment-helpers');
|
||||
ah.installInto(module);
|
||||
ah.gMockFilePickReg.register();
|
||||
|
||||
prepare_messages();
|
||||
prepare_downloads_view();
|
||||
}
|
||||
|
||||
function setupTest(test) {
|
||||
downloadsView.init();
|
||||
}
|
||||
|
||||
function open_about_downloads() {
|
||||
let preCount = mc.tabmail.tabContainer.childNodes.length;
|
||||
let newTab = mc.tabmail.openTab("chromeTab", { chromePage: "about:downloads",
|
||||
clickHandler: "specialTabs.aboutClickHandler(event);" });
|
||||
mc.waitFor(() => mc.tabmail.tabContainer.childNodes.length == preCount + 1,
|
||||
"Timeout waiting for about:downloads tab");
|
||||
|
||||
wait_for_browser_load(newTab.browser, "about:downloads");
|
||||
// We append new tabs at the end, so check the last one.
|
||||
let expectedNewTab = mc.tabmail.tabInfo[preCount];
|
||||
return expectedNewTab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that there is no file in the list at first.
|
||||
*/
|
||||
function test_empty_list() {
|
||||
downloadsTab = open_about_downloads();
|
||||
|
||||
switch_tab(downloadsTab);
|
||||
|
||||
let empty = content_tab_e(downloadsTab, "msgDownloadsListEmptyDescription");
|
||||
assert_false(empty.hidden, "msgDownloadsListEmptyDescription is not visible");
|
||||
}
|
||||
|
||||
function save_attachment_files() {
|
||||
switch_tab(0);
|
||||
|
||||
let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
||||
|
||||
let length = attachmentFileNames.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
let file = profileDir.clone();
|
||||
file.append(attachmentFileNames[i]);
|
||||
select_click_row(i);
|
||||
gMockFilePicker.returnFiles = [ file ];
|
||||
mc.click(mc.eid("attachmentSaveAllSingle",
|
||||
{"class": "toolbarbutton-menubutton-button"}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that all downloaded files are showed up in the list.
|
||||
*/
|
||||
function test_save_attachment_files_in_list() {
|
||||
save_attachment_files();
|
||||
|
||||
mc.tabmail.switchToTab(downloadsTab);
|
||||
let list = content_tab_e(downloadsTab, "msgDownloadsRichListBox");
|
||||
|
||||
let length = attachmentFileNames.length;
|
||||
mc.waitFor(() => downloadsView.count == length,
|
||||
"Timeout waiting for saving three attachment files.");
|
||||
|
||||
assert_equals(length, list.childNodes.length);
|
||||
assert_equals(downloadsView.count, list.childNodes.length);
|
||||
|
||||
let actualNames = [];
|
||||
let child = list.firstChild;
|
||||
while (child) {
|
||||
actualNames.push(child.getAttribute("displayName"));
|
||||
child = child.nextSibling;
|
||||
}
|
||||
actualNames.sort();
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
assert_equals(attachmentFileNames[i], actualNames[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that 'remove' in context menu removes surely the target file from
|
||||
* the list.
|
||||
*/
|
||||
function test_remove_file() {
|
||||
test_save_attachment_files_in_list();
|
||||
|
||||
let list = content_tab_e(downloadsTab, "msgDownloadsRichListBox");
|
||||
let firstElement = list.firstChild;
|
||||
let removingFileName = firstElement.getAttribute("displayName");
|
||||
|
||||
// select first element
|
||||
mc.click(new elementslib.Elem(firstElement));
|
||||
mc.rightClick(new elementslib.Elem(firstElement));
|
||||
|
||||
let contextMenu = content_tab_e(downloadsTab, "msgDownloadsContextMenu");
|
||||
wait_for_popup_to_open(contextMenu);
|
||||
mc.click_menus_in_sequence(contextMenu, [
|
||||
{ command: "msgDownloadsCmd_remove" }
|
||||
]);
|
||||
mc.waitFor(() => downloadsView.count == 2,
|
||||
"Timeout waiting for removing a saved attachment file.");
|
||||
|
||||
child = list.firstChild;
|
||||
while (child) {
|
||||
assert_not_equals(removingFileName, child.getAttribute("displayName"));
|
||||
child = child.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that removing multiple files surely removes the files.
|
||||
*/
|
||||
function test_remove_multiple_files() {
|
||||
test_save_attachment_files_in_list();
|
||||
|
||||
let list = content_tab_e(downloadsTab, "msgDownloadsRichListBox");
|
||||
let firstElement = list.firstChild;
|
||||
let secondElement = firstElement.nextSibling;
|
||||
let removingFileNames = [];
|
||||
|
||||
removingFileNames.push(firstElement.getAttribute("displayName"));
|
||||
removingFileNames.push(secondElement.getAttribute("displayName"));
|
||||
|
||||
// select two elements
|
||||
mc.click(new elementslib.Elem(firstElement));
|
||||
list.selectItemRange(firstElement, secondElement);
|
||||
mc.rightClick(new elementslib.Elem(firstElement));
|
||||
|
||||
let contextMenu = content_tab_e(downloadsTab, "msgDownloadsContextMenu");
|
||||
wait_for_popup_to_open(contextMenu);
|
||||
mc.click_menus_in_sequence(contextMenu, [
|
||||
{ command: "msgDownloadsCmd_remove" }
|
||||
]);
|
||||
mc.waitFor(() => downloadsView.count == 1,
|
||||
"Timeout waiting for removing two saved attachment files.");
|
||||
|
||||
child = list.firstChild;
|
||||
while (child) {
|
||||
for (let [, name] in Iterator(removingFileNames)) {
|
||||
assert_not_equals(name, child.getAttribute("displayName"));
|
||||
}
|
||||
child = child.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that 'clearDownloads" in context menu purges all files in the list.
|
||||
*/
|
||||
function test_clear_all_files() {
|
||||
test_save_attachment_files_in_list();
|
||||
downloadsView.waitForFinish();
|
||||
|
||||
mc.click(content_tab_eid(downloadsTab, "msgDownloadsRichListBox"));
|
||||
mc.rightClick(content_tab_eid(downloadsTab, "msgDownloadsRichListBox"));
|
||||
|
||||
let contextMenu = content_tab_e(downloadsTab, "msgDownloadsContextMenu");
|
||||
wait_for_popup_to_open(contextMenu);
|
||||
mc.click_menus_in_sequence(contextMenu, [
|
||||
{ command: "msgDownloadsCmd_clearDownloads" }
|
||||
]);
|
||||
mc.waitFor(() => downloadsView.count == 0,
|
||||
"Timeout waiting for clearing all saved attachment files.");
|
||||
|
||||
let empty = content_tab_e(downloadsTab, "msgDownloadsListEmptyDescription");
|
||||
assert_false(empty.hidden, "msgDownloadsListEmptyDescription is not visible");
|
||||
}
|
||||
|
||||
function teardownTest() {
|
||||
downloads.Downloads.getList(downloads.Downloads.ALL)
|
||||
.then(function(list) {
|
||||
for (let download of downloadsView.items.keys()) {
|
||||
list.remove(download);
|
||||
}
|
||||
})
|
||||
.then(null, Cu.reportError);
|
||||
mc.waitFor(() => downloadsView.count == 0,
|
||||
"Timeout waiting for clearing all saved attachment files.");
|
||||
let empty = content_tab_e(downloadsTab, "msgDownloadsListEmptyDescription");
|
||||
mc.waitFor(() => empty.hidden == false,
|
||||
"Timeout waiting for msgDownloadsListEmptyDescription is visible.");
|
||||
}
|
||||
|
||||
function teardownModule(module) {
|
||||
close_tab(downloadsTab);
|
||||
ah.gMockFilePickReg.unregister();
|
||||
}
|
|
@ -7,6 +7,7 @@ content-policy
|
|||
content-tabs
|
||||
cookies
|
||||
crypto
|
||||
downloads
|
||||
folder-display
|
||||
folder-pane
|
||||
folder-tree-modes
|
||||
|
|
|
@ -1777,6 +1777,10 @@ nsresult nsSaveMsgListener::InitializeDownload(nsIRequest * aRequest, uint32_t a
|
|||
// so make an arbitrary decision based on the content length of the
|
||||
// attachment -- show it if less than half of the download has completed
|
||||
|
||||
// Set saveToDisk explicitly to avoid launching the saved file.
|
||||
// See http://hg.mozilla.org/mozilla-central/file/814a6f071472/toolkit/components/jsdownloads/src/DownloadLegacy.js#l164
|
||||
mimeinfo->SetPreferredAction(nsIHandlerInfo::saveToDisk);
|
||||
|
||||
// When we don't allow warnings, also don't show progress, as this
|
||||
// is an environment (typically filters) where we don't want
|
||||
// interruption.
|
||||
|
|
Загрузка…
Ссылка в новой задаче