summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--application/palemoon/components/downloads/DownloadsCommon.jsm239
-rw-r--r--application/palemoon/components/downloads/content/allDownloadsViewOverlay.js196
-rw-r--r--application/palemoon/components/downloads/content/downloads.js82
-rw-r--r--application/palemoon/components/downloads/content/downloadsViewCommon.js110
4 files changed, 321 insertions, 306 deletions
diff --git a/application/palemoon/components/downloads/DownloadsCommon.jsm b/application/palemoon/components/downloads/DownloadsCommon.jsm
index aac43d197..c2185f623 100644
--- a/application/palemoon/components/downloads/DownloadsCommon.jsm
+++ b/application/palemoon/components/downloads/DownloadsCommon.jsm
@@ -379,6 +379,26 @@ this.DownloadsCommon = {
},
/**
+ * Returns the highest number of bytes transferred or the known size of the
+ * given Download object, or -1 if the size is not available. Callers should
+ * use Download properties directly when possible.
+ */
+ maxBytesOfDownload(download) {
+ if (download.succeeded) {
+ // If the download succeeded, show the final size if available, otherwise
+ // use the last known number of bytes transferred. The final size on disk
+ // will be available when bug 941063 is resolved.
+ return download.hasProgress ? download.totalBytes : download.currentBytes;
+ } else if (download.hasProgress) {
+ // If the final size and progress are known, use them.
+ return download.totalBytes;
+ } else {
+ // The download final size and progress percentage is unknown.
+ return -1;
+ }
+ },
+
+ /**
* Given an iterable collection of DownloadDataItems, generates and returns
* statistics about that collection.
*
@@ -418,8 +438,12 @@ this.DownloadsCommon = {
}
for (let dataItem of aDataItems) {
+ let download = dataItem.download;
+ let state = DownloadsCommon.stateOfDownload(download);
+ let maxBytes = DownloadsCommon.maxBytesOfDownload(download);
+
summary.numActive++;
- switch (dataItem.state) {
+ switch (state) {
case nsIDM.DOWNLOAD_PAUSED:
summary.numPaused++;
break;
@@ -428,21 +452,20 @@ this.DownloadsCommon = {
break;
case nsIDM.DOWNLOAD_DOWNLOADING:
summary.numDownloading++;
- if (dataItem.maxBytes > 0 && dataItem.download.speed > 0) {
- let sizeLeft = dataItem.maxBytes - dataItem.download.currentBytes;
+ if (maxBytes > 0 && download.speed > 0) {
+ let sizeLeft = maxBytes - download.currentBytes;
summary.rawTimeLeft = Math.max(summary.rawTimeLeft,
- sizeLeft / dataItem.download.speed);
+ sizeLeft / download.speed);
summary.slowestSpeed = Math.min(summary.slowestSpeed,
- dataItem.download.speed);
+ download.speed);
}
break;
}
// Only add to total values if we actually know the download size.
- if (dataItem.maxBytes > 0 &&
- dataItem.state != nsIDM.DOWNLOAD_CANCELED &&
- dataItem.state != nsIDM.DOWNLOAD_FAILED) {
- summary.totalSize += dataItem.maxBytes;
- summary.totalTransferred += dataItem.download.currentBytes;
+ if (maxBytes > 0 && state != nsIDM.DOWNLOAD_CANCELED &&
+ state != nsIDM.DOWNLOAD_FAILED) {
+ summary.totalSize += maxBytes;
+ summary.totalTransferred += download.currentBytes;
}
}
@@ -498,7 +521,7 @@ this.DownloadsCommon = {
/**
* Opens a downloaded file.
- * If you've a dataItem, you should call dataItem.openLocalFile.
+ *
* @param aFile
* the downloaded file to be opened.
* @param aMimeInfo
@@ -649,6 +672,7 @@ function DownloadsDataCtor(aPrivate) {
// Contains all the available DownloadsDataItem objects.
this.dataItems = new Set();
+ this.oldDownloadStates = new Map();
// Array of view objects that should be notified when the available download
// data changes.
@@ -687,7 +711,9 @@ DownloadsDataCtor.prototype = {
get canRemoveFinished()
{
for (let dataItem of this.dataItems) {
- if (!dataItem.inProgress) {
+ let download = dataItem.download;
+ // Stopped, paused, and failed downloads with partial data are removed.
+ if (download.stopped && !(download.canceled && download.hasPartialData)) {
return true;
}
}
@@ -713,99 +739,97 @@ DownloadsDataCtor.prototype = {
let dataItem = new DownloadsDataItem(aDownload);
this._downloadToDataItemMap.set(aDownload, dataItem);
this.dataItems.add(dataItem);
+ this.oldDownloadStates.set(aDownload,
+ DownloadsCommon.stateOfDownload(aDownload));
for (let view of this._views) {
view.onDataItemAdded(dataItem, true);
}
-
- this._updateDataItemState(dataItem);
},
onDownloadChanged: function (aDownload)
{
- let dataItem = this._downloadToDataItemMap.get(aDownload);
- if (!dataItem) {
- Cu.reportError("Download doesn't exist.");
- return;
- }
-
- this._updateDataItemState(dataItem);
- },
-
- onDownloadRemoved: function (aDownload)
- {
- let dataItem = this._downloadToDataItemMap.get(aDownload);
- if (!dataItem) {
+ let aDataItem = this._downloadToDataItemMap.get(aDownload);
+ if (!aDataItem) {
Cu.reportError("Download doesn't exist.");
return;
}
- this._downloadToDataItemMap.delete(aDownload);
- this.dataItems.delete(dataItem);
- for (let view of this._views) {
- view.onDataItemRemoved(dataItem);
- }
- },
-
- /**
- * Updates the given data item and sends related notifications.
- */
- _updateDataItemState: function (aDataItem)
- {
- let oldState = aDataItem.state;
- let wasInProgress = aDataItem.inProgress;
- let wasDone = aDataItem.done;
-
- aDataItem.updateFromJSDownload();
-
- if (wasInProgress && !aDataItem.inProgress) {
- aDataItem.endTime = Date.now();
- }
+ let oldState = this.oldDownloadStates.get(aDownload);
+ let newState = DownloadsCommon.stateOfDownload(aDownload);
+ this.oldDownloadStates.set(aDownload, newState);
+
+ if (oldState != newState) {
+ if (aDownload.succeeded ||
+ (aDownload.canceled && !aDownload.hasPartialData) ||
+ aDownload.error) {
+ // Store the end time that may be displayed by the views.
+ aDownload.endTime = Date.now();
+
+ // This state transition code should actually be located in a Downloads
+ // API module (bug 941009). Moreover, the fact that state is stored as
+ // annotations should be ideally hidden behind methods of
+ // nsIDownloadHistory (bug 830415).
+ if (!this._isPrivate) {
+ try {
+ let downloadMetaData = {
+ state: DownloadsCommon.stateOfDownload(aDownload),
+ endTime: aDownload.endTime,
+ };
+ if (aDownload.succeeded ||
+ (aDownload.error && aDownload.error.becauseBlocked)) {
+ downloadMetaData.fileSize =
+ DownloadsCommon.maxBytesOfDownload(aDataItem.download);
+ }
+
+ PlacesUtils.annotations.setPageAnnotation(
+ NetUtil.newURI(aDownload.source.url),
+ "downloads/metaData",
+ JSON.stringify(downloadMetaData), 0,
+ PlacesUtils.annotations.EXPIRE_WITH_HISTORY);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ }
- if (oldState != aDataItem.state) {
for (let view of this._views) {
try {
- view.onDataItemStateChanged(aDataItem, oldState);
+ view.onDataItemStateChanged(aDataItem);
} catch (ex) {
Cu.reportError(ex);
}
}
- // This state transition code should actually be located in a Downloads
- // API module (bug 941009). Moreover, the fact that state is stored as
- // annotations should be ideally hidden behind methods of
- // nsIDownloadHistory (bug 830415).
- if (!this._isPrivate && !aDataItem.inProgress) {
- try {
- let downloadMetaData = { state: aDataItem.state,
- endTime: aDataItem.endTime };
- if (aDataItem.done) {
- downloadMetaData.fileSize = aDataItem.maxBytes;
- }
-
- // RRR: Annotation service throws here. commented out for now.
- /*PlacesUtils.annotations.setPageAnnotation(
- NetUtil.newURI(aDataItem.download.source.url),
- "downloads/metaData",
- JSON.stringify(downloadMetaData), 0,
- PlacesUtils.annotations.EXPIRE_WITH_HISTORY);*/
- } catch (ex) {
- Cu.reportError(ex);
- }
+ if (aDownload.succeeded ||
+ (aDownload.error && aDownload.error.becauseBlocked)) {
+ this._notifyDownloadEvent("finish");
}
}
- if (!aDataItem.newDownloadNotified) {
- aDataItem.newDownloadNotified = true;
+ if (!aDownload.newDownloadNotified) {
+ aDownload.newDownloadNotified = true;
this._notifyDownloadEvent("start");
}
- if (!wasDone && aDataItem.done) {
- this._notifyDownloadEvent("finish");
+ for (let view of this._views) {
+ view.onDataItemChanged(aDataItem);
+ }
+ },
+
+ onDownloadRemoved: function (aDownload)
+ {
+ let dataItem = this._downloadToDataItemMap.get(aDownload);
+ if (!dataItem) {
+ Cu.reportError("Download doesn't exist.");
+ return;
}
+ this._downloadToDataItemMap.delete(aDownload);
+ this.dataItems.delete(dataItem);
+ this.oldDownloadStates.delete(aDownload);
for (let view of this._views) {
- view.onDataItemChanged(aDataItem);
+ view.onDataItemRemoved(dataItem);
}
},
@@ -1333,6 +1357,8 @@ function DownloadsDataItem(aSource)
}
DownloadsDataItem.prototype = {
+ get state() DownloadsCommon.stateOfDownload(this.download),
+
/**
* Initializes this object from the JavaScript API for downloads.
*
@@ -1344,51 +1370,7 @@ DownloadsDataItem.prototype = {
_initFromJSDownload: function (aDownload)
{
this.download = aDownload;
- this.endTime = Date.now();
-
- this.updateFromJSDownload();
- },
-
- /**
- * Updates this object from the JavaScript API for downloads.
- */
- updateFromJSDownload: function ()
- {
- // Collapse state using the correct priority.
- if (this.download.succeeded) {
- this.state = nsIDM.DOWNLOAD_FINISHED;
- } else if (this.download.error &&
- this.download.error.becauseBlockedByParentalControls) {
- this.state = nsIDM.DOWNLOAD_BLOCKED_PARENTAL;
- } else if (this.download.error) {
- this.state = nsIDM.DOWNLOAD_FAILED;
- } else if (this.download.canceled && this.download.hasPartialData) {
- this.state = nsIDM.DOWNLOAD_PAUSED;
- } else if (this.download.canceled) {
- this.state = nsIDM.DOWNLOAD_CANCELED;
- } else if (this.download.stopped) {
- this.state = nsIDM.DOWNLOAD_NOTSTARTED;
- } else {
- this.state = nsIDM.DOWNLOAD_DOWNLOADING;
- }
-
- if (this.download.succeeded) {
- // If the download succeeded, show the final size if available, otherwise
- // use the last known number of bytes transferred. The final size on disk
- // will be available when bug 941063 is resolved.
- this.maxBytes = this.download.hasProgress ?
- this.download.totalBytes :
- this.download.currentBytes;
- this.percentComplete = 100;
- } else if (this.download.hasProgress) {
- // If the final size and progress are known, use them.
- this.maxBytes = this.download.totalBytes;
- this.percentComplete = this.download.progress;
- } else {
- // The download final size and progress percentage is unknown.
- this.maxBytes = -1;
- this.percentComplete = -1;
- }
+ this.download.endTime = Date.now();
},
/**
@@ -1896,9 +1878,10 @@ DownloadsIndicatorDataCtor.prototype = {
},
// DownloadsView
- onDataItemStateChanged(aDataItem, aOldState) {
- if (aDataItem.state == nsIDM.DOWNLOAD_FINISHED ||
- aDataItem.state == nsIDM.DOWNLOAD_FAILED) {
+ onDataItemStateChanged(aDataItem) {
+ let download = aDataItem.download;
+
+ if (download.succeeded || download.error) {
this.attention = true;
}
@@ -2010,7 +1993,11 @@ DownloadsIndicatorDataCtor.prototype = {
let dataItems = this._isPrivate ? PrivateDownloadsData.dataItems
: DownloadsData.dataItems;
for (let dataItem of dataItems) {
- if (dataItem && dataItem.inProgress) {
+ if (!dataItem) {
+ continue;
+ }
+ let download = dataItem.download;
+ if (!download.stopped || (download.canceled && download.hasPartialData)) {
yield dataItem;
}
}
@@ -2164,7 +2151,7 @@ DownloadsSummaryData.prototype = {
},
// DownloadsView
- onDataItemStateChanged(aOldState) {
+ onDataItemStateChanged() {
// Since the state of a download changed, reset the estimated time left.
this._lastRawTimeLeft = -1;
this._lastTimeLeft = -1;
diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js
index b85b64c1c..59da61c91 100644
--- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js
+++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js
@@ -24,69 +24,48 @@ const DOWNLOAD_VIEW_SUPPORTED_COMMANDS =
*
* @param url
* URI string for the download source.
+ * @param endTime
+ * Timestamp with the end time for the download, used if there is no
+ * additional metadata available.
*/
-function HistoryDownload(url) {
+function HistoryDownload(aPlacesNode) {
// TODO (bug 829201): history downloads should get the referrer from Places.
- this.source = { url };
+ this.source = { url: aPlacesNode.uri };
this.target = { path: undefined, size: undefined };
-}
-
-HistoryDownload.prototype = {
- /**
- * This method mimicks the "start" method of session downloads, and is called
- * when the user retries a history download.
- */
- start() {
- // In future we may try to download into the same original target uri, when
- // we have it. Though that requires verifying the path is still valid and
- // may surprise the user if he wants to be requested every time.
- let browserWin = RecentWindow.getMostRecentBrowserWindow();
- let initiatingDoc = browserWin ? browserWin.document : document;
-
- // Do not suggest a file name if we don't know the original target.
- let leafName = this.target.path ? OS.Path.basename(this.target.path) : null;
- DownloadURL(this.source.url, leafName, initiatingDoc);
-
- return Promise.resolve();
- },
-};
-
-/**
- * Represents a download from the browser history. It uses the same interface as
- * the DownloadsDataItem object.
- *
- * @param aPlacesNode
- * The Places node for the history download.
- */
-function DownloadsHistoryDataItem(aPlacesNode) {
- this.download = new HistoryDownload(aPlacesNode.uri);
// In case this download cannot obtain its end time from the Places metadata,
// use the time from the Places node, that is the start time of the download.
this.endTime = aPlacesNode.time / 1000;
}
-DownloadsHistoryDataItem.prototype = {
- __proto__: DownloadsDataItem.prototype,
-
+HistoryDownload.prototype = {
/**
* Pushes information from Places metadata into this object.
*/
updateFromMetaData(aPlacesMetaData) {
try {
- let targetFile = Cc["@mozilla.org/network/protocol;1?name=file"]
- .getService(Ci.nsIFileProtocolHandler)
- .getFileFromURLSpec(aPlacesMetaData.targetFileURISpec);
- this.download.target.path = targetFile.path;
+ this.target.path = Cc["@mozilla.org/network/protocol;1?name=file"]
+ .getService(Ci.nsIFileProtocolHandler)
+ .getFileFromURLSpec(aPlacesMetaData.
+ targetFileURISpec).path;
} catch (ex) {
- this.download.target.path = undefined;
+ this.target.path = undefined;
}
try {
let metaData = JSON.parse(aPlacesMetaData.jsonDetails);
- this.state = metaData.state;
+ this.succeeded = metaData.state == nsIDM.DOWNLOAD_FINISHED;
+ this.error = metaData.state == nsIDM.DOWNLOAD_FAILED
+ ? { message: "History download failed." }
+ : metaData.state == nsIDM.DOWNLOAD_BLOCKED_PARENTAL
+ ? { becauseBlockedByParentalControls: true }
+ : metaData.state == nsIDM.DOWNLOAD_DIRTY
+ ? { becauseBlockedByReputationCheck: true }
+ : null;
+ this.canceled = metaData.state == nsIDM.DOWNLOAD_CANCELED ||
+ metaData.state == nsIDM.DOWNLOAD_PAUSED;
this.endTime = metaData.endTime;
- this.download.target.size = metaData.fileSize;
+ this.target.size = metaData.fileSize;
} catch (ex) {
// Metadata might be missing from a download that has started but hasn't
// stopped already. Normally, this state is overridden with the one from
@@ -98,21 +77,72 @@ DownloadsHistoryDataItem.prototype = {
// On the other hand, if the download is missing the target file
// annotation as well, it is just a very old one, and we can assume it
// succeeded.
- this.state = this.download.target.path ? nsIDM.DOWNLOAD_FAILED
- : nsIDM.DOWNLOAD_FINISHED;
- this.download.target.size = undefined;
+ this.succeeded = !this.target.path;
+ this.error = this.target.path ? { message: "Unstarted download." } : null;
+ this.canceled = false;
+ this.target.size = -1;
}
// This property is currently used to get the size of downloads, but will be
// replaced by download.target.size when available for session downloads.
- this.maxBytes = this.download.target.size;
+ this.totalBytes = this.target.size;
+ this.currentBytes = this.target.size;
+ },
+
+ /**
+ * History downloads are never in progress.
+ */
+ stopped: true,
- // This is not displayed for history downloads, that are never in progress.
- this.percentComplete = 100;
+ /**
+ * No percentage indication is shown for history downloads.
+ */
+ hasProgress: false,
+
+ /**
+ * History downloads cannot be restarted using their partial data, even if
+ * they are indicated as paused in their Places metadata. The only way is to
+ * use the information from a persisted session download, that will be shown
+ * instead of the history download. In case this session download is not
+ * available, we show the history download as canceled, not paused.
+ */
+ hasPartialData: false,
+
+ /**
+ * This method mimicks the "start" method of session downloads, and is called
+ * when the user retries a history download.
+ */
+ start() {
+ // In future we may try to download into the same original target uri, when
+ // we have it. Though that requires verifying the path is still valid and
+ // may surprise the user if he wants to be requested every time.
+ let browserWin = RecentWindow.getMostRecentBrowserWindow();
+ let initiatingDoc = browserWin ? browserWin.document : document;
+
+ // Do not suggest a file name if we don't know the original target.
+ let leafName = this.target.path ? OS.Path.basename(this.target.path) : null;
+ DownloadURL(this.source.url, leafName, initiatingDoc);
+
+ return Promise.resolve();
},
};
/**
+ * Represents a download from the browser history. It uses the same interface as
+ * the DownloadsDataItem object.
+ *
+ * @param aPlacesNode
+ * The Places node for the history download.
+ */
+function DownloadsHistoryDataItem(aPlacesNode) {
+ this.download = new HistoryDownload(aPlacesNode);
+}
+
+DownloadsHistoryDataItem.prototype = {
+ __proto__: DownloadsDataItem.prototype,
+};
+
+/**
* A download element shell is responsible for handling the commands and the
* displayed data for a single download view element.
*
@@ -223,7 +253,7 @@ HistoryDownloadElementShell.prototype = {
// The base object would show extended progress information in the tooltip,
// but we move this to the main view and never display a tooltip.
- if (this.dataItem.state == nsIDM.DOWNLOAD_DOWNLOADING) {
+ if (!this.download.stopped) {
status.text = status.tip;
}
status.tip = "";
@@ -232,18 +262,9 @@ HistoryDownloadElementShell.prototype = {
},
onStateChanged() {
- // If a download just finished successfully, it means that the target file
- // now exists and we can extract its specific icon. To ensure that the icon
- // is reloaded, we must change the URI used by the XUL image element, for
- // example by adding a query parameter. Since this URI has a "moz-icon"
- // scheme, this only works if we add one of the parameters explicitly
- // supported by the nsIMozIconURI interface.
- if (this.dataItem.state == nsIDM.DOWNLOAD_FINISHED) {
- this.element.setAttribute("image", this.image + "&state=normal");
- }
-
- // Update the user interface after switching states.
- this.element.setAttribute("state", this.dataItem.state);
+ this.element.setAttribute("image", this.image);
+ this.element.setAttribute("state",
+ DownloadsCommon.stateOfDownload(this.download));
if (this.element.selected) {
goUpdateDownloadCommands();
@@ -275,12 +296,14 @@ HistoryDownloadElementShell.prototype = {
// If the target file information is not yet fetched,
// temporarily assume that the file is in place.
- return this.dataItem.state == nsIDM.DOWNLOAD_FINISHED;
+ return this.download.succeeded;
case "downloadsCmd_show":
// TODO: Bug 827010 - Handle part-file asynchronously.
- if (this._sessionDataItem &&
- this.dataItem.partFile && this.dataItem.partFile.exists()) {
- return true;
+ if (this._sessionDataItem && this.download.target.partFilePath) {
+ let partFile = new FileUtils.File(this.download.target.partFilePath);
+ if (partFile.exists()) {
+ return true;
+ }
}
if (this._targetFileChecked) {
@@ -289,17 +312,16 @@ HistoryDownloadElementShell.prototype = {
// If the target file information is not yet fetched,
// temporarily assume that the file is in place.
- return this.dataItem.state == nsIDM.DOWNLOAD_FINISHED;
+ return this.download.succeeded;
case "downloadsCmd_pauseResume":
- return this._sessionDataItem && this.dataItem.inProgress &&
- this.dataItem.download.hasPartialData;
+ return this.download.hasPartialData && !this.download.error;
case "downloadsCmd_retry":
- return this.dataItem.canRetry;
+ return this.download.canceled || this.download.error;
case "downloadsCmd_openReferrer":
return !!this.download.source.referrer;
case "cmd_delete":
- // The behavior in this case is somewhat unexpected, so we disallow that.
- return !this.dataItem.inProgress;
+ // We don't want in-progress downloads to be removed accidentally.
+ return this.download.stopped;
case "downloadsCmd_cancel":
return !!this._sessionDataItem;
}
@@ -393,7 +415,8 @@ HistoryDownloadElementShell.prototype = {
}
return "";
}
- let command = getDefaultCommandForState(this.dataItem.state);
+ let command = getDefaultCommandForState(
+ DownloadsCommon.stateOfDownload(this.download));
if (command && this.isCommandEnabled(command))
this.doCommand(command);
},
@@ -621,8 +644,9 @@ DownloadsPlacesView.prototype = {
_addDownloadData:
function DPV_addDownloadData(aDataItem, aPlacesNode, aNewest = false,
aDocumentFragment = null) {
+ let sessionDownload = aDataItem && aDataItem.download;
let downloadURI = aPlacesNode ? aPlacesNode.uri
- : aDataItem.download.source.url;
+ : sessionDownload.source.url;
let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI);
if (!shellsForURI) {
shellsForURI = new Set();
@@ -674,7 +698,7 @@ DownloadsPlacesView.prototype = {
if (aPlacesNode) {
let metaData = this._getCachedPlacesMetaDataFor(aPlacesNode.uri);
historyDataItem = new DownloadsHistoryDataItem(aPlacesNode);
- historyDataItem.updateFromMetaData(metaData);
+ historyDataItem.download.updateFromMetaData(metaData);
}
let shell = new HistoryDownloadElementShell(aDataItem, historyDataItem);
shell.element._placesNode = aPlacesNode;
@@ -782,8 +806,9 @@ DownloadsPlacesView.prototype = {
_removeSessionDownloadFromView:
function DPV__removeSessionDownloadFromView(aDataItem) {
+ let download = aDataItem.download;
let shells = this._downloadElementsShellsForURI
- .get(aDataItem.download.source.url);
+ .get(download.source.url);
if (shells.size == 0)
throw new Error("Should have had at leaat one shell for this uri");
@@ -799,7 +824,7 @@ DownloadsPlacesView.prototype = {
this._removeElement(shell.element);
shells.delete(shell);
if (shells.size == 0)
- this._downloadElementsShellsForURI.delete(aDataItem.download.source.url);
+ this._downloadElementsShellsForURI.delete(download.source.url);
}
else {
// We have one download element shell containing both a session download
@@ -809,7 +834,7 @@ DownloadsPlacesView.prototype = {
// read the latest metadata before removing the session download.
let url = shell.historyDataItem.download.source.url;
let metaData = this._getPlacesMetaDataFor(url);
- shell.historyDataItem.updateFromMetaData(metaData);
+ shell.historyDataItem.download.updateFromMetaData(metaData);
shell.sessionDataItem = null;
// Move it below the session-download items;
if (this._lastSessionDownloadElement == shell.element) {
@@ -1142,7 +1167,9 @@ DownloadsPlacesView.prototype = {
// Because history downloads are always removable and are listed after the
// session downloads, check from bottom to top.
for (let elt = this._richlistbox.lastChild; elt; elt = elt.previousSibling) {
- if (!elt._shell.dataItem.inProgress) {
+ // Stopped, paused, and failed downloads with partial data are removed.
+ let download = elt._shell.download;
+ if (download.stopped && !(download.canceled && download.hasPartialData)) {
return true;
}
}
@@ -1244,12 +1271,13 @@ DownloadsPlacesView.prototype = {
// Set the state attribute so that only the appropriate items are displayed.
let contextMenu = document.getElementById("downloadsContextMenu");
- let state = element._shell.dataItem.state;
- contextMenu.setAttribute("state", state);
+ let download = element._shell.download;
+ contextMenu.setAttribute("state",
+ DownloadsCommon.stateOfDownload(download));
- if (state == nsIDM.DOWNLOAD_DOWNLOADING) {
- // The resumable property of a download may change at any time, so
- // ensure we update the related command now.
+ if (!download.stopped) {
+ // The hasPartialData property of a download may change at any time after
+ // it has started, so ensure we update the related command now.
goUpdateCommand("downloadsCmd_pauseResume");
}
return true;
diff --git a/application/palemoon/components/downloads/content/downloads.js b/application/palemoon/components/downloads/content/downloads.js
index edc9fc2ec..05233e76d 100644
--- a/application/palemoon/components/downloads/content/downloads.js
+++ b/application/palemoon/components/downloads/content/downloads.js
@@ -1025,9 +1025,10 @@ const DownloadsView = {
return;
}
- let localFile = DownloadsView.controllerForElement(element)
- .dataItem.localFile;
- if (!localFile.exists()) {
+ // We must check for existence synchronously because this is a DOM event.
+ let file = new FileUtils.File(DownloadsView.controllerForElement(element)
+ .download.target.path);
+ if (!file.exists()) {
return;
}
@@ -1085,24 +1086,17 @@ DownloadsViewItem.prototype = {
_element: null,
onStateChanged() {
- // If a download just finished successfully, it means that the target file
- // now exists and we can extract its specific icon. To ensure that the icon
- // is reloaded, we must change the URI used by the XUL image element, for
- // example by adding a query parameter. Since this URI has a "moz-icon"
- // scheme, this only works if we add one of the parameters explicitly
- // supported by the nsIMozIconURI interface.
- if (this.dataItem.state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) {
- this.element.setAttribute("image", this.image + "&state=normal");
+ this.element.setAttribute("image", this.image);
+ this.element.setAttribute("state",
+ DownloadsCommon.stateOfDownload(this.download));
+ if (this.download.succeeded) {
// We assume the existence of the target of a download that just completed
// successfully, without checking the condition in the background. If the
// panel is already open, this will take effect immediately. If the panel
// is opened later, a new background existence check will be performed.
this.element.setAttribute("exists", "true");
}
-
- // Update the user interface after switching states.
- this.element.setAttribute("state", this.dataItem.state);
},
onChanged() {
@@ -1252,24 +1246,38 @@ DownloadsViewItemController.prototype = {
*/
dataItem: null,
+ get download() this.dataItem.download,
+
isCommandEnabled: function DVIC_isCommandEnabled(aCommand)
{
switch (aCommand) {
case "downloadsCmd_open": {
- return this.dataItem.download.succeeded &&
- this.dataItem.localFile.exists();
+ if (!this.download.succeeded) {
+ return false;
+ }
+
+ let file = new FileUtils.File(this.download.target.path);
+ return file.exists();
}
case "downloadsCmd_show": {
- return this.dataItem.localFile.exists() ||
- this.dataItem.partFile.exists();
+ let file = new FileUtils.File(this.download.target.path);
+ if (file.exists()) {
+ return true;
+ }
+
+ if (!this.download.target.partFilePath) {
+ return false;
+ }
+
+ let partFile = new FileUtils.File(this.download.target.partFilePath);
+ return partFile.exists();
}
case "downloadsCmd_pauseResume":
- return this.dataItem.inProgress &&
- this.dataItem.download.hasPartialData;
+ return this.download.hasPartialData && !this.download.error;
case "downloadsCmd_retry":
- return this.dataItem.canRetry;
+ return this.download.canceled || this.download.error;
case "downloadsCmd_openReferrer":
- return !!this.dataItem.download.source.referrer;
+ return !!this.download.source.referrer;
case "cmd_delete":
case "downloadsCmd_cancel":
case "downloadsCmd_copyLocation":
@@ -1298,22 +1306,23 @@ DownloadsViewItemController.prototype = {
cmd_delete: function DVIC_cmd_delete()
{
Downloads.getList(Downloads.ALL)
- .then(list => list.remove(this.dataItem.download))
- .then(() => this.dataItem.download.finalize(true))
+ .then(list => list.remove(this.download))
+ .then(() => this.download.finalize(true))
.catch(Cu.reportError);
PlacesUtils.bhistory.removePage(
- NetUtil.newURI(this.dataItem.download.source.url));
+ NetUtil.newURI(this.download.source.url));
},
downloadsCmd_cancel: function DVIC_downloadsCmd_cancel()
{
- this.dataItem.download.cancel().catch(() => {});
- this.dataItem.download.removePartialData().catch(Cu.reportError);
+ this.download.cancel().catch(() => {});
+ this.download.removePartialData().catch(Cu.reportError);
},
downloadsCmd_open: function DVIC_downloadsCmd_open()
{
- this.dataItem.download.launch().catch(Cu.reportError);
+ this.download.launch().catch(Cu.reportError);
+
// We explicitly close the panel here to give the user the feedback that
// their click has been received, and we're handling the action.
// Otherwise, we'd have to wait for the file-type handler to execute
@@ -1324,7 +1333,8 @@ DownloadsViewItemController.prototype = {
downloadsCmd_show: function DVIC_downloadsCmd_show()
{
- DownloadsCommon.showDownloadedFile(this.dataItem.localFile);
+ let file = new FileUtils.File(this.download.target.path);
+ DownloadsCommon.showDownloadedFile(file);
// We explicitly close the panel here to give the user the feedback that
// their click has been received, and we're handling the action.
@@ -1336,28 +1346,28 @@ DownloadsViewItemController.prototype = {
downloadsCmd_pauseResume: function DVIC_downloadsCmd_pauseResume()
{
- if (this.dataItem.download.stopped) {
- this.dataItem.download.start();
+ if (this.download.stopped) {
+ this.download.start();
} else {
- this.dataItem.download.cancel();
+ this.download.cancel();
}
},
downloadsCmd_retry: function DVIC_downloadsCmd_retry()
{
- this.dataItem.download.start().catch(() => {});
+ this.download.start().catch(() => {});
},
downloadsCmd_openReferrer: function DVIC_downloadsCmd_openReferrer()
{
- openURL(this.dataItem.download.source.referrer);
+ openURL(this.download.source.referrer);
},
downloadsCmd_copyLocation: function DVIC_downloadsCmd_copyLocation()
{
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]
.getService(Ci.nsIClipboardHelper);
- clipboard.copyString(this.dataItem.download.source.url, document);
+ clipboard.copyString(this.download.source.url, document);
},
downloadsCmd_doDefault: function DVIC_downloadsCmd_doDefault()
@@ -1366,7 +1376,7 @@ DownloadsViewItemController.prototype = {
// Determine the default command for the current item.
let defaultCommand = function () {
- switch (this.dataItem.state) {
+ switch (DownloadsCommon.stateOfDownload(this.download)) {
case nsIDM.DOWNLOAD_NOTSTARTED: return "downloadsCmd_cancel";
case nsIDM.DOWNLOAD_FINISHED: return "downloadsCmd_open";
case nsIDM.DOWNLOAD_FAILED: return "downloadsCmd_retry";
diff --git a/application/palemoon/components/downloads/content/downloadsViewCommon.js b/application/palemoon/components/downloads/content/downloadsViewCommon.js
index 7b763ed07..7d18eadc6 100644
--- a/application/palemoon/components/downloads/content/downloadsViewCommon.js
+++ b/application/palemoon/components/downloads/content/downloadsViewCommon.js
@@ -72,12 +72,19 @@ DownloadElementShell.prototype = {
* URI string for the file type icon displayed in the download element.
*/
get image() {
- if (this.download.target.path) {
- return "moz-icon://" + this.download.target.path + "?size=32";
+ if (!this.download.target.path) {
+ // Old history downloads may not have a target path.
+ return "moz-icon://.unknown?size=32";
}
- // Old history downloads may not have a target path.
- return "moz-icon://.unknown?size=32";
+ // When a download that was previously in progress finishes successfully, it
+ // means that the target file now exists and we can extract its specific
+ // icon, for example from a Windows executable. To ensure that the icon is
+ // reloaded, however, we must change the URI used by the XUL image element,
+ // for example by adding a query parameter. This only works if we add one of
+ // the parameters explicitly supported by the nsIMozIconURI interface.
+ return "moz-icon://" + this.download.target.path + "?size=32" +
+ (this.download.succeeded ? "&state=normal" : "");
},
/**
@@ -127,11 +134,12 @@ DownloadElementShell.prototype = {
* update in order to improve performance.
*/
_updateState() {
- this.element.setAttribute("state", this.dataItem.state);
this.element.setAttribute("displayName", this.displayName);
this.element.setAttribute("extendedDisplayName", this.extendedDisplayName);
this.element.setAttribute("extendedDisplayNameTip", this.extendedDisplayNameTip);
this.element.setAttribute("image", this.image);
+ this.element.setAttribute("state",
+ DownloadsCommon.stateOfDownload(this.download));
// Since state changed, reset the time left estimation.
this.lastEstimatedSecondsLeft = Infinity;
@@ -144,19 +152,12 @@ DownloadElementShell.prototype = {
* namely the progress bar and the status line.
*/
_updateProgress() {
- if (this.dataItem.starting) {
- // Before the download starts, the progress meter has its initial value.
+ // The progress bar is only displayed for in-progress downloads.
+ if (this.download.hasProgress) {
this.element.setAttribute("progressmode", "normal");
- this.element.setAttribute("progress", "0");
- } else if (this.dataItem.state == Ci.nsIDownloadManager.DOWNLOAD_SCANNING ||
- this.dataItem.percentComplete == -1) {
- // We might not know the progress of a running download, and we don't know
- // the remaining time during the malware scanning phase.
- this.element.setAttribute("progressmode", "undetermined");
+ this.element.setAttribute("progress", this.download.progress);
} else {
- // This is a running download of which we know the progress.
- this.element.setAttribute("progressmode", "normal");
- this.element.setAttribute("progress", this.dataItem.percentComplete);
+ this.element.setAttribute("progressmode", "undetermined");
}
// Dispatch the ValueChange event for accessibility, if possible.
@@ -190,71 +191,60 @@ DownloadElementShell.prototype = {
let text = "";
let tip = "";
- if (this.dataItem.paused) {
- let transfer = DownloadUtils.getTransferTotal(this.download.currentBytes,
- this.dataItem.maxBytes);
-
- // We use the same XUL label to display both the state and the amount
- // transferred, for example "Paused - 1.1 MB".
- text = s.statusSeparatorBeforeNumber(s.statePaused, transfer);
- } else if (this.dataItem.state == nsIDM.DOWNLOAD_DOWNLOADING) {
+ if (!this.download.stopped) {
+ let maxBytes = DownloadsCommon.maxBytesOfDownload(this.download);
// By default, extended status information including the individual
// download rate is displayed in the tooltip. The history view overrides
// the getter and displays the detials in the main area instead.
[text] = DownloadUtils.getDownloadStatusNoRate(
this.download.currentBytes,
- this.dataItem.maxBytes,
+ maxBytes,
this.download.speed,
this.lastEstimatedSecondsLeft);
let newEstimatedSecondsLeft;
[tip, newEstimatedSecondsLeft] = DownloadUtils.getDownloadStatus(
this.download.currentBytes,
- this.dataItem.maxBytes,
+ maxBytes,
this.download.speed,
this.lastEstimatedSecondsLeft);
this.lastEstimatedSecondsLeft = newEstimatedSecondsLeft;
- } else if (this.dataItem.starting) {
+ } else if (this.download.canceled && this.download.hasPartialData) {
+ let maxBytes = DownloadsCommon.maxBytesOfDownload(this.download);
+ let transfer = DownloadUtils.getTransferTotal(this.download.currentBytes,
+ maxBytes);
+
+ // We use the same XUL label to display both the state and the amount
+ // transferred, for example "Paused - 1.1 MB".
+ text = s.statusSeparatorBeforeNumber(s.statePaused, transfer);
+ } else if (!this.download.succeeded && !this.download.canceled &&
+ !this.download.error) {
text = s.stateStarting;
- } else if (this.dataItem.state == nsIDM.DOWNLOAD_SCANNING) {
- text = s.stateScanning;
} else {
let stateLabel;
- switch (this.dataItem.state) {
- case nsIDM.DOWNLOAD_FAILED:
- stateLabel = s.stateFailed;
- break;
- case nsIDM.DOWNLOAD_CANCELED:
- stateLabel = s.stateCanceled;
- break;
- case nsIDM.DOWNLOAD_BLOCKED_PARENTAL:
- stateLabel = s.stateBlockedParentalControls;
- break;
- case nsIDM.DOWNLOAD_BLOCKED_POLICY:
- stateLabel = s.stateBlockedPolicy;
- break;
- case nsIDM.DOWNLOAD_DIRTY:
- stateLabel = s.stateDirty;
- break;
- case nsIDM.DOWNLOAD_FINISHED:
- // For completed downloads, show the file size (e.g. "1.5 MB")
- if (this.dataItem.maxBytes !== undefined &&
- this.dataItem.maxBytes >= 0) {
- let [size, unit] =
- DownloadUtils.convertByteUnits(this.dataItem.maxBytes);
- stateLabel = s.sizeWithUnits(size, unit);
- break;
- }
- // Fallback to default unknown state.
- default:
+
+ if (this.download.succeeded) {
+ // For completed downloads, show the file size (e.g. "1.5 MB")
+ let maxBytes = DownloadsCommon.maxBytesOfDownload(this.download);
+ if (maxBytes >= 0) {
+ let [size, unit] = DownloadUtils.convertByteUnits(maxBytes);
+ stateLabel = s.sizeWithUnits(size, unit);
+ } else {
stateLabel = s.sizeUnknown;
- break;
+ }
+ } else if (this.download.canceled) {
+ stateLabel = s.stateCanceled;
+ } else if (this.download.error.becauseBlockedByParentalControls) {
+ stateLabel = s.stateBlockedParentalControls;
+ } else if (this.download.error.becauseBlockedByReputationCheck) {
+ stateLabel = s.stateDirty;
+ } else {
+ stateLabel = s.stateFailed;
}
- let referrer = this.download.source.referrer ||
- this.download.source.url;
+ let referrer = this.download.source.referrer || this.download.source.url;
let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer);
- let date = new Date(this.dataItem.endTime);
+ let date = new Date(this.download.endTime);
let [displayDate, fullDate] = DownloadUtils.getReadableDates(date);
let firstPart = s.statusSeparator(stateLabel, displayHost);