diff options
Diffstat (limited to 'application')
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); |