From a970e88a1753101d2d55139bd7ae44d005c33f44 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Jul 2018 07:23:54 +0200 Subject: [PALEMOON] Bug 1115369 - Use notifications instead of getViewItem for DownloadsView --- .../downloads/content/allDownloadsViewOverlay.js | 27 +++++++++++++--------- 1 file changed, 16 insertions(+), 11 deletions(-) (limited to 'application/palemoon/components/downloads/content/allDownloadsViewOverlay.js') diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js index 054f0405f..ba1aa6092 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js @@ -55,9 +55,8 @@ const NOT_AVAILABLE = Number.MAX_VALUE; * * The caller is also responsible for "passing over" notification from both the * download-view and the places-result-observer, in the following manner: - * - The DownloadsPlacesView object implements getViewItem of the download-view - * pseudo interface. It returns this object (therefore we implement - * onStateChangea and onProgressChange here). + * - The DownloadsPlacesView object implements onDataItemStateChanged and + * onDataItemChanged of the DownloadsView pseudo interface. * - The DownloadsPlacesView object adds itself as a places result observer and * calls this object's placesNodeIconChanged, placesNodeTitleChanged and * placeNodeAnnotationChanged from its callbacks. @@ -557,8 +556,7 @@ DownloadElementShell.prototype = { } }, - /* DownloadView */ - onStateChange: function DES_onStateChange(aOldState) { + onStateChanged(aOldState) { let metaData = this.getDownloadMetaData(); metaData.state = this.dataItem.state; if (aOldState != nsIDM.DOWNLOAD_FINISHED && aOldState != metaData.state) { @@ -572,14 +570,14 @@ DownloadElementShell.prototype = { } this._updateDownloadStatusUI(); + if (this._element.selected) goUpdateDownloadCommands(); else goUpdateCommand("downloadsCmd_clearDownloads"); }, - /* DownloadView */ - onProgressChange: function DES_onProgressChange() { + onChanged() { this._updateDownloadStatusUI(); }, @@ -915,7 +913,7 @@ DownloadsPlacesView.prototype = { // data item. Thus, we also check that we make sure we don't have a view item // already. if (!shouldCreateShell && - aDataItem && this.getViewItem(aDataItem) == null) { + aDataItem && !this._viewItemsForDataItems.has(aDataItem)) { // If there's a past-download-only shell for this download-uri with no // associated data item, use it for the new data item. Otherwise, go ahead // and create another shell. @@ -1034,7 +1032,7 @@ DownloadsPlacesView.prototype = { if (shells.size == 0) throw new Error("Should have had at leaat one shell for this uri"); - let shell = this.getViewItem(aDataItem); + let shell = this._viewItemsForDataItems.get(aDataItem); if (!shells.has(shell)) throw new Error("Missing download element shell in shells list for url"); @@ -1342,8 +1340,15 @@ DownloadsPlacesView.prototype = { this._removeSessionDownloadFromView(aDataItem); }, - getViewItem: function(aDataItem) - this._viewItemsForDataItems.get(aDataItem, null), + // DownloadsView + onDataItemStateChanged(aDataItem, aOldState) { + this._viewItemsForDataItems.get(aDataItem).onStateChanged(aOldState); + }, + + // DownloadsView + onDataItemChanged(aDataItem) { + this._viewItemsForDataItems.get(aDataItem).onChanged(); + }, supportsCommand: function DPV_supportsCommand(aCommand) { if (DOWNLOAD_VIEW_SUPPORTED_COMMANDS.indexOf(aCommand) != -1) { -- cgit v1.2.3 From 1b414c2b93be093ea8c0a8366ca0ef7fc8f0509e Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Jul 2018 07:31:34 +0200 Subject: [PALEMOON] Bug 1115971 - Don't fall back to the Places title for downloads without the target file name annotation --- .../downloads/content/allDownloadsViewOverlay.js | 27 ++++++---------------- 1 file changed, 7 insertions(+), 20 deletions(-) (limited to 'application/palemoon/components/downloads/content/allDownloadsViewOverlay.js') diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js index ba1aa6092..0c28d218a 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js @@ -58,8 +58,8 @@ const NOT_AVAILABLE = Number.MAX_VALUE; * - The DownloadsPlacesView object implements onDataItemStateChanged and * onDataItemChanged of the DownloadsView pseudo interface. * - The DownloadsPlacesView object adds itself as a places result observer and - * calls this object's placesNodeIconChanged, placesNodeTitleChanged and - * placeNodeAnnotationChanged from its callbacks. + * calls this object's placesNodeIconChanged and placesNodeAnnotationChanged + * from its callbacks. * * @param [optional] aDataItem * The data item of a the session download. Required if aPlacesNode is not set @@ -287,9 +287,9 @@ DownloadElementShell.prototype = { * - fileName: the downloaded file name on the file system. Set if filePath * is set. * - displayName: the user-facing label for the download. This is always - * set. If available, it's set to the downloaded file name. If not, - * the places title for the download uri is used. As a last resort, - * we fallback to the download uri. + * set. If available, it's set to the downloaded file name. If not, this + * means the download does not have Places metadata because it is very old, + * and in this rare case the download uri is used. * - fileSize (only set for downloads which completed successfully): * the downloaded file size. For downloads done after the landing of * bug 826991, this value is "static" - that is, it does not necessarily @@ -337,7 +337,7 @@ DownloadElementShell.prototype = { this._metaData.displayName = this._metaData.fileName; } catch(ex) { - this._metaData.displayName = this._placesNode.title || this.downloadURI; + this._metaData.displayName = this.downloadURI; } } } @@ -511,14 +511,6 @@ DownloadElementShell.prototype = { this._element.setAttribute("image", this._getIcon()); }, - placesNodeTitleChanged: function DES_placesNodeTitleChanged() { - // If there's a file path, we use the leaf name for the title. - if (!this._dataItem && this.active && !this.getDownloadMetaData().filePath) { - this._metaData = null; - this._updateDisplayNameAndIcon(); - } - }, - placesNodeAnnotationChanged: function DES_placesNodeAnnotationChanged(aAnnoName) { this._annotations.delete(aAnnoName); if (!this._dataItem && this.active) { @@ -1264,12 +1256,7 @@ DownloadsPlacesView.prototype = { }); }, - nodeTitleChanged: function DPV_nodeTitleChanged(aNode, aNewTitle) { - this._forEachDownloadElementShellForURI(aNode.uri, function(aDownloadElementShell) { - aDownloadElementShell.placesNodeTitleChanged(); - }); - }, - + nodeTitleChanged() {}, nodeKeywordChanged: function() {}, nodeDateAddedChanged: function() {}, nodeLastModifiedChanged: function() {}, -- cgit v1.2.3 From 8bd13f3a28c512f6b4523a1e3930dcfa0c2bbb55 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Jul 2018 07:33:26 +0200 Subject: [PALEMOON] Bug 1115972 - Don't fall back to the Places icon for downloads without the target file name annotation --- .../downloads/content/allDownloadsViewOverlay.js | 23 +++++----------------- 1 file changed, 5 insertions(+), 18 deletions(-) (limited to 'application/palemoon/components/downloads/content/allDownloadsViewOverlay.js') diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js index 0c28d218a..a0493c5f4 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js @@ -58,8 +58,7 @@ const NOT_AVAILABLE = Number.MAX_VALUE; * - The DownloadsPlacesView object implements onDataItemStateChanged and * onDataItemChanged of the DownloadsView pseudo interface. * - The DownloadsPlacesView object adds itself as a places result observer and - * calls this object's placesNodeIconChanged and placesNodeAnnotationChanged - * from its callbacks. + * calls this object's placesNodeAnnotationChanged from its callbacks. * * @param [optional] aDataItem * The data item of a the session download. Required if aPlacesNode is not set @@ -164,12 +163,10 @@ DownloadElementShell.prototype = { return "moz-icon://" + metaData.filePath + "?size=32"; if (this._placesNode) { - // Try to extract an extension from the uri. - let ext = this._downloadURIObj.QueryInterface(Ci.nsIURL).fileExtension; - if (ext) - return "moz-icon://." + ext + "?size=32"; - return this._placesNode.icon || "moz-icon://.unknown?size=32"; + return "moz-icon://.unknown?size=32"; } + + // Assert unreachable. if (this._dataItem) throw new Error("Session-download items should always have a target file uri"); @@ -506,11 +503,6 @@ DownloadElementShell.prototype = { this._fetchTargetFileInfo(true); }, - placesNodeIconChanged: function DES_placesNodeIconChanged() { - if (!this._dataItem) - this._element.setAttribute("image", this._getIcon()); - }, - placesNodeAnnotationChanged: function DES_placesNodeAnnotationChanged(aAnnoName) { this._annotations.delete(aAnnoName); if (!this._dataItem && this.active) { @@ -1244,18 +1236,13 @@ DownloadsPlacesView.prototype = { this._removeHistoryDownloadFromView(aPlacesNode); }, - nodeIconChanged: function DPV_nodeIconChanged(aNode) { - this._forEachDownloadElementShellForURI(aNode.uri, function(aDownloadElementShell) { - aDownloadElementShell.placesNodeIconChanged(); - }); - }, - nodeAnnotationChanged: function DPV_nodeAnnotationChanged(aNode, aAnnoName) { this._forEachDownloadElementShellForURI(aNode.uri, function(aDownloadElementShell) { aDownloadElementShell.placesNodeAnnotationChanged(aAnnoName); }); }, + nodeIconChanged() {}, nodeTitleChanged() {}, nodeKeywordChanged: function() {}, nodeDateAddedChanged: function() {}, -- cgit v1.2.3 From 1eab01b243d9a4efb45bb73a1b228cd39436fba2 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Jul 2018 07:40:06 +0200 Subject: [PALEMOON] Bug 1120429 - Remove unused code handling nodeAnnotationChanged --- .../downloads/content/allDownloadsViewOverlay.js | 82 ++++------------------ 1 file changed, 12 insertions(+), 70 deletions(-) (limited to 'application/palemoon/components/downloads/content/allDownloadsViewOverlay.js') diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js index a0493c5f4..97a9d242b 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js @@ -53,12 +53,10 @@ const NOT_AVAILABLE = Number.MAX_VALUE; * The shell doesn't take care of inserting the item, or removing it when it's no longer * valid. That's the caller (a DownloadsPlacesView object) responsibility. * - * The caller is also responsible for "passing over" notification from both the - * download-view and the places-result-observer, in the following manner: - * - The DownloadsPlacesView object implements onDataItemStateChanged and - * onDataItemChanged of the DownloadsView pseudo interface. - * - The DownloadsPlacesView object adds itself as a places result observer and - * calls this object's placesNodeAnnotationChanged from its callbacks. + * The caller is also responsible for "passing over" notifications. The + * DownloadsPlacesView object implements onDataItemStateChanged and + * onDataItemChanged of the DownloadsView pseudo interface, and registers as a + * Places result observer. * * @param [optional] aDataItem * The data item of a the session download. Required if aPlacesNode is not set @@ -475,16 +473,6 @@ DownloadElementShell.prototype = { } }, - _updateDisplayNameAndIcon: function DES__updateDisplayNameAndIcon() { - let metaData = this.getDownloadMetaData(); - this._element.setAttribute("displayName", metaData.displayName); - if ("extendedDisplayName" in metaData) - this._element.setAttribute("extendedDisplayName", metaData.extendedDisplayName); - if ("extendedDisplayNameTip" in metaData) - this._element.setAttribute("extendedDisplayNameTip", metaData.extendedDisplayNameTip); - this._element.setAttribute("image", this._getIcon()); - }, - _updateUI: function DES__updateUI() { if (!this.active) throw new Error("Trying to _updateUI on an inactive download shell"); @@ -492,7 +480,13 @@ DownloadElementShell.prototype = { this._metaData = null; this._targetFileInfoFetched = false; - this._updateDisplayNameAndIcon(); + let metaData = this.getDownloadMetaData(); + this._element.setAttribute("displayName", metaData.displayName); + if ("extendedDisplayName" in metaData) + this._element.setAttribute("extendedDisplayName", metaData.extendedDisplayName); + if ("extendedDisplayNameTip" in metaData) + this._element.setAttribute("extendedDisplayNameTip", metaData.extendedDisplayNameTip); + this._element.setAttribute("image", this._getIcon()); // For history downloads done in past releases, the downloads/metaData // annotation is not set, and therefore we cannot tell the download @@ -503,43 +497,6 @@ DownloadElementShell.prototype = { this._fetchTargetFileInfo(true); }, - placesNodeAnnotationChanged: function DES_placesNodeAnnotationChanged(aAnnoName) { - this._annotations.delete(aAnnoName); - if (!this._dataItem && this.active) { - if (aAnnoName == DOWNLOAD_META_DATA_ANNO) { - let metaData = this.getDownloadMetaData(); - let annotatedMetaData = this._getAnnotatedMetaData(); - metaData.endTime = annotatedMetaData.endTime; - if ("fileSize" in annotatedMetaData) - metaData.fileSize = annotatedMetaData.fileSize; - else - delete metaData.fileSize; - - if (metaData.state != annotatedMetaData.state) { - metaData.state = annotatedMetaData.state; - if (this._element.selected) - goUpdateDownloadCommands(); - } - - this._updateDownloadStatusUI(); - } - else if (aAnnoName == DESTINATION_FILE_URI_ANNO) { - let metaData = this.getDownloadMetaData(); - let targetFileURI = this._getAnnotation(DESTINATION_FILE_URI_ANNO); - [metaData.filePath, metaData.fileName] = - this._extractFilePathAndNameFromFileURI(targetFileURI); - metaData.displayName = metaData.fileName; - this._updateDisplayNameAndIcon(); - - if (this._targetFileInfoFetched) { - // This will also update the download commands if necessary. - this._targetFileInfoFetched = false; - this._fetchTargetFileInfo(); - } - } - } - }, - onStateChanged(aOldState) { let metaData = this.getDownloadMetaData(); metaData.state = this.dataItem.state; @@ -799,16 +756,6 @@ DownloadsPlacesView.prototype = { return this._active; }, - _forEachDownloadElementShellForURI: - function DPV__forEachDownloadElementShellForURI(aURI, aCallback) { - if (this._downloadElementsShellsForURI.has(aURI)) { - let downloadElementShells = this._downloadElementsShellsForURI.get(aURI); - for (let des of downloadElementShells) { - aCallback(des); - } - } - }, - _getAnnotationsFor: function DPV_getAnnotationsFor(aURI) { if (!this._cachedAnnotations) { this._cachedAnnotations = new Map(); @@ -1236,12 +1183,7 @@ DownloadsPlacesView.prototype = { this._removeHistoryDownloadFromView(aPlacesNode); }, - nodeAnnotationChanged: function DPV_nodeAnnotationChanged(aNode, aAnnoName) { - this._forEachDownloadElementShellForURI(aNode.uri, function(aDownloadElementShell) { - aDownloadElementShell.placesNodeAnnotationChanged(aAnnoName); - }); - }, - + nodeAnnotationChanged() {}, nodeIconChanged() {}, nodeTitleChanged() {}, nodeKeywordChanged: function() {}, -- cgit v1.2.3 From 81265fae3383179a531a9d258b5eb1d8d475df26 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Jul 2018 07:49:10 +0200 Subject: [PALEMOON] Bug 1115421 - Simplify download annotations handling in the Library --- .../downloads/content/allDownloadsViewOverlay.js | 232 ++++++++++++--------- 1 file changed, 137 insertions(+), 95 deletions(-) (limited to 'application/palemoon/components/downloads/content/allDownloadsViewOverlay.js') diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js index 97a9d242b..580cf6c61 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js @@ -38,8 +38,6 @@ const DOWNLOAD_VIEW_SUPPORTED_COMMANDS = "downloadsCmd_open", "downloadsCmd_show", "downloadsCmd_retry", "downloadsCmd_openReferrer", "downloadsCmd_clearDownloads"]; -const NOT_AVAILABLE = Number.MAX_VALUE; - /** * A download element shell is responsible for handling the commands and the * displayed data for a single download view element. The download element @@ -62,22 +60,24 @@ const NOT_AVAILABLE = Number.MAX_VALUE; * The data item of a the session download. Required if aPlacesNode is not set * @param [optional] aPlacesNode * The places node for a past download. Required if aDataItem is not set. - * @param [optional] aAnnotations - * Map containing annotations values, to speed up the initial loading. + * @param [optional] aPlacesMetaData + * Object containing metadata from Places annotations values. + * This is required when a Places node is provided on construction. */ -function DownloadElementShell(aDataItem, aPlacesNode, aAnnotations) { +function DownloadElementShell(aDataItem, aPlacesNode, aPlacesMetaData) { this._element = document.createElement("richlistitem"); this._element._shell = this; this._element.classList.add("download"); this._element.classList.add("download-state"); - if (aAnnotations) - this._annotations = aAnnotations; - if (aDataItem) + if (aDataItem) { this.dataItem = aDataItem; - if (aPlacesNode) + } + if (aPlacesNode) { + this.placesMetaData = aPlacesMetaData; this.placesNode = aPlacesNode; + } } DownloadElementShell.prototype = { @@ -124,12 +124,6 @@ DownloadElementShell.prototype = { if (!aValue && !this._dataItem) throw new Error("Should always have either a dataItem or a placesNode"); - // Preserve the annotations map if this is the first loading and we got - // cached values. - if (this._placesNode || !this._annotations) { - this._annotations = new Map(); - } - this._placesNode = aValue; // We don't need to update the UI if we had a data item, because @@ -171,36 +165,6 @@ DownloadElementShell.prototype = { throw new Error("Unexpected download element state"); }, - // Helper for getting a places annotation set for the download. - _getAnnotation: function DES__getAnnotation(aAnnotation, aDefaultValue) { - let value; - if (this._annotations.has(aAnnotation)) - value = this._annotations.get(aAnnotation); - - // If the value is cached, or we know it doesn't exist, avoid a database - // lookup. - if (value === undefined) { - try { - value = PlacesUtils.annotations.getPageAnnotation( - this._downloadURIObj, aAnnotation); - } - catch(ex) { - value = NOT_AVAILABLE; - } - } - - if (value === NOT_AVAILABLE) { - if (aDefaultValue === undefined) { - throw new Error("Could not get required annotation '" + aAnnotation + - "' for download with url '" + this.downloadURI + "'"); - } - value = aDefaultValue; - } - - this._annotations.set(aAnnotation, value); - return value; - }, - _fetchTargetFileInfo: function DES__fetchTargetFileInfo(aUpdateMetaDataAndStatusUI = false) { if (this._targetFileInfoFetched) throw new Error("_fetchTargetFileInfo should not be called if the information was already fetched"); @@ -257,17 +221,6 @@ DownloadElementShell.prototype = { ); }, - _getAnnotatedMetaData: function DES__getAnnotatedMetaData() - JSON.parse(this._getAnnotation(DOWNLOAD_META_DATA_ANNO)), - - _extractFilePathAndNameFromFileURI: - function DES__extractFilePathAndNameFromFileURI(aFileURI) { - let file = Cc["@mozilla.org/network/protocol;1?name=file"] - .getService(Ci.nsIFileProtocolHandler) - .getFileFromURLSpec(aFileURI); - return [file.path, file.leafName]; - }, - /** * Retrieve the meta data object for the download. The following fields * may be set. @@ -311,13 +264,16 @@ DownloadElementShell.prototype = { } else { try { - this._metaData = this._getAnnotatedMetaData(); + this._metaData = JSON.parse(this.placesMetaData.jsonDetails); } catch(ex) { this._metaData = { }; if (this._targetFileInfoFetched && this._targetFileExists) { - this._metaData.state = this._targetFileSize > 0 ? - nsIDM.DOWNLOAD_FINISHED : nsIDM.DOWNLOAD_FAILED; + // For very old downloads without metadata, we assume that a zero + // byte file is a placeholder, and allow the download to restart. + this._metaData.state = this._targetFileSize > 0 + ? nsIDM.DOWNLOAD_FINISHED + : nsIDM.DOWNLOAD_FAILED; this._metaData.fileSize = this._targetFileSize; } @@ -326,10 +282,13 @@ DownloadElementShell.prototype = { } try { - let targetFileURI = this._getAnnotation(DESTINATION_FILE_URI_ANNO); - [this._metaData.filePath, this._metaData.fileName] = - this._extractFilePathAndNameFromFileURI(targetFileURI); - this._metaData.displayName = this._metaData.fileName; + let targetFile = Cc["@mozilla.org/network/protocol;1?name=file"] + .getService(Ci.nsIFileProtocolHandler) + .getFileFromURLSpec(this.placesMetaData + .targetFileURISpec); + this._metaData.filePath = targetFile.path; + this._metaData.fileName = targetFile.leafName; + this._metaData.displayName = targetFile.leafName; } catch(ex) { this._metaData.displayName = this.downloadURI; @@ -756,34 +715,95 @@ DownloadsPlacesView.prototype = { return this._active; }, - _getAnnotationsFor: function DPV_getAnnotationsFor(aURI) { - if (!this._cachedAnnotations) { - this._cachedAnnotations = new Map(); - for (let name of [ DESTINATION_FILE_URI_ANNO, - DOWNLOAD_META_DATA_ANNO ]) { - let results = PlacesUtils.annotations.getAnnotationsWithName(name); - for (let result of results) { - let url = result.uri.spec; - if (!this._cachedAnnotations.has(url)) - this._cachedAnnotations.set(url, new Map()); - let m = this._cachedAnnotations.get(url); - m.set(result.annotationName, result.annotationValue); + /** + * Map containing a metadata object for each download source URI found in + * Places annotations, or null if this cache must be rebuilt. + */ + _cachedPlacesMetaData: null, + + /** + * This method exists in order to optimize the first load of the Downloads + * View, when Places annotations for history downloads must be read. In fact, + * annotations are stored in a single table, and reading all of them at once + * is much more efficient than an individual query. + * + * When this metod is first called, after the Places result container has been + * invalidated, it reads the annotations for all the history downloads. + * + * At this point, as metadata for the various elements is requested by the + * view, it is pulled from the cache and returned as an object. + * + * This works efficiently because the metadata is requested only for newly + * created element shells that contain a history download but don't have a + * matching session download, and at most one such element shell is created + * for each source URI. + * + * @param url + * URI string of the Places node for which metadata is requested. + * + * @return Object of the form { targetFileURISpec, jsonDetails }. + * Any of the properties may be missing from the object. + */ + _getCachedPlacesMetaDataFor(url) { + if (!this._cachedPlacesMetaData) { + this._cachedPlacesMetaData = new Map(); + + // Use the destination file annotation to build the initial Map of items. + for (let result of PlacesUtils.annotations.getAnnotationsWithName( + DESTINATION_FILE_URI_ANNO)) { + this._cachedPlacesMetaData.set(result.uri.spec, { + targetFileURISpec: result.annotationValue, + }); + } + + // Add the string with the JSON details from the metadata annotation. + for (let result of PlacesUtils.annotations.getAnnotationsWithName( + DOWNLOAD_META_DATA_ANNO)) { + let metadata = this._cachedPlacesMetaData.get(result.uri.spec); + + // The destination file annotation is expected to be present for all + // the downloads with the metadata annotation. If this is not the case, + // we simply ignore the item, as if it has no JSON details at all. + if (metadata) { + metadata.jsonDetails = result.annotationValue; } } } - let annotations = this._cachedAnnotations.get(aURI); - if (!annotations) { - // There are no annotations for this entry, that means it is quite old. - // Make up a fake annotations entry with default values. - annotations = new Map(); - annotations.set(DESTINATION_FILE_URI_ANNO, NOT_AVAILABLE); - } - // The meta-data annotation has been added recently, so it's likely missing. - if (!annotations.has(DOWNLOAD_META_DATA_ANNO)) { - annotations.set(DOWNLOAD_META_DATA_ANNO, NOT_AVAILABLE); - } - return annotations; + let metadata = this._cachedPlacesMetaData.get(url); + this._cachedPlacesMetaData.delete(url); + + // In the rare case where a history download is added again, but there is no + // corresponding session download, read the metadata from the database. This + // could potentially cause an inefficient database access for very old + // downloads with no metadata to begin with, but these should have been + // already deleted from history on a recent browser profile. + return metadata || this._getPlacesMetaDataFor(url); + }, + + /** + * Read the latest version of the Places metadata for the given URI. + * + * @param url + * URI string of the Places node for which metadata is requested. + * + * @return Object of the form { targetFileURISpec, jsonDetails }. + * Any of the properties may be missing from the object. + */ + _getPlacesMetaDataFor(url) { + let metadata = {}; + + try { + let uri = NetUtil.newURI(url); + metadata = { + targetFileURISpec: PlacesUtils.annotations.getPageAnnotation( + uri, DESTINATION_FILE_URI_ANNO), + }; + metadata.jsonDetails = PlacesUtils.annotations.getPageAnnotation( + uri, DOWNLOAD_META_DATA_ANNO); + } catch (ex) {} + + return metadata; }, /** @@ -861,20 +881,30 @@ DownloadsPlacesView.prototype = { } if (shouldCreateShell) { - // Bug 836271: The annotations for a url should be cached only when the - // places node is available, i.e. when we know we we'd be notified for - // annotation changes. - // Otherwise we may cache NOT_AVILABLE values first for a given session - // download, and later use these NOT_AVILABLE values when a history - // download for the same URL is added. - let cachedAnnotations = aPlacesNode ? this._getAnnotationsFor(downloadURI) : null; - let shell = new DownloadElementShell(aDataItem, aPlacesNode, cachedAnnotations); + // If we are adding a new history download here, it means there is no + // associated session download, thus we must read the Places metadata, + // because it will not be obscured by the session download. + let metaData = aPlacesNode + ? this._getCachedPlacesMetaDataFor(aPlacesNode.uri) + : null; + let shell = new DownloadElementShell(aDataItem, aPlacesNode, metaData); newOrUpdatedShell = shell; shellsForURI.add(shell); if (aDataItem) this._viewItemsForDataItems.set(aDataItem, shell); } else if (aPlacesNode) { + // We are updating information for a history download for which we have + // at least one download element shell already. There are two cases: + // 1) There are one or more download element shells for this source URI, + // each with an associated session download. We update the Places node + // because we may need it later, but we don't need to read the Places + // metadata until the last session download is removed. + // 2) Occasionally, we may receive a duplicate notification for a history + // download with no associated session download. We have exactly one + // download element shell in this case, but the metdata cannot have + // changed, just the reference to the Places node object is different. + // So, we update all the node references and keep the metadata intact. for (let shell of shellsForURI) { if (shell.placesNode != aPlacesNode) shell.placesNode = aPlacesNode; @@ -978,6 +1008,12 @@ DownloadsPlacesView.prototype = { this._downloadElementsShellsForURI.delete(aDataItem.uri); } else { + // We have one download element shell containing both a session download + // and a history download, and we are now removing the session download. + // Previously, we did not use the Places metadata because it was obscured + // by the session download. Since this is no longer the case, we have to + // read the latest metadata before removing the session download. + shell.placesMetaData = this._getPlacesMetaDataFor(shell.placesNode.uri); shell.dataItem = null; // Move it below the session-download items; if (this._lastSessionDownloadElement == shell.element) { @@ -1114,6 +1150,12 @@ DownloadsPlacesView.prototype = { if (!aContainer.containerOpen) throw new Error("Root container for the downloads query cannot be closed"); + // When the container is invalidated, it means we are about to re-read all + // the information about history downloads from the database, discarding the + // download element shells, thus we fully rebuild the Places annotation + // cache with the latest values. + this._cachedPlacesMetaData = null; + let suppressOnSelect = this._richlistbox.suppressOnSelect; this._richlistbox.suppressOnSelect = true; try { -- cgit v1.2.3 From e666c9a8e06c7de97fe0462a9219b19835980384 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Jul 2018 08:40:06 +0200 Subject: [PALEMOON] Bug 1115983 - Keep only minimal state information in the DataItem --- .../downloads/content/allDownloadsViewOverlay.js | 79 ++++++++++++---------- 1 file changed, 43 insertions(+), 36 deletions(-) (limited to 'application/palemoon/components/downloads/content/allDownloadsViewOverlay.js') diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js index 580cf6c61..4983c422d 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js @@ -137,7 +137,7 @@ DownloadElementShell.prototype = { // The download uri (as a string) get downloadURI() { if (this._dataItem) - return this._dataItem.uri; + return this._dataItem.download.source.url; if (this._placesNode) return this._placesNode.uri; throw new Error("Unexpected download element state"); @@ -246,21 +246,22 @@ DownloadElementShell.prototype = { getDownloadMetaData: function DES_getDownloadMetaData() { if (!this._metaData) { if (this._dataItem) { + let leafName = OS.Path.basename(this._dataItem.download.target.path); let s = DownloadsCommon.strings; - let referrer = this._dataItem.referrer || this._dataItem.uri; + let referrer = this.dataItem.download.source.referrer || + this.dataItem.download.source.url; let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer); this._metaData = { state: this._dataItem.state, endTime: this._dataItem.endTime, - fileName: this._dataItem.target, - displayName: this._dataItem.target, - extendedDisplayName: s.statusSeparator(this._dataItem.target, displayHost), - extendedDisplayNameTip: s.statusSeparator(this._dataItem.target, fullHost) + fileName: leafName, + displayName: leafName, + extendedDisplayName: s.statusSeparator(leafName, displayHost), + extendedDisplayNameTip: s.statusSeparator(leafName, fullHost) }; if (this._dataItem.done) this._metaData.fileSize = this._dataItem.maxBytes; - if (this._dataItem.localFile) - this._metaData.filePath = this._dataItem.localFile.path; + this._metaData.filePath = this._dataItem.download.target.path; } else { try { @@ -304,7 +305,7 @@ DownloadElementShell.prototype = { if (this._dataItem && this._dataItem.inProgress) { if (this._dataItem.paused) { let transfer = - DownloadUtils.getTransferTotal(this._dataItem.currBytes, + DownloadUtils.getTransferTotal(this._dataItem.download.currentBytes, this._dataItem.maxBytes); // We use the same XUL label to display both the state and the amount @@ -313,9 +314,9 @@ DownloadElementShell.prototype = { } if (this._dataItem.state == nsIDM.DOWNLOAD_DOWNLOADING) { let [status, newEstimatedSecondsLeft] = - DownloadUtils.getDownloadStatus(this.dataItem.currBytes, + DownloadUtils.getDownloadStatus(this.dataItem.download.currentBytes, this.dataItem.maxBytes, - this.dataItem.speed, + this.dataItem.download.speed, this._lastEstimatedSecondsLeft || Infinity); this._lastEstimatedSecondsLeft = newEstimatedSecondsLeft; return status; @@ -365,7 +366,7 @@ DownloadElementShell.prototype = { } // TODO (bug 829201): history downloads should get the referrer from Places. - let referrer = this._dataItem && this._dataItem.referrer || + let referrer = this._dataItem && this._dataItem.download.source.referrer || this.downloadURI; let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer); @@ -488,10 +489,10 @@ DownloadElementShell.prototype = { return false; switch (aCommand) { case "downloadsCmd_open": { - // We cannot open a session download file unless it's done ("openable"). - // If it's finished, we need to make sure the file was not removed, + // We cannot open a session download file unless it's succeeded. + // If it's succeeded, we need to make sure the file was not removed, // as we do for past downloads. - if (this._dataItem && !this._dataItem.openable) + if (this._dataItem && !this._dataItem.download.succeeded) { return false; if (this._targetFileInfoFetched) @@ -515,12 +516,13 @@ DownloadElementShell.prototype = { return this.getDownloadMetaData().state == nsIDM.DOWNLOAD_FINISHED; } case "downloadsCmd_pauseResume": - return this._dataItem && this._dataItem.inProgress && this._dataItem.resumable; + return this._dataItem && this._dataItem.inProgress && + this._dataItem.download.hasPartialData; case "downloadsCmd_retry": // An history download can always be retried. return !this._dataItem || this._dataItem.canRetry; case "downloadsCmd_openReferrer": - return this._dataItem && !!this._dataItem.referrer; + return this._dataItem && !!this._dataItem.download.source.referrer; case "cmd_delete": // The behavior in this case is somewhat unexpected, so we disallow that. if (this._placesNode && this._dataItem && this._dataItem.inProgress) @@ -546,36 +548,35 @@ DownloadElementShell.prototype = { doCommand: function DES_doCommand(aCommand) { switch (aCommand) { case "downloadsCmd_open": { - let file = this._dataItem ? - this.dataItem.localFile : - new FileUtils.File(this.getDownloadMetaData().filePath); + let file = new FileUtils.File(this._dataItem + ? this._dataItem.download.target.path + : this.getDownloadMetaData().filePath); DownloadsCommon.openDownloadedFile(file, null, window); break; } case "downloadsCmd_show": { - if (this._dataItem) { - this._dataItem.showLocalFile(); - } - else { - let file = new FileUtils.File(this.getDownloadMetaData().filePath); - DownloadsCommon.showDownloadedFile(file); - } + let file = new FileUtils.File(this._dataItem + ? this._dataItem.download.target.path + : this.getDownloadMetaData().filePath); + + DownloadsCommon.showDownloadedFile(file); break; } case "downloadsCmd_openReferrer": { - openURL(this._dataItem.referrer); + openURL(this._dataItem.download.source.referrer); break; } case "downloadsCmd_cancel": { - this._dataItem.cancel(); + this._dataItem.download.cancel().catch(() => {}); + this._dataItem.download.removePartialData().catch(Cu.reportError); break; } case "cmd_delete": { if (this._dataItem) Downloads.getList(Downloads.ALL) - .then(list => list.remove(this._dataItem._download)) - .then(() => this._dataItem._download.finalize(true)) + .then(list => list.remove(this._dataItem.download)) + .then(() => this._dataItem.download.finalize(true)) .catch(Cu.reportError); if (this._placesNode) PlacesUtils.bhistory.removePage(this._downloadURIObj); @@ -583,13 +584,17 @@ DownloadElementShell.prototype = { } case "downloadsCmd_retry": { if (this._dataItem) - this._dataItem.retry(); + this._dataItem.download.start().catch(() => {}); else this._retryAsHistoryDownload(); break; } case "downloadsCmd_pauseResume": { - this._dataItem.togglePauseResume(); + if (this._dataItem.download.stopped) { + this._dataItem.download.start(); + } else { + this._dataItem.download.cancel(); + } break; } } @@ -836,7 +841,8 @@ DownloadsPlacesView.prototype = { _addDownloadData: function DPV_addDownloadData(aDataItem, aPlacesNode, aNewest = false, aDocumentFragment = null) { - let downloadURI = aPlacesNode ? aPlacesNode.uri : aDataItem.uri; + let downloadURI = aPlacesNode ? aPlacesNode.uri + : aDataItem.download.source.url; let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI); if (!shellsForURI) { shellsForURI = new Set(); @@ -989,7 +995,8 @@ DownloadsPlacesView.prototype = { _removeSessionDownloadFromView: function DPV__removeSessionDownloadFromView(aDataItem) { - let shells = this._downloadElementsShellsForURI.get(aDataItem.uri); + let shells = this._downloadElementsShellsForURI + .get(aDataItem.download.source.url); if (shells.size == 0) throw new Error("Should have had at leaat one shell for this uri"); @@ -1005,7 +1012,7 @@ DownloadsPlacesView.prototype = { this._removeElement(shell.element); shells.delete(shell); if (shells.size == 0) - this._downloadElementsShellsForURI.delete(aDataItem.uri); + this._downloadElementsShellsForURI.delete(aDataItem.download.source.url); } else { // We have one download element shell containing both a session download -- cgit v1.2.3 From ac3159f02f3b60a8f20a22ff6a66c51d075e11a7 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Jul 2018 09:32:40 +0200 Subject: [PALEMOON] Bug 1116176 - Create DownloadsHistoryDataItem and HistoryDownload objects --- .../downloads/content/allDownloadsViewOverlay.js | 797 ++++++++++----------- 1 file changed, 385 insertions(+), 412 deletions(-) (limited to 'application/palemoon/components/downloads/content/allDownloadsViewOverlay.js') diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js index 4983c422d..d756edf23 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js @@ -18,6 +18,7 @@ Cu.import("resource://gre/modules/NetUtil.jsm"); Cu.import("resource://gre/modules/DownloadUtils.jsm"); Cu.import("resource:///modules/DownloadsCommon.jsm"); Cu.import("resource://gre/modules/PlacesUtils.jsm"); +Cu.import("resource://gre/modules/Promise.jsm"); Cu.import("resource://gre/modules/osfile.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", @@ -38,57 +39,146 @@ const DOWNLOAD_VIEW_SUPPORTED_COMMANDS = "downloadsCmd_open", "downloadsCmd_show", "downloadsCmd_retry", "downloadsCmd_openReferrer", "downloadsCmd_clearDownloads"]; +/** + * Represents a download from the browser history. It implements part of the + * interface of the Download object. + * + * @param url + * URI string for the download source. + */ +function HistoryDownload(url) { + // TODO (bug 829201): history downloads should get the referrer from Places. + this.source = { url }; + 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, + + /** + * 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; + } catch (ex) { + this.download.target.path = undefined; + } + + try { + let metaData = JSON.parse(aPlacesMetaData.jsonDetails); + this.state = metaData.state; + this.endTime = metaData.endTime; + this.download.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 + // the corresponding in-progress session download. But if the browser is + // terminated abruptly and additionally the file with information about + // in-progress downloads is lost, we may end up using this state. We use + // the failed state to allow the download to be restarted. + // + // 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 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 is not displayed for history downloads, that are never in progress. + this.percentComplete = 100; + }, +}; + /** * A download element shell is responsible for handling the commands and the - * displayed data for a single download view element. The download element - * could represent either a past download (for which we get data from places) or - * a "session" download (using a data-item object. See DownloadsCommon.jsm), or both. + * displayed data for a single download view element. * - * Once initialized with either a data item or a places node, the created richlistitem - * can be accessed through the |element| getter, and can then be inserted/removed from - * a richlistbox. + * The shell may contain a session download, a history download, or both. When + * both a history and a current download are present, the current download gets + * priority and its information is displayed. * - * The shell doesn't take care of inserting the item, or removing it when it's no longer - * valid. That's the caller (a DownloadsPlacesView object) responsibility. + * On construction, a new richlistitem is created, and can be accessed through + * the |element| getter. The shell doesn't insert the item in a richlistbox, the + * caller must do it and remove the element when it's no longer needed. * - * The caller is also responsible for "passing over" notifications. The - * DownloadsPlacesView object implements onDataItemStateChanged and - * onDataItemChanged of the DownloadsView pseudo interface, and registers as a - * Places result observer. + * The caller is also responsible for forwarding status notifications for + * session downloads, calling the onStateChanged and onChanged methods. * - * @param [optional] aDataItem - * The data item of a the session download. Required if aPlacesNode is not set - * @param [optional] aPlacesNode - * The places node for a past download. Required if aDataItem is not set. - * @param [optional] aPlacesMetaData - * Object containing metadata from Places annotations values. - * This is required when a Places node is provided on construction. + * @param [optional] aSessionDataItem + * The session download, required if aHistoryDataItem is not set. + * @param [optional] aHistoryDataItem + * The history download, required if aSessionDataItem is not set. */ -function DownloadElementShell(aDataItem, aPlacesNode, aPlacesMetaData) { +function DownloadElementShell(aSessionDataItem, aHistoryDataItem) { this._element = document.createElement("richlistitem"); this._element._shell = this; this._element.classList.add("download"); this._element.classList.add("download-state"); - if (aDataItem) { - this.dataItem = aDataItem; + if (aSessionDataItem) { + this.sessionDataItem = aSessionDataItem; } - if (aPlacesNode) { - this.placesMetaData = aPlacesMetaData; - this.placesNode = aPlacesNode; + if (aHistoryDataItem) { + this.historyDataItem = aHistoryDataItem; } } DownloadElementShell.prototype = { - // The richlistitem for the download + /** + * The richlistitem for the download. + */ get element() this._element, /** - * Manages the "active" state of the shell. By default all the shells - * without a dataItem are inactive, thus their UI is not updated. They must - * be activated when entering the visible area. Session downloads are - * always active since they always have a dataItem. + * Manages the "active" state of the shell. By default all the shells without + * a session download are inactive, thus their UI is not updated. They must + * be activated when entering the visible area. Session downloads are always + * active. */ ensureActive: function DES_ensureActive() { if (!this._active) { @@ -99,232 +189,185 @@ DownloadElementShell.prototype = { }, get active() !!this._active, - // The data item for the download - _dataItem: null, - get dataItem() this._dataItem, + /** + * Download or HistoryDownload object to use for displaying information and + * for executing commands in the user interface. + */ + get download() this.dataItem.download, + + /** + * DownloadsDataItem or DownloadsHistoryDataItem object to use for displaying + * information and for executing commands in the user interface. + */ + get dataItem() this._sessionDataItem || this._historyDataItem, + + _sessionDataItem: null, + get sessionDataItem() this._sessionDataItem, + set sessionDataItem(aValue) { + if (this._sessionDataItem != aValue) { + if (!aValue && !this._historyDataItem) { + throw new Error("Should always have either a dataItem or a historyDataItem"); + } - set dataItem(aValue) { - if (this._dataItem != aValue) { - if (!aValue && !this._placesNode) - throw new Error("Should always have either a dataItem or a placesNode"); + this._sessionDataItem = aValue; - this._dataItem = aValue; - if (!this.active) - this.ensureActive(); - else - this._updateUI(); + this.ensureActive(); + this._updateUI(); } return aValue; }, - _placesNode: null, - get placesNode() this._placesNode, - set placesNode(aValue) { - if (this._placesNode != aValue) { - if (!aValue && !this._dataItem) - throw new Error("Should always have either a dataItem or a placesNode"); + _historyDataItem: null, + get historyDataItem() this._historyDataItem, + set historyDataItem(aValue) { + if (this._historyDataItem != aValue) { + if (!aValue && !this._sessionDataItem) { + throw new Error("Should always have either a dataItem or a historyDataItem"); + } - this._placesNode = aValue; + this._historyDataItem = aValue; - // We don't need to update the UI if we had a data item, because + // We don't need to update the UI if we had a session data item, because // the places information isn't used in this case. - if (!this._dataItem && this.active) + if (!this._sessionDataItem) { this._updateUI(); + } } return aValue; }, - // The download uri (as a string) - get downloadURI() { - if (this._dataItem) - return this._dataItem.download.source.url; - if (this._placesNode) - return this._placesNode.uri; - throw new Error("Unexpected download element state"); - }, - - get _downloadURIObj() { - if (!("__downloadURIObj" in this)) - this.__downloadURIObj = NetUtil.newURI(this.downloadURI); - return this.__downloadURIObj; + // The progressmeter element for the download + get _progressElement() { + if (!("__progressElement" in this)) { + this.__progressElement = + document.getAnonymousElementByAttribute(this._element, "anonid", + "progressmeter"); + } + return this.__progressElement; }, - _getIcon: function DES__getIcon() { - let metaData = this.getDownloadMetaData(); - if ("filePath" in metaData) - return "moz-icon://" + metaData.filePath + "?size=32"; - - if (this._placesNode) { - return "moz-icon://.unknown?size=32"; + _updateUI() { + // There is nothing to do if the item has always been invisible. + if (!this.active) { + return; } - // Assert unreachable. - if (this._dataItem) - throw new Error("Session-download items should always have a target file uri"); + // Since the state changed, we may need to check the target file again. + this._targetFileChecked = false; + + this._element.setAttribute("displayName", this.displayName); + this._element.setAttribute("extendedDisplayName", this.extendedDisplayName); + this._element.setAttribute("extendedDisplayNameTip", this.extendedDisplayNameTip); + this._element.setAttribute("image", this.image); - throw new Error("Unexpected download element state"); + this._updateActiveStatusUI(); }, - _fetchTargetFileInfo: function DES__fetchTargetFileInfo(aUpdateMetaDataAndStatusUI = false) { - if (this._targetFileInfoFetched) - throw new Error("_fetchTargetFileInfo should not be called if the information was already fetched"); + // Updates the download state attribute (and by that hide/unhide the + // appropriate buttons and context menu items), the status text label, + // and the progress meter. + _updateActiveStatusUI() { if (!this.active) - throw new Error("Trying to _fetchTargetFileInfo on an inactive download shell"); - - let path = this.getDownloadMetaData().filePath; - - // In previous version, the target file annotations were not set, - // so we cannot tell where is the file. - if (path === undefined) { - this._targetFileInfoFetched = true; - this._targetFileExists = false; - if (aUpdateMetaDataAndStatusUI) { - this._metaData = null; - this._updateDownloadStatusUI(); - } - // Here we don't need to update the download commands, - // as the state is unknown as it was. + throw new Error("_updateActiveStatusUI called for an inactive item."); + + this._element.setAttribute("state", this.dataItem.state); + this._element.setAttribute("status", this.statusText); + + // We have update the progress meter only for session downloads. + if (!this._sessionDataItem) { return; } - OS.File.stat(path).then( - function onSuccess(fileInfo) { - this._targetFileInfoFetched = true; - this._targetFileExists = true; - this._targetFileSize = fileInfo.size; - if (aUpdateMetaDataAndStatusUI) { - this._metaData = null; - this._updateDownloadStatusUI(); - } - if (this._element.selected) - goUpdateDownloadCommands(); - }.bind(this), - - function onFailure(reason) { - if (reason instanceof OS.File.Error && reason.becauseNoSuchFile) { - this._targetFileInfoFetched = true; - this._targetFileExists = false; - } - else { - Cu.reportError("Could not fetch info for target file (reason: " + - reason + ")"); - } - - if (aUpdateMetaDataAndStatusUI) { - this._metaData = null; - this._updateDownloadStatusUI(); - } + // Copied from updateProgress in downloads.js. + if (this.dataItem.starting) { + // Before the download starts, the progress meter has its initial value. + this._element.setAttribute("progressmode", "normal"); + this._element.setAttribute("progress", "0"); + } else if (this.dataItem.state == nsIDM.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"); + } else { + // This is a running download of which we know the progress. + this._element.setAttribute("progressmode", "normal"); + this._element.setAttribute("progress", this.dataItem.percentComplete); + } - if (this._element.selected) - goUpdateDownloadCommands(); - }.bind(this) - ); + // Dispatch the ValueChange event for accessibility, if possible. + if (this._progressElement) { + let event = document.createEvent("Events"); + event.initEvent("ValueChange", true, true); + this._progressElement.dispatchEvent(event); + } }, /** - * Retrieve the meta data object for the download. The following fields - * may be set. - * - * - state - any download state defined in nsIDownloadManager. If this field - * is not set, the download state is unknown. - * - endTime: the end time of the download. - * - filePath: the downloaded file path on the file system, when it - * was downloaded. The file may not exist. This is set for session - * downloads that have a local file set, and for history downloads done - * after the landing of bug 591289. - * - fileName: the downloaded file name on the file system. Set if filePath - * is set. - * - displayName: the user-facing label for the download. This is always - * set. If available, it's set to the downloaded file name. If not, this - * means the download does not have Places metadata because it is very old, - * and in this rare case the download uri is used. - * - fileSize (only set for downloads which completed successfully): - * the downloaded file size. For downloads done after the landing of - * bug 826991, this value is "static" - that is, it does not necessarily - * mean that the file is in place and has this size. + * URI string for the file type icon displayed in the download element. */ - getDownloadMetaData: function DES_getDownloadMetaData() { - if (!this._metaData) { - if (this._dataItem) { - let leafName = OS.Path.basename(this._dataItem.download.target.path); - let s = DownloadsCommon.strings; - let referrer = this.dataItem.download.source.referrer || - this.dataItem.download.source.url; - let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer); - this._metaData = { - state: this._dataItem.state, - endTime: this._dataItem.endTime, - fileName: leafName, - displayName: leafName, - extendedDisplayName: s.statusSeparator(leafName, displayHost), - extendedDisplayNameTip: s.statusSeparator(leafName, fullHost) - }; - if (this._dataItem.done) - this._metaData.fileSize = this._dataItem.maxBytes; - this._metaData.filePath = this._dataItem.download.target.path; - } - else { - try { - this._metaData = JSON.parse(this.placesMetaData.jsonDetails); - } - catch(ex) { - this._metaData = { }; - if (this._targetFileInfoFetched && this._targetFileExists) { - // For very old downloads without metadata, we assume that a zero - // byte file is a placeholder, and allow the download to restart. - this._metaData.state = this._targetFileSize > 0 - ? nsIDM.DOWNLOAD_FINISHED - : nsIDM.DOWNLOAD_FAILED; - this._metaData.fileSize = this._targetFileSize; - } - - // This is actually the start-time, but it's the best we can get. - this._metaData.endTime = this._placesNode.time / 1000; - } - - try { - let targetFile = Cc["@mozilla.org/network/protocol;1?name=file"] - .getService(Ci.nsIFileProtocolHandler) - .getFileFromURLSpec(this.placesMetaData - .targetFileURISpec); - this._metaData.filePath = targetFile.path; - this._metaData.fileName = targetFile.leafName; - this._metaData.displayName = targetFile.leafName; - } - catch(ex) { - this._metaData.displayName = this.downloadURI; - } - } + get image() { + if (this.download.target.path) { + return "moz-icon://" + this.download.target.path + "?size=32"; } - return this._metaData; + + // Old history downloads may not have a target path. + return "moz-icon://.unknown?size=32"; }, // The status text for the download - _getStatusText: function DES__getStatusText() { + /** + * The user-facing label for the download. This is normally the leaf name of + * download target file. In case this is a very old history download for + * which the target file is unknown, the download source URI is displayed. + */ + get displayName() { + if (!this.download.target.path) { + return this.download.source.url; + } + return OS.Path.basename(this.download.target.path); + }, + + get extendedDisplayName() { + let s = DownloadsCommon.strings; + let referrer = this.dataItem.download.source.referrer || + this.dataItem.download.source.url; + let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer); + return s.statusSeparator(this.displayName, displayHost); + }, + + get extendedDisplayNameTip() { let s = DownloadsCommon.strings; - if (this._dataItem && this._dataItem.inProgress) { - if (this._dataItem.paused) { + let referrer = this.dataItem.download.source.referrer || + this.dataItem.download.source.url; + let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer); + return s.statusSeparator(this.displayName, fullHost); + }, + + get statusText() { + let s = DownloadsCommon.strings; + if (this.dataItem.inProgress) { + if (this.dataItem.paused) { let transfer = - DownloadUtils.getTransferTotal(this._dataItem.download.currentBytes, - this._dataItem.maxBytes); + 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". return s.statusSeparatorBeforeNumber(s.statePaused, transfer); } - if (this._dataItem.state == nsIDM.DOWNLOAD_DOWNLOADING) { + if (this.dataItem.state == nsIDM.DOWNLOAD_DOWNLOADING) { let [status, newEstimatedSecondsLeft] = - DownloadUtils.getDownloadStatus(this.dataItem.download.currentBytes, + DownloadUtils.getDownloadStatus(this.download.currentBytes, this.dataItem.maxBytes, - this.dataItem.download.speed, + this.download.speed, this._lastEstimatedSecondsLeft || Infinity); this._lastEstimatedSecondsLeft = newEstimatedSecondsLeft; return status; } - if (this._dataItem.starting) { + if (this.dataItem.starting) { return s.stateStarting; } - if (this._dataItem.state == nsIDM.DOWNLOAD_SCANNING) { + if (this.dataItem.state == nsIDM.DOWNLOAD_SCANNING) { return s.stateScanning; } @@ -333,8 +376,7 @@ DownloadElementShell.prototype = { // This is a not-in-progress or history download. let stateLabel = ""; - let state = this.getDownloadMetaData().state; - switch (state) { + switch (this.dataItem.state) { case nsIDM.DOWNLOAD_FAILED: stateLabel = s.stateFailed; break; @@ -350,27 +392,25 @@ DownloadElementShell.prototype = { case nsIDM.DOWNLOAD_DIRTY: stateLabel = s.stateDirty; break; - case nsIDM.DOWNLOAD_FINISHED:{ + case nsIDM.DOWNLOAD_FINISHED: // For completed downloads, show the file size (e.g. "1.5 MB") - let metaData = this.getDownloadMetaData(); - if ("fileSize" in metaData) { - let [size, unit] = DownloadUtils.convertByteUnits(metaData.fileSize); + if (this.dataItem.maxBytes !== undefined) { + let [size, unit] = + DownloadUtils.convertByteUnits(this.dataItem.maxBytes); stateLabel = s.sizeWithUnits(size, unit); break; } // Fallback to default unknown state. - } default: stateLabel = s.sizeUnknown; break; } - // TODO (bug 829201): history downloads should get the referrer from Places. - let referrer = this._dataItem && this._dataItem.download.source.referrer || - this.downloadURI; + let referrer = this.download.source.referrer || + this.download.source.url; let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer); - let date = new Date(this.getDownloadMetaData().endTime); + let date = new Date(this.dataItem.endTime); let [displayDate, fullDate] = DownloadUtils.getReadableDates(date); // We use the same XUL label to display the state, the host name, and the @@ -379,99 +419,17 @@ DownloadElementShell.prototype = { return s.statusSeparator(firstPart, displayDate); }, - // The progressmeter element for the download - get _progressElement() { - if (!("__progressElement" in this)) { - this.__progressElement = - document.getAnonymousElementByAttribute(this._element, "anonid", - "progressmeter"); - } - return this.__progressElement; - }, - - // Updates the download state attribute (and by that hide/unhide the - // appropriate buttons and context menu items), the status text label, - // and the progress meter. - _updateDownloadStatusUI: function DES__updateDownloadStatusUI() { - if (!this.active) - throw new Error("_updateDownloadStatusUI called for an inactive item."); - - let state = this.getDownloadMetaData().state; - if (state !== undefined) - this._element.setAttribute("state", state); - - this._element.setAttribute("status", this._getStatusText()); - - // For past-downloads, we're done. For session-downloads, we may also need - // to update the progress-meter. - if (!this._dataItem) - return; - - // Copied from updateProgress in downloads.js. - if (this._dataItem.starting) { - // Before the download starts, the progress meter has its initial value. - this._element.setAttribute("progressmode", "normal"); - this._element.setAttribute("progress", "0"); - } - else if (this._dataItem.state == nsIDM.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"); - } - else { - // This is a running download of which we know the progress. - this._element.setAttribute("progressmode", "normal"); - this._element.setAttribute("progress", this._dataItem.percentComplete); - } - - // Dispatch the ValueChange event for accessibility, if possible. - if (this._progressElement) { - let event = document.createEvent("Events"); - event.initEvent("ValueChange", true, true); - this._progressElement.dispatchEvent(event); - } - }, - - _updateUI: function DES__updateUI() { - if (!this.active) - throw new Error("Trying to _updateUI on an inactive download shell"); - - this._metaData = null; - this._targetFileInfoFetched = false; - - let metaData = this.getDownloadMetaData(); - this._element.setAttribute("displayName", metaData.displayName); - if ("extendedDisplayName" in metaData) - this._element.setAttribute("extendedDisplayName", metaData.extendedDisplayName); - if ("extendedDisplayNameTip" in metaData) - this._element.setAttribute("extendedDisplayNameTip", metaData.extendedDisplayNameTip); - this._element.setAttribute("image", this._getIcon()); - - // For history downloads done in past releases, the downloads/metaData - // annotation is not set, and therefore we cannot tell the download - // state without the target file information. - if (this._dataItem || this.getDownloadMetaData().state !== undefined) - this._updateDownloadStatusUI(); - else - this._fetchTargetFileInfo(true); - }, - - onStateChanged(aOldState) { - let metaData = this.getDownloadMetaData(); - metaData.state = this.dataItem.state; - if (aOldState != nsIDM.DOWNLOAD_FINISHED && aOldState != metaData.state) { - // See comment in DVI_onStateChange in downloads.js (the panel-view) - this._element.setAttribute("image", this._getIcon() + "&state=normal"); - metaData.fileSize = this._dataItem.maxBytes; - if (this._targetFileInfoFetched) { - this._targetFileInfoFetched = false; - this._fetchTargetFileInfo(); - } + 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"); } - this._updateDownloadStatusUI(); - if (this._element.selected) goUpdateDownloadCommands(); else @@ -479,7 +437,7 @@ DownloadElementShell.prototype = { }, onChanged() { - this._updateDownloadStatusUI(); + this._updateActiveStatusUI(); }, /* nsIController */ @@ -488,112 +446,97 @@ DownloadElementShell.prototype = { if (!this.active && aCommand != "cmd_delete") return false; switch (aCommand) { - case "downloadsCmd_open": { + case "downloadsCmd_open": // We cannot open a session download file unless it's succeeded. // If it's succeeded, we need to make sure the file was not removed, // as we do for past downloads. - if (this._dataItem && !this._dataItem.download.succeeded) { + if (this._sessionDataItem && !this.download.succeeded) { return false; + } - if (this._targetFileInfoFetched) + if (this._targetFileChecked) { return this._targetFileExists; + } // If the target file information is not yet fetched, // temporarily assume that the file is in place. - return this.getDownloadMetaData().state == nsIDM.DOWNLOAD_FINISHED; - } - case "downloadsCmd_show": { + return this.dataItem.state == nsIDM.DOWNLOAD_FINISHED; + case "downloadsCmd_show": // TODO: Bug 827010 - Handle part-file asynchronously. - if (this._dataItem && - this._dataItem.partFile && this._dataItem.partFile.exists()) + if (this._sessionDataItem && + this.dataItem.partFile && this.dataItem.partFile.exists()) { return true; + } - if (this._targetFileInfoFetched) + if (this._targetFileChecked) { return this._targetFileExists; + } // If the target file information is not yet fetched, // temporarily assume that the file is in place. - return this.getDownloadMetaData().state == nsIDM.DOWNLOAD_FINISHED; - } + return this.dataItem.state == nsIDM.DOWNLOAD_FINISHED; case "downloadsCmd_pauseResume": - return this._dataItem && this._dataItem.inProgress && - this._dataItem.download.hasPartialData; + return this._sessionDataItem && this.dataItem.inProgress && + this.dataItem.download.hasPartialData; case "downloadsCmd_retry": - // An history download can always be retried. - return !this._dataItem || this._dataItem.canRetry; + return this.dataItem.canRetry; case "downloadsCmd_openReferrer": - return this._dataItem && !!this._dataItem.download.source.referrer; + return !!this.download.source.referrer; case "cmd_delete": // The behavior in this case is somewhat unexpected, so we disallow that. - if (this._placesNode && this._dataItem && this._dataItem.inProgress) - return false; - return true; + return !this.dataItem.inProgress; case "downloadsCmd_cancel": - return this._dataItem != null; + return !!this._sessionDataItem; } return false; }, - _retryAsHistoryDownload: function DES__retryAsHistoryDownload() { - // 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; - DownloadURL(this.downloadURI, this.getDownloadMetaData().fileName, - initiatingDoc); - }, - /* nsIController */ doCommand: function DES_doCommand(aCommand) { switch (aCommand) { case "downloadsCmd_open": { - let file = new FileUtils.File(this._dataItem - ? this._dataItem.download.target.path - : this.getDownloadMetaData().filePath); - + let file = new FileUtils.File(this.download.target.path); DownloadsCommon.openDownloadedFile(file, null, window); break; } case "downloadsCmd_show": { - let file = new FileUtils.File(this._dataItem - ? this._dataItem.download.target.path - : this.getDownloadMetaData().filePath); - + let file = new FileUtils.File(this.download.target.path); DownloadsCommon.showDownloadedFile(file); break; } case "downloadsCmd_openReferrer": { - openURL(this._dataItem.download.source.referrer); + openURL(this.download.source.referrer); break; } case "downloadsCmd_cancel": { - this._dataItem.download.cancel().catch(() => {}); - this._dataItem.download.removePartialData().catch(Cu.reportError); + this.download.cancel().catch(() => {}); + this.download.removePartialData().catch(Cu.reportError); break; } case "cmd_delete": { - if (this._dataItem) + if (this._sessionDataItem) { 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); - if (this._placesNode) - PlacesUtils.bhistory.removePage(this._downloadURIObj); + } + if (this._historyDataItem) { + let uri = NetUtil.newURI(this.download.source.url); + PlacesUtils.bhistory.removePage(uri); + } break; - } + } case "downloadsCmd_retry": { - if (this._dataItem) - this._dataItem.download.start().catch(() => {}); - else - this._retryAsHistoryDownload(); + // Errors when retrying are already reported as download failures. + this.download.start().catch(() => {}); break; } case "downloadsCmd_pauseResume": { - if (this._dataItem.download.stopped) { - this._dataItem.download.start(); + // This command is only enabled for session downloads. + if (this.download.stopped) { + this.download.start(); } else { - this._dataItem.download.cancel(); + this.download.cancel(); } break; } @@ -607,8 +550,8 @@ DownloadElementShell.prototype = { if (!aTerm) return true; aTerm = aTerm.toLowerCase(); - return this.getDownloadMetaData().displayName.toLowerCase().includes(aTerm) || - this.downloadURI.toLowerCase().includes(aTerm); + return this.displayName.toLowerCase().contains(aTerm) || + this.download.source.url.toLowerCase().contains(aTerm); }, // Handles return keypress on the element (the keypress listener is @@ -635,25 +578,47 @@ DownloadElementShell.prototype = { } return ""; } - let command = getDefaultCommandForState(this.getDownloadMetaData().state); + let command = getDefaultCommandForState(this.dataItem.state); if (command && this.isCommandEnabled(command)) this.doCommand(command); }, /** - * At the first time an item is selected, we don't yet have - * the target file information. Thus the call to goUpdateDownloadCommands - * in DPV_onSelect would result in best-guess enabled/disabled result. - * That way we let the user perform command immediately. However, once - * we have the target file information, we can update the commands - * appropriately (_fetchTargetFileInfo() calls goUpdateDownloadCommands). + * This method is called by the outer download view, after the controller + * commands have already been updated. In case we did not check for the + * existence of the target file already, we can do it now and then update + * the commands as needed. */ onSelect: function DES_onSelect() { if (!this.active) return; - if (!this._targetFileInfoFetched) - this._fetchTargetFileInfo(); - } + + // If this is a history download for which no target file information is + // available, we cannot retrieve information about the target file. + if (!this.download.target.path) { + return; + } + + // Start checking for existence. This may be done twice if onSelect is + // called again before the information is collected. + if (!this._targetFileChecked) { + this._checkTargetFileOnSelect().catch(Cu.reportError); + } + }, + + _checkTargetFileOnSelect: Task.async(function* () { + try { + this._targetFileExists = yield OS.File.exists(this.download.target.path); + } finally { + // Do not try to check for existence again if this failed once. + this._targetFileChecked = true; + } + + // Update the commands only if the element is still selected. + if (this._element.selected) { + goUpdateDownloadCommands(); + } + }), }; /** @@ -876,9 +841,9 @@ DownloadsPlacesView.prototype = { // and create another shell. shouldCreateShell = true; for (let shell of shellsForURI) { - if (!shell.dataItem) { + if (!shell.sessionDataItem) { shouldCreateShell = false; - shell.dataItem = aDataItem; + shell.sessionDataItem = aDataItem; newOrUpdatedShell = shell; this._viewItemsForDataItems.set(aDataItem, shell); break; @@ -890,10 +855,14 @@ DownloadsPlacesView.prototype = { // If we are adding a new history download here, it means there is no // associated session download, thus we must read the Places metadata, // because it will not be obscured by the session download. - let metaData = aPlacesNode - ? this._getCachedPlacesMetaDataFor(aPlacesNode.uri) - : null; - let shell = new DownloadElementShell(aDataItem, aPlacesNode, metaData); + let historyDataItem = null; + if (aPlacesNode) { + let metaData = this._getCachedPlacesMetaDataFor(aPlacesNode.uri); + historyDataItem = new DownloadsHistoryDataItem(aPlacesNode); + historyDataItem.updateFromMetaData(metaData); + } + let shell = new DownloadElementShell(aDataItem, historyDataItem); + shell.element._placesNode = aPlacesNode; newOrUpdatedShell = shell; shellsForURI.add(shell); if (aDataItem) @@ -912,8 +881,11 @@ DownloadsPlacesView.prototype = { // changed, just the reference to the Places node object is different. // So, we update all the node references and keep the metadata intact. for (let shell of shellsForURI) { - if (shell.placesNode != aPlacesNode) - shell.placesNode = aPlacesNode; + if (!shell.historyDataItem) { + // Create the element to host the metadata when needed. + shell.historyDataItem = new DownloadsHistoryDataItem(aPlacesNode); + } + shell.element._placesNode = aPlacesNode; } } @@ -980,8 +952,8 @@ DownloadsPlacesView.prototype = { let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI); if (shellsForURI) { for (let shell of shellsForURI) { - if (shell.dataItem) { - shell.placesNode = null; + if (shell.sessionDataItem) { + shell.historyDataItem = null; } else { this._removeElement(shell.element); @@ -1008,7 +980,7 @@ DownloadsPlacesView.prototype = { // view item for this this particular data item go away. // If there's only one item for this download uri, we should only // keep it if it is associated with a history download. - if (shells.size > 1 || !shell.placesNode) { + if (shells.size > 1 || !shell.historyDataItem) { this._removeElement(shell.element); shells.delete(shell); if (shells.size == 0) @@ -1020,8 +992,10 @@ DownloadsPlacesView.prototype = { // Previously, we did not use the Places metadata because it was obscured // by the session download. Since this is no longer the case, we have to // read the latest metadata before removing the session download. - shell.placesMetaData = this._getPlacesMetaDataFor(shell.placesNode.uri); - shell.dataItem = null; + let url = shell.historyDataItem.download.source.url; + let metaData = this._getPlacesMetaDataFor(url); + shell.historyDataItem.updateFromMetaData(metaData); + shell.sessionDataItem = null; // Move it below the session-download items; if (this._lastSessionDownloadElement == shell.element) { this._lastSessionDownloadElement = shell.element.previousSibling; @@ -1129,13 +1103,9 @@ DownloadsPlacesView.prototype = { }, get selectedNodes() { - let placesNodes = []; - let selectedElements = this._richlistbox.selectedItems; - for (let elt of selectedElements) { - if (elt._shell.placesNode) - placesNodes.push(elt._shell.placesNode); - } - return placesNodes; + return [for (element of this._richlistbox.selectedItems) + if (element._placesNode) + element._placesNode]; }, get selectedNode() { @@ -1171,8 +1141,9 @@ DownloadsPlacesView.prototype = { // Loop backwards since _removeHistoryDownloadFromView may removeChild(). for (let i = this._richlistbox.childNodes.length - 1; i >= 0; --i) { let element = this._richlistbox.childNodes[i]; - if (element._shell.placesNode) - this._removeHistoryDownloadFromView(element._shell.placesNode); + if (element._placesNode) { + this._removeHistoryDownloadFromView(element._placesNode); + } } } finally { @@ -1306,8 +1277,8 @@ DownloadsPlacesView.prototype = { }, // DownloadsView - onDataItemStateChanged(aDataItem, aOldState) { - this._viewItemsForDataItems.get(aDataItem).onStateChanged(aOldState); + onDataItemStateChanged(aDataItem) { + this._viewItemsForDataItems.get(aDataItem).onStateChanged(); }, // DownloadsView @@ -1356,8 +1327,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.placesNode || !elt._shell.dataItem.inProgress) + if (!elt._shell.dataItem.inProgress) { return true; + } } return false; }, @@ -1365,10 +1337,11 @@ DownloadsPlacesView.prototype = { _copySelectedDownloadsToClipboard: function DPV__copySelectedDownloadsToClipboard() { let urls = [for (element of this._richlistbox.selectedItems) - element._shell.downloadURI]; + element._shell.download.source.url]; - Cc["@mozilla.org/widget/clipboardhelper;1"]. - getService(Ci.nsIClipboardHelper).copyString(urls.join("\n")); + Cc["@mozilla.org/widget/clipboardhelper;1"] + .getService(Ci.nsIClipboardHelper) + .copyString(urls.join("\n"), document); }, _getURLFromClipboardData: function DPV__getURLFromClipboardData() { @@ -1456,11 +1429,8 @@ DownloadsPlacesView.prototype = { // Set the state attribute so that only the appropriate items are displayed. let contextMenu = document.getElementById("downloadsContextMenu"); - let state = element._shell.getDownloadMetaData().state; - if (state !== undefined) - contextMenu.setAttribute("state", state); - else - contextMenu.removeAttribute("state"); + let state = element._shell.dataItem.state; + contextMenu.setAttribute("state", state); if (state == nsIDM.DOWNLOAD_DOWNLOADING) { // The resumable property of a download may change at any time, so @@ -1525,10 +1495,13 @@ DownloadsPlacesView.prototype = { if (!selectedItem) return; - let metaData = selectedItem._shell.getDownloadMetaData(); - if (!("filePath" in metaData)) + let targetPath = selectedItem._shell.download.target.path; + if (!targetPath) { return; - let file = new FileUtils.File(metaData.filePath); + } + + // We must check for existence synchronously because this is a DOM event. + let file = new FileUtils.File(targetPath); if (!file.exists()) return; -- cgit v1.2.3 From f2f7005141bbc366aee8fce0c88592d5c544d30d Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Jul 2018 10:27:51 +0200 Subject: [PALEMOON] Bug 1117139 - Move code controlling the "download.xml" binding to a common place --- .../downloads/content/allDownloadsViewOverlay.js | 239 +++------------------ 1 file changed, 27 insertions(+), 212 deletions(-) (limited to 'application/palemoon/components/downloads/content/allDownloadsViewOverlay.js') diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js index d756edf23..b85b64c1c 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js @@ -2,31 +2,10 @@ * 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/. */ -/** - * THE PLACES VIEW IMPLEMENTED IN THIS FILE HAS A VERY PARTICULAR USE CASE. - * IT IS HIGHLY RECOMMENDED NOT TO EXTEND IT FOR ANY OTHER USE CASES OR RELY - * ON IT AS AN API. - */ - -var Cu = Components.utils; -var Ci = Components.interfaces; -var Cc = Components.classes; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/NetUtil.jsm"); -Cu.import("resource://gre/modules/DownloadUtils.jsm"); -Cu.import("resource:///modules/DownloadsCommon.jsm"); -Cu.import("resource://gre/modules/PlacesUtils.jsm"); -Cu.import("resource://gre/modules/Promise.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", - "resource://gre/modules/PrivateBrowsingUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "DownloadsDataItem", + "resource:///modules/DownloadsCommon.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", "resource:///modules/RecentWindow.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); const nsIDM = Ci.nsIDownloadManager; @@ -153,12 +132,12 @@ DownloadsHistoryDataItem.prototype = { * @param [optional] aHistoryDataItem * The history download, required if aSessionDataItem is not set. */ -function DownloadElementShell(aSessionDataItem, aHistoryDataItem) { - this._element = document.createElement("richlistitem"); - this._element._shell = this; +function HistoryDownloadElementShell(aSessionDataItem, aHistoryDataItem) { + this.element = document.createElement("richlistitem"); + this.element._shell = this; - this._element.classList.add("download"); - this._element.classList.add("download-state"); + this.element.classList.add("download"); + this.element.classList.add("download-state"); if (aSessionDataItem) { this.sessionDataItem = aSessionDataItem; @@ -168,11 +147,8 @@ function DownloadElementShell(aSessionDataItem, aHistoryDataItem) { } } -DownloadElementShell.prototype = { - /** - * The richlistitem for the download. - */ - get element() this._element, +HistoryDownloadElementShell.prototype = { + __proto__: DownloadElementShell.prototype, /** * Manages the "active" state of the shell. By default all the shells without @@ -183,18 +159,12 @@ DownloadElementShell.prototype = { ensureActive: function DES_ensureActive() { if (!this._active) { this._active = true; - this._element.setAttribute("active", true); + this.element.setAttribute("active", true); this._updateUI(); } }, get active() !!this._active, - /** - * Download or HistoryDownload object to use for displaying information and - * for executing commands in the user interface. - */ - get download() this.dataItem.download, - /** * DownloadsDataItem or DownloadsHistoryDataItem object to use for displaying * information and for executing commands in the user interface. @@ -236,16 +206,6 @@ DownloadElementShell.prototype = { return aValue; }, - // The progressmeter element for the download - get _progressElement() { - if (!("__progressElement" in this)) { - this.__progressElement = - document.getAnonymousElementByAttribute(this._element, "anonid", - "progressmeter"); - } - return this.__progressElement; - }, - _updateUI() { // There is nothing to do if the item has always been invisible. if (!this.active) { @@ -255,168 +215,20 @@ DownloadElementShell.prototype = { // Since the state changed, we may need to check the target file again. this._targetFileChecked = false; - 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._updateActiveStatusUI(); - }, - - // Updates the download state attribute (and by that hide/unhide the - // appropriate buttons and context menu items), the status text label, - // and the progress meter. - _updateActiveStatusUI() { - if (!this.active) - throw new Error("_updateActiveStatusUI called for an inactive item."); - - this._element.setAttribute("state", this.dataItem.state); - this._element.setAttribute("status", this.statusText); - - // We have update the progress meter only for session downloads. - if (!this._sessionDataItem) { - return; - } - - // Copied from updateProgress in downloads.js. - if (this.dataItem.starting) { - // Before the download starts, the progress meter has its initial value. - this._element.setAttribute("progressmode", "normal"); - this._element.setAttribute("progress", "0"); - } else if (this.dataItem.state == nsIDM.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"); - } else { - // This is a running download of which we know the progress. - this._element.setAttribute("progressmode", "normal"); - this._element.setAttribute("progress", this.dataItem.percentComplete); - } - - // Dispatch the ValueChange event for accessibility, if possible. - if (this._progressElement) { - let event = document.createEvent("Events"); - event.initEvent("ValueChange", true, true); - this._progressElement.dispatchEvent(event); - } - }, - - /** - * 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"; - } - - // Old history downloads may not have a target path. - return "moz-icon://.unknown?size=32"; - }, - - // The status text for the download - /** - * The user-facing label for the download. This is normally the leaf name of - * download target file. In case this is a very old history download for - * which the target file is unknown, the download source URI is displayed. - */ - get displayName() { - if (!this.download.target.path) { - return this.download.source.url; - } - return OS.Path.basename(this.download.target.path); - }, - - get extendedDisplayName() { - let s = DownloadsCommon.strings; - let referrer = this.dataItem.download.source.referrer || - this.dataItem.download.source.url; - let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer); - return s.statusSeparator(this.displayName, displayHost); + this._updateState(); }, - - get extendedDisplayNameTip() { - let s = DownloadsCommon.strings; - let referrer = this.dataItem.download.source.referrer || - this.dataItem.download.source.url; - let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer); - return s.statusSeparator(this.displayName, fullHost); - }, - - get statusText() { - let s = DownloadsCommon.strings; - if (this.dataItem.inProgress) { - 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". - return s.statusSeparatorBeforeNumber(s.statePaused, transfer); - } - if (this.dataItem.state == nsIDM.DOWNLOAD_DOWNLOADING) { - let [status, newEstimatedSecondsLeft] = - DownloadUtils.getDownloadStatus(this.download.currentBytes, - this.dataItem.maxBytes, - this.download.speed, - this._lastEstimatedSecondsLeft || Infinity); - this._lastEstimatedSecondsLeft = newEstimatedSecondsLeft; - return status; - } - if (this.dataItem.starting) { - return s.stateStarting; - } - if (this.dataItem.state == nsIDM.DOWNLOAD_SCANNING) { - return s.stateScanning; - } - throw new Error("_getStatusText called with a bogus download state"); - } + get statusTextAndTip() { + let status = this.rawStatusTextAndTip; - // This is a not-in-progress or history download. - 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) { - let [size, unit] = - DownloadUtils.convertByteUnits(this.dataItem.maxBytes); - stateLabel = s.sizeWithUnits(size, unit); - break; - } - // Fallback to default unknown state. - default: - stateLabel = s.sizeUnknown; - break; + // 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) { + status.text = status.tip; } + status.tip = ""; - let referrer = this.download.source.referrer || - this.download.source.url; - let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer); - - let date = new Date(this.dataItem.endTime); - let [displayDate, fullDate] = DownloadUtils.getReadableDates(date); - - // We use the same XUL label to display the state, the host name, and the - // end time. - let firstPart = s.statusSeparator(stateLabel, displayHost); - return s.statusSeparator(firstPart, displayDate); + return status; }, onStateChanged() { @@ -427,17 +239,20 @@ DownloadElementShell.prototype = { // 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"); + this.element.setAttribute("image", this.image + "&state=normal"); } - if (this._element.selected) + // Update the user interface after switching states. + this.element.setAttribute("state", this.dataItem.state); + + if (this.element.selected) { goUpdateDownloadCommands(); else goUpdateCommand("downloadsCmd_clearDownloads"); }, onChanged() { - this._updateActiveStatusUI(); + this._updateProgress(); }, /* nsIController */ @@ -615,7 +430,7 @@ DownloadElementShell.prototype = { } // Update the commands only if the element is still selected. - if (this._element.selected) { + if (this.element.selected) { goUpdateDownloadCommands(); } }), @@ -861,7 +676,7 @@ DownloadsPlacesView.prototype = { historyDataItem = new DownloadsHistoryDataItem(aPlacesNode); historyDataItem.updateFromMetaData(metaData); } - let shell = new DownloadElementShell(aDataItem, historyDataItem); + let shell = new HistoryDownloadElementShell(aDataItem, historyDataItem); shell.element._placesNode = aPlacesNode; newOrUpdatedShell = shell; shellsForURI.add(shell); -- cgit v1.2.3 From 462332eee018e24d88255c708fa8acb67a717673 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Jul 2018 11:13:36 +0200 Subject: [PALEMOON] Bug 1117141 - Part 1 of 2 - Bypass all the DownloadsDataItem properties --- .../downloads/content/allDownloadsViewOverlay.js | 196 ++++++++++++--------- 1 file changed, 112 insertions(+), 84 deletions(-) (limited to 'application/palemoon/components/downloads/content/allDownloadsViewOverlay.js') 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,20 +77,71 @@ 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; -- cgit v1.2.3 From d6c976bf65e309d298c250d7466b2cb0e523d088 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Jul 2018 11:41:22 +0200 Subject: [PALEMOON] Bug 1117141 - Part 2 of 2 - Refactor notifications and remove the DownloadsDataItem object --- .../downloads/content/allDownloadsViewOverlay.js | 165 +++++++++------------ 1 file changed, 71 insertions(+), 94 deletions(-) (limited to 'application/palemoon/components/downloads/content/allDownloadsViewOverlay.js') diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js index 59da61c91..a895bd9f6 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js @@ -2,8 +2,6 @@ * 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/. */ -XPCOMUtils.defineLazyModuleGetter(this, "DownloadsDataItem", - "resource:///modules/DownloadsCommon.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", "resource:///modules/RecentWindow.jsm"); @@ -127,21 +125,6 @@ HistoryDownload.prototype = { }, }; -/** - * 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. @@ -157,23 +140,23 @@ DownloadsHistoryDataItem.prototype = { * The caller is also responsible for forwarding status notifications for * session downloads, calling the onStateChanged and onChanged methods. * - * @param [optional] aSessionDataItem - * The session download, required if aHistoryDataItem is not set. - * @param [optional] aHistoryDataItem - * The history download, required if aSessionDataItem is not set. + * @param [optional] aSessionDownload + * The session download, required if aHistoryDownload is not set. + * @param [optional] aHistoryDownload + * The history download, required if aSessionDownload is not set. */ -function HistoryDownloadElementShell(aSessionDataItem, aHistoryDataItem) { +function HistoryDownloadElementShell(aSessionDownload, aHistoryDownload) { this.element = document.createElement("richlistitem"); this.element._shell = this; this.element.classList.add("download"); this.element.classList.add("download-state"); - if (aSessionDataItem) { - this.sessionDataItem = aSessionDataItem; + if (aSessionDownload) { + this.sessionDownload = aSessionDownload; } - if (aHistoryDataItem) { - this.historyDataItem = aHistoryDataItem; + if (aHistoryDownload) { + this.historyDownload = aHistoryDownload; } } @@ -196,20 +179,20 @@ HistoryDownloadElementShell.prototype = { get active() !!this._active, /** - * DownloadsDataItem or DownloadsHistoryDataItem object to use for displaying - * information and for executing commands in the user interface. + * Overrides the base getter to return the Download or HistoryDownload object + * for displaying information and executing commands in the user interface. */ - get dataItem() this._sessionDataItem || this._historyDataItem, - - _sessionDataItem: null, - get sessionDataItem() this._sessionDataItem, - set sessionDataItem(aValue) { - if (this._sessionDataItem != aValue) { - if (!aValue && !this._historyDataItem) { - throw new Error("Should always have either a dataItem or a historyDataItem"); + get download() this._sessionDownload || this._historyDownload, + + _sessionDownload: null, + get sessionDownload() this._sessionDownload, + set sessionDownload(aValue) { + if (this._sessionDownload != aValue) { + if (!aValue && !this._historyDownload) { + throw new Error("Should always have either a Download or a HistoryDownload"); } - this._sessionDataItem = aValue; + this._sessionDownload = aValue; this.ensureActive(); this._updateUI(); @@ -217,19 +200,19 @@ HistoryDownloadElementShell.prototype = { return aValue; }, - _historyDataItem: null, - get historyDataItem() this._historyDataItem, - set historyDataItem(aValue) { - if (this._historyDataItem != aValue) { - if (!aValue && !this._sessionDataItem) { - throw new Error("Should always have either a dataItem or a historyDataItem"); + _historyDownload: null, + get historyDownload() this._historyDownload, + set historyDownload(aValue) { + if (this._historyDownload != aValue) { + if (!aValue && !this._sessionDownload) { + throw new Error("Should always have either a Download or a HistoryDownload"); } - this._historyDataItem = aValue; + this._historyDownload = aValue; // We don't need to update the UI if we had a session data item, because // the places information isn't used in this case. - if (!this._sessionDataItem) { + if (!this._sessionDownload) { this._updateUI(); } } @@ -286,7 +269,7 @@ HistoryDownloadElementShell.prototype = { // We cannot open a session download file unless it's succeeded. // If it's succeeded, we need to make sure the file was not removed, // as we do for past downloads. - if (this._sessionDataItem && !this.download.succeeded) { + if (this._sessionDownload && !this.download.succeeded) { return false; } @@ -299,7 +282,7 @@ HistoryDownloadElementShell.prototype = { return this.download.succeeded; case "downloadsCmd_show": // TODO: Bug 827010 - Handle part-file asynchronously. - if (this._sessionDataItem && this.download.target.partFilePath) { + if (this._sessionDownload && this.download.target.partFilePath) { let partFile = new FileUtils.File(this.download.target.partFilePath); if (partFile.exists()) { return true; @@ -323,7 +306,7 @@ HistoryDownloadElementShell.prototype = { // We don't want in-progress downloads to be removed accidentally. return this.download.stopped; case "downloadsCmd_cancel": - return !!this._sessionDataItem; + return !!this._sessionDownload; } return false; }, @@ -351,13 +334,13 @@ HistoryDownloadElementShell.prototype = { break; } case "cmd_delete": { - if (this._sessionDataItem) { + if (this._sessionDownload) { Downloads.getList(Downloads.ALL) .then(list => list.remove(this.download)) .then(() => this.download.finalize(true)) .catch(Cu.reportError); } - if (this._historyDataItem) { + if (this._historyDownload) { let uri = NetUtil.newURI(this.download.source.url); PlacesUtils.bhistory.removePage(uri); } @@ -480,7 +463,7 @@ function DownloadsPlacesView(aRichListBox, aActive = true) { this._downloadElementsShellsForURI = new Map(); // Map download data items to their element shells. - this._viewItemsForDataItems = new WeakMap(); + this._viewItemsForDownloads = new WeakMap(); // Points to the last session download element. We keep track of this // in order to keep all session downloads above past downloads. @@ -626,14 +609,12 @@ DownloadsPlacesView.prototype = { * alongside the other session downloads. If we don't, then we go ahead * and create a new element for the download. * - * @param aDataItem - * The data item of a session download. Set to null for history - * downloads data. + * @param [optional] sessionDownload + * A Download object, or null for history downloads. * @param [optional] aPlacesNode - * The places node for a history download. Required if there's no data - * item. + * The Places node for a history download, or null for session downloads. * @param [optional] aNewest - * @see onDataItemAdded. Ignored for history downloads. + * @see onDownloadAdded. Ignored for history downloads. * @param [optional] aDocumentFragment * To speed up the appending of multiple elements to the end of the * list which are coming in a single batch (i.e. invalidateContainer), @@ -641,10 +622,8 @@ DownloadsPlacesView.prototype = { * be appended. It's the caller's job to ensure the fragment is merged * to the richlistbox at the end. */ - _addDownloadData: - function DPV_addDownloadData(aDataItem, aPlacesNode, aNewest = false, + _addDownloadData(sessionDownload, aPlacesNode, aNewest = false, aDocumentFragment = null) { - let sessionDownload = aDataItem && aDataItem.download; let downloadURI = aPlacesNode ? aPlacesNode.uri : sessionDownload.source.url; let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI); @@ -670,21 +649,21 @@ DownloadsPlacesView.prototype = { // item). // // Note: If a cancelled session download is already in the list, and the - // download is retired, onDataItemAdded is called again for the same + // download is retried, onDownloadAdded is called again for the same // data item. Thus, we also check that we make sure we don't have a view item // already. if (!shouldCreateShell && - aDataItem && !this._viewItemsForDataItems.has(aDataItem)) { + sessionDownload && !this._viewItemsForDownloads.has(sessionDownload)) { // If there's a past-download-only shell for this download-uri with no // associated data item, use it for the new data item. Otherwise, go ahead // and create another shell. shouldCreateShell = true; for (let shell of shellsForURI) { - if (!shell.sessionDataItem) { + if (!shell.sessionDownload) { shouldCreateShell = false; - shell.sessionDataItem = aDataItem; + shell.sessionDownload = sessionDownload; newOrUpdatedShell = shell; - this._viewItemsForDataItems.set(aDataItem, shell); + this._viewItemsForDownloads.set(sessionDownload, shell); break; } } @@ -694,18 +673,20 @@ DownloadsPlacesView.prototype = { // If we are adding a new history download here, it means there is no // associated session download, thus we must read the Places metadata, // because it will not be obscured by the session download. - let historyDataItem = null; + let historyDownload = null; if (aPlacesNode) { let metaData = this._getCachedPlacesMetaDataFor(aPlacesNode.uri); - historyDataItem = new DownloadsHistoryDataItem(aPlacesNode); - historyDataItem.download.updateFromMetaData(metaData); + historyDownload = new HistoryDownload(aPlacesNode); + historyDownload.updateFromMetaData(metaData); } - let shell = new HistoryDownloadElementShell(aDataItem, historyDataItem); + let shell = new HistoryDownloadElementShell(sessionDownload, + historyDownload); shell.element._placesNode = aPlacesNode; newOrUpdatedShell = shell; shellsForURI.add(shell); - if (aDataItem) - this._viewItemsForDataItems.set(aDataItem, shell); + if (sessionDownload) { + this._viewItemsForDownloads.set(sessionDownload, shell); + } } else if (aPlacesNode) { // We are updating information for a history download for which we have @@ -720,9 +701,9 @@ DownloadsPlacesView.prototype = { // changed, just the reference to the Places node object is different. // So, we update all the node references and keep the metadata intact. for (let shell of shellsForURI) { - if (!shell.historyDataItem) { + if (!shell.historyDownload) { // Create the element to host the metadata when needed. - shell.historyDataItem = new DownloadsHistoryDataItem(aPlacesNode); + shell.historyDownload = new HistoryDownload(aPlacesNode); } shell.element._placesNode = aPlacesNode; } @@ -740,7 +721,7 @@ DownloadsPlacesView.prototype = { // More generally, if a new download is added, should be made visible. this._richlistbox.ensureElementIsVisible(newOrUpdatedShell.element); } - else if (aDataItem) { + } else if (sessionDownload) { let before = this._lastSessionDownloadElement ? this._lastSessionDownloadElement.nextSibling : this._richlistbox.firstChild; this._richlistbox.insertBefore(newOrUpdatedShell.element, before); @@ -791,8 +772,8 @@ DownloadsPlacesView.prototype = { let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI); if (shellsForURI) { for (let shell of shellsForURI) { - if (shell.sessionDataItem) { - shell.historyDataItem = null; + if (shell.sessionDownload) { + shell.historyDownload = null; } else { this._removeElement(shell.element); @@ -804,15 +785,13 @@ DownloadsPlacesView.prototype = { } }, - _removeSessionDownloadFromView: - function DPV__removeSessionDownloadFromView(aDataItem) { - let download = aDataItem.download; + _removeSessionDownloadFromView(download) { let shells = this._downloadElementsShellsForURI .get(download.source.url); if (shells.size == 0) throw new Error("Should have had at leaat one shell for this uri"); - let shell = this._viewItemsForDataItems.get(aDataItem); + let shell = this._viewItemsForDownloads.get(download); if (!shells.has(shell)) throw new Error("Missing download element shell in shells list for url"); @@ -820,7 +799,7 @@ DownloadsPlacesView.prototype = { // view item for this this particular data item go away. // If there's only one item for this download uri, we should only // keep it if it is associated with a history download. - if (shells.size > 1 || !shell.historyDataItem) { + if (shells.size > 1 || !shell.historyDownload) { this._removeElement(shell.element); shells.delete(shell); if (shells.size == 0) @@ -832,10 +811,10 @@ DownloadsPlacesView.prototype = { // Previously, we did not use the Places metadata because it was obscured // by the session download. Since this is no longer the case, we have to // read the latest metadata before removing the session download. - let url = shell.historyDataItem.download.source.url; + let url = shell.historyDownload.source.url; let metaData = this._getPlacesMetaDataFor(url); - shell.historyDataItem.download.updateFromMetaData(metaData); - shell.sessionDataItem = null; + shell.historyDownload.updateFromMetaData(metaData); + shell.sessionDownload = null; // Move it below the session-download items; if (this._lastSessionDownloadElement == shell.element) { this._lastSessionDownloadElement = shell.element.previousSibling; @@ -1108,22 +1087,20 @@ DownloadsPlacesView.prototype = { this._ensureInitialSelection(); }, - onDataItemAdded: function DPV_onDataItemAdded(aDataItem, aNewest) { - this._addDownloadData(aDataItem, null, aNewest); + onDownloadAdded(download, newest) { + this._addDownloadData(download, null, newest); }, - onDataItemRemoved: function DPV_onDataItemRemoved(aDataItem) { - this._removeSessionDownloadFromView(aDataItem); + onDownloadStateChanged(download) { + this._viewItemsForDownloads.get(download).onStateChanged(); }, - // DownloadsView - onDataItemStateChanged(aDataItem) { - this._viewItemsForDataItems.get(aDataItem).onStateChanged(); + onDownloadChanged(download) { + this._viewItemsForDownloads.get(download).onChanged(); }, - // DownloadsView - onDataItemChanged(aDataItem) { - this._viewItemsForDataItems.get(aDataItem).onChanged(); + onDownloadRemoved(download) { + this._removeSessionDownloadFromView(download); }, supportsCommand: function DPV_supportsCommand(aCommand) { -- cgit v1.2.3 From 761b6eb255109a07d7cbcaaef6891cb14c961dbc Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Jul 2018 12:29:40 +0200 Subject: Fix typos --- .../palemoon/components/downloads/content/allDownloadsViewOverlay.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'application/palemoon/components/downloads/content/allDownloadsViewOverlay.js') diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js index a895bd9f6..a5cbb3a5f 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js @@ -251,8 +251,9 @@ HistoryDownloadElementShell.prototype = { if (this.element.selected) { goUpdateDownloadCommands(); - else + } else { goUpdateCommand("downloadsCmd_clearDownloads"); + } }, onChanged() { @@ -720,7 +721,6 @@ DownloadsPlacesView.prototype = { // the top of the richlistbox, along with other session downloads. // More generally, if a new download is added, should be made visible. this._richlistbox.ensureElementIsVisible(newOrUpdatedShell.element); - } } else if (sessionDownload) { let before = this._lastSessionDownloadElement ? this._lastSessionDownloadElement.nextSibling : this._richlistbox.firstChild; -- cgit v1.2.3 From beeede618586155d0f3fcb8e9313e076eef3e6e5 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Jul 2018 12:41:33 +0200 Subject: [PALEMOON] Bug 1127867 - Use the new back-end property to get the size of downloads asynchronously --- .../downloads/content/allDownloadsViewOverlay.js | 69 ++++++++++++---------- 1 file changed, 39 insertions(+), 30 deletions(-) (limited to 'application/palemoon/components/downloads/content/allDownloadsViewOverlay.js') diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js index a5cbb3a5f..50f77041b 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js @@ -28,8 +28,14 @@ const DOWNLOAD_VIEW_SUPPORTED_COMMANDS = */ function HistoryDownload(aPlacesNode) { // TODO (bug 829201): history downloads should get the referrer from Places. - this.source = { url: aPlacesNode.uri }; - this.target = { path: undefined, size: undefined }; + this.source = { + url: aPlacesNode.uri, + }; + this.target = { + path: undefined, + exists: false, + size: undefined, + }; // 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. @@ -63,6 +69,10 @@ HistoryDownload.prototype = { this.canceled = metaData.state == nsIDM.DOWNLOAD_CANCELED || metaData.state == nsIDM.DOWNLOAD_PAUSED; this.endTime = metaData.endTime; + + // Normal history downloads are assumed to exist until the user interface + // is refreshed, at which point these values may be updated. + this.target.exists = true; this.target.size = metaData.fileSize; } catch (ex) { // Metadata might be missing from a download that has started but hasn't @@ -78,13 +88,11 @@ HistoryDownload.prototype = { 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.totalBytes = this.target.size; - this.currentBytes = this.target.size; + // These properties may be updated if the user interface is refreshed. + this.exists = false; + this.target.size = undefined; + } }, /** @@ -123,6 +131,20 @@ HistoryDownload.prototype = { return Promise.resolve(); }, + + /** + * This method mimicks the "refresh" method of session downloads, except that + * it cannot notify that the data changed to the Downloads View. + */ + refresh: Task.async(function* () { + try { + this.target.size = (yield OS.File.stat(this.target.path)).size; + this.target.exists = true; + } catch (ex) { + // We keep the known file size from the metadata, if any. + this.target.exists = false; + } + }), }; /** @@ -267,20 +289,8 @@ HistoryDownloadElementShell.prototype = { return false; switch (aCommand) { case "downloadsCmd_open": - // We cannot open a session download file unless it's succeeded. - // If it's succeeded, we need to make sure the file was not removed, - // as we do for past downloads. - if (this._sessionDownload && !this.download.succeeded) { - return false; - } - - if (this._targetFileChecked) { - return this._targetFileExists; - } - - // If the target file information is not yet fetched, - // temporarily assume that the file is in place. - return this.download.succeeded; + // This property is false if the download did not succeed. + return this.download.target.exists; case "downloadsCmd_show": // TODO: Bug 827010 - Handle part-file asynchronously. if (this._sessionDownload && this.download.target.partFilePath) { @@ -290,13 +300,8 @@ HistoryDownloadElementShell.prototype = { } } - if (this._targetFileChecked) { - return this._targetFileExists; - } - - // If the target file information is not yet fetched, - // temporarily assume that the file is in place. - return this.download.succeeded; + // This property is false if the download did not succeed. + return this.download.target.exists; case "downloadsCmd_pauseResume": return this.download.hasPartialData && !this.download.error; case "downloadsCmd_retry": @@ -430,7 +435,7 @@ HistoryDownloadElementShell.prototype = { _checkTargetFileOnSelect: Task.async(function* () { try { - this._targetFileExists = yield OS.File.exists(this.download.target.path); + yield this.download.refresh(); } finally { // Do not try to check for existence again if this failed once. this._targetFileChecked = true; @@ -440,6 +445,10 @@ HistoryDownloadElementShell.prototype = { if (this.element.selected) { goUpdateDownloadCommands(); } + + // Ensure the interface has been updated based on the new values. We need to + // do this because history downloads can't trigger update notifications. + this._updateProgress(); }), }; -- cgit v1.2.3 From 8cc3f3616905228be77661a5c9a15ab3f2d867f0 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Jul 2018 12:42:15 +0200 Subject: [PALEMOON] Bug 1195279 - Fix typo in allDownloadsViewOverlay.js following landing of bug 1127867 --- .../palemoon/components/downloads/content/allDownloadsViewOverlay.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'application/palemoon/components/downloads/content/allDownloadsViewOverlay.js') diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js index 50f77041b..980913cfe 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js @@ -90,7 +90,7 @@ HistoryDownload.prototype = { this.canceled = false; // These properties may be updated if the user interface is refreshed. - this.exists = false; + this.target.exists = false; this.target.size = undefined; } }, -- cgit v1.2.3 From 0b15a2b89e6615c5e68f75c2515fe0cdf49178e4 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Jul 2018 12:58:23 +0200 Subject: [PALEMOON] Bug 1129896 - Part 1 of 2 - Keep cached metadata for history downloads indefinitely --- .../downloads/content/allDownloadsViewOverlay.js | 145 ++++++++++----------- 1 file changed, 69 insertions(+), 76 deletions(-) (limited to 'application/palemoon/components/downloads/content/allDownloadsViewOverlay.js') diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js index 980913cfe..c86df36df 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js @@ -46,18 +46,16 @@ HistoryDownload.prototype = { /** * Pushes information from Places metadata into this object. */ - updateFromMetaData(aPlacesMetaData) { + updateFromMetaData(metaData) { try { this.target.path = Cc["@mozilla.org/network/protocol;1?name=file"] .getService(Ci.nsIFileProtocolHandler) - .getFileFromURLSpec(aPlacesMetaData. - targetFileURISpec).path; + .getFileFromURLSpec(metaData.targetFileSpec).path; } catch (ex) { this.target.path = undefined; } - try { - let metaData = JSON.parse(aPlacesMetaData.jsonDetails); + if ("state" in metaData) { this.succeeded = metaData.state == nsIDM.DOWNLOAD_FINISHED; this.error = metaData.state == nsIDM.DOWNLOAD_FAILED ? { message: "History download failed." } @@ -74,7 +72,7 @@ HistoryDownload.prototype = { // is refreshed, at which point these values may be updated. this.target.exists = true; this.target.size = metaData.fileSize; - } catch (ex) { + } else { // Metadata might be missing from a download that has started but hasn't // stopped already. Normally, this state is overridden with the one from // the corresponding in-progress session download. But if the browser is @@ -517,94 +515,82 @@ DownloadsPlacesView.prototype = { }, /** - * Map containing a metadata object for each download source URI found in - * Places annotations, or null if this cache must be rebuilt. - */ - _cachedPlacesMetaData: null, - - /** - * This method exists in order to optimize the first load of the Downloads - * View, when Places annotations for history downloads must be read. In fact, - * annotations are stored in a single table, and reading all of them at once - * is much more efficient than an individual query. + * This cache exists in order to optimize the load of the Downloads View, when + * Places annotations for history downloads must be read. In fact, annotations + * are stored in a single table, and reading all of them at once is much more + * efficient than an individual query. * - * When this metod is first called, after the Places result container has been - * invalidated, it reads the annotations for all the history downloads. + * When this property is first requested, it reads the annotations for all the + * history downloads and stores them indefinitely. * - * At this point, as metadata for the various elements is requested by the - * view, it is pulled from the cache and returned as an object. + * The historical annotations are not expected to change for the duration of + * the session, except in the case where a session download is running for the + * same URI as a history download. To ensure we don't use stale data, URIs + * corresponding to session downloads are permanently removed from the cache. + * This is a very small mumber compared to history downloads. * - * This works efficiently because the metadata is requested only for newly - * created element shells that contain a history download but don't have a - * matching session download, and at most one such element shell is created - * for each source URI. + * This property returns a Map from each download source URI found in Places + * annotations to an object with the format: * - * @param url - * URI string of the Places node for which metadata is requested. + * { targetFileSpec, state, endTime, fileSize, ... } * - * @return Object of the form { targetFileURISpec, jsonDetails }. - * Any of the properties may be missing from the object. + * The targetFileSpec property is the value of "downloads/destinationFileURI", + * while the other properties are taken from "downloads/metaData". Any of the + * properties may be missing from the object. */ - _getCachedPlacesMetaDataFor(url) { - if (!this._cachedPlacesMetaData) { - this._cachedPlacesMetaData = new Map(); + get _cachedPlacesMetaData() { + if (!this.__cachedPlacesMetaData) { + this.__cachedPlacesMetaData = new Map(); - // Use the destination file annotation to build the initial Map of items. + // Read the metadata annotations first, but ignore invalid JSON. for (let result of PlacesUtils.annotations.getAnnotationsWithName( - DESTINATION_FILE_URI_ANNO)) { - this._cachedPlacesMetaData.set(result.uri.spec, { - targetFileURISpec: result.annotationValue, - }); + DOWNLOAD_META_DATA_ANNO)) { + try { + this.__cachedPlacesMetaData.set(result.uri.spec, + JSON.parse(result.annotationValue)); + } catch (ex) {} } - // Add the string with the JSON details from the metadata annotation. + // Add the target file annotations to the metadata. for (let result of PlacesUtils.annotations.getAnnotationsWithName( - DOWNLOAD_META_DATA_ANNO)) { - let metadata = this._cachedPlacesMetaData.get(result.uri.spec); - - // The destination file annotation is expected to be present for all - // the downloads with the metadata annotation. If this is not the case, - // we simply ignore the item, as if it has no JSON details at all. - if (metadata) { - metadata.jsonDetails = result.annotationValue; + DESTINATION_FILE_URI_ANNO)) { + let metaData = this.__cachedPlacesMetaData.get(result.uri.spec); + if (!metaData) { + metaData = {}; + this.__cachedPlacesMetaData.set(result.uri.spec, metaData); } + metaData.targetFileSpec = result.annotationValue; } } - let metadata = this._cachedPlacesMetaData.get(url); - this._cachedPlacesMetaData.delete(url); - - // In the rare case where a history download is added again, but there is no - // corresponding session download, read the metadata from the database. This - // could potentially cause an inefficient database access for very old - // downloads with no metadata to begin with, but these should have been - // already deleted from history on a recent browser profile. - return metadata || this._getPlacesMetaDataFor(url); + return this.__cachedPlacesMetaData; }, + __cachedPlacesMetaData: null, /** - * Read the latest version of the Places metadata for the given URI. + * Reads current metadata from Places annotations for the specified URI, and + * returns an object with the format: * - * @param url - * URI string of the Places node for which metadata is requested. + * { targetFileSpec, state, endTime, fileSize, ... } * - * @return Object of the form { targetFileURISpec, jsonDetails }. - * Any of the properties may be missing from the object. + * The targetFileSpec property is the value of "downloads/destinationFileURI", + * while the other properties are taken from "downloads/metaData". Any of the + * properties may be missing from the object. */ - _getPlacesMetaDataFor(url) { - let metadata = {}; + _getPlacesMetaDataFor(spec) { + let metaData = {}; try { - let uri = NetUtil.newURI(url); - metadata = { - targetFileURISpec: PlacesUtils.annotations.getPageAnnotation( - uri, DESTINATION_FILE_URI_ANNO), - }; - metadata.jsonDetails = PlacesUtils.annotations.getPageAnnotation( - uri, DOWNLOAD_META_DATA_ANNO); + let uri = NetUtil.newURI(spec); + try { + metaData = JSON.parse(PlacesUtils.annotations.getPageAnnotation( + uri, DOWNLOAD_META_DATA_ANNO)); + } catch (ex) {} + metaData.targetFileSpec = PlacesUtils.annotations.getPageAnnotation( + uri, DESTINATION_FILE_URI_ANNO); } catch (ex) {} - return metadata; + return metaData; }, /** @@ -642,6 +628,18 @@ DownloadsPlacesView.prototype = { this._downloadElementsShellsForURI.set(downloadURI, shellsForURI); } + // When a session download is attached to a shell, we ensure not to keep + // stale metadata around for the corresponding history download. This + // prevents stale state from being used if the view is rebuilt. + // + // Note that we will eagerly load the data in the cache at this point, even + // if we have seen no history download. The case where no history download + // will appear at all is rare enough in normal usage, so we can apply this + // simpler solution rather than keeping a list of cache items to ignore. + if (sessionDownload) { + this._cachedPlacesMetaData.delete(sessionDownload.source.url); + } + let newOrUpdatedShell = null; // Trivial: if there are no shells for this download URI, we always @@ -685,7 +683,8 @@ DownloadsPlacesView.prototype = { // because it will not be obscured by the session download. let historyDownload = null; if (aPlacesNode) { - let metaData = this._getCachedPlacesMetaDataFor(aPlacesNode.uri); + let metaData = this._cachedPlacesMetaData.get(aPlacesNode.uri) || + this._getPlacesMetaDataFor(aPlacesNode.uri); historyDownload = new HistoryDownload(aPlacesNode); historyDownload.updateFromMetaData(metaData); } @@ -955,12 +954,6 @@ DownloadsPlacesView.prototype = { if (!aContainer.containerOpen) throw new Error("Root container for the downloads query cannot be closed"); - // When the container is invalidated, it means we are about to re-read all - // the information about history downloads from the database, discarding the - // download element shells, thus we fully rebuild the Places annotation - // cache with the latest values. - this._cachedPlacesMetaData = null; - let suppressOnSelect = this._richlistbox.suppressOnSelect; this._richlistbox.suppressOnSelect = true; try { -- cgit v1.2.3 From 551c6ff0463b555d32c51d22163318b7204c5388 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Jul 2018 13:12:21 +0200 Subject: [PALEMOON] Bug 1129896 - Part 2 of 2 - Convert the shared front-end code to a JavaScript code module --- .../downloads/content/allDownloadsViewOverlay.js | 53 +++++++++++++++------- 1 file changed, 36 insertions(+), 17 deletions(-) (limited to 'application/palemoon/components/downloads/content/allDownloadsViewOverlay.js') diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js index c86df36df..a663b6659 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js @@ -2,8 +2,30 @@ * 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/. */ +var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils", + "resource://gre/modules/DownloadUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon", + "resource:///modules/DownloadsCommon.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "DownloadsViewUI", + "resource:///modules/DownloadsViewUI.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", + "resource://gre/modules/NetUtil.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "OS", + "resource://gre/modules/osfile.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", + "resource://gre/modules/PlacesUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", "resource:///modules/RecentWindow.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); const nsIDM = Ci.nsIDownloadManager; @@ -20,11 +42,8 @@ const DOWNLOAD_VIEW_SUPPORTED_COMMANDS = * Represents a download from the browser history. It implements part of the * interface of the Download object. * - * @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. + * @param aPlacesNode + * The Places node from which the history download should be initialized. */ function HistoryDownload(aPlacesNode) { // TODO (bug 829201): history downloads should get the referrer from Places. @@ -115,11 +134,14 @@ HistoryDownload.prototype = { /** * This method mimicks the "start" method of session downloads, and is called * when the user retries a history download. + * + * At present, we always ask the user for a new target path when retrying a + * history download. In the future we may consider reusing the known target + * path if the folder still exists and the file name is not already used, + * except when the user preferences indicate that the target path should be + * requested every time a new download is started. */ 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; @@ -150,7 +172,7 @@ HistoryDownload.prototype = { * displayed data for a single download view element. * * The shell may contain a session download, a history download, or both. When - * both a history and a current download are present, the current download gets + * both a history and a session download are present, the session download gets * priority and its information is displayed. * * On construction, a new richlistitem is created, and can be accessed through @@ -181,7 +203,7 @@ function HistoryDownloadElementShell(aSessionDownload, aHistoryDownload) { } HistoryDownloadElementShell.prototype = { - __proto__: DownloadElementShell.prototype, + __proto__: DownloadsViewUI.DownloadElementShell.prototype, /** * Manages the "active" state of the shell. By default all the shells without @@ -339,10 +361,7 @@ HistoryDownloadElementShell.prototype = { } case "cmd_delete": { if (this._sessionDownload) { - Downloads.getList(Downloads.ALL) - .then(list => list.remove(this.download)) - .then(() => this.download.finalize(true)) - .catch(Cu.reportError); + DownloadsCommon.removeAndFinalizeDownload(this.download); } if (this._historyDownload) { let uri = NetUtil.newURI(this.download.source.url); @@ -402,8 +421,8 @@ HistoryDownloadElementShell.prototype = { } return ""; } - let command = getDefaultCommandForState( - DownloadsCommon.stateOfDownload(this.download)); + let state = DownloadsCommon.stateOfDownload(this.download); + let command = getDefaultCommandForState(state); if (command && this.isCommandEnabled(command)) this.doCommand(command); }, @@ -452,7 +471,7 @@ HistoryDownloadElementShell.prototype = { /** * A Downloads Places View is a places view designed to show a places query - * for history downloads alongside the current "session"-downloads. + * for history downloads alongside the session downloads. * * As we don't use the places controller, some methods implemented by other * places views are not implemented by this view. -- cgit v1.2.3 From 909c3ee13f633b0fd8c4b1b7501d3896ae630f6d Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Jul 2018 13:20:49 +0200 Subject: [PALEMOON] Bug 1135348 - Fix about:downloads by adding missing XPCOMUtils imports --- .../palemoon/components/downloads/content/allDownloadsViewOverlay.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'application/palemoon/components/downloads/content/allDownloadsViewOverlay.js') diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js index a663b6659..4830f2128 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js @@ -4,6 +4,8 @@ var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils", "resource://gre/modules/DownloadUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon", -- cgit v1.2.3