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 --- .../components/downloads/DownloadsCommon.jsm | 93 ++++++++++------------ .../downloads/content/allDownloadsViewOverlay.js | 27 ++++--- .../components/downloads/content/downloads.js | 56 +++++-------- 3 files changed, 78 insertions(+), 98 deletions(-) (limited to 'application/palemoon/components') diff --git a/application/palemoon/components/downloads/DownloadsCommon.jsm b/application/palemoon/components/downloads/DownloadsCommon.jsm index bd5d55a73..0a3af7878 100644 --- a/application/palemoon/components/downloads/DownloadsCommon.jsm +++ b/application/palemoon/components/downloads/DownloadsCommon.jsm @@ -773,7 +773,7 @@ DownloadsDataCtor.prototype = { if (oldState != aDataItem.state) { for (let view of this._views) { try { - view.getViewItem(aDataItem).onStateChange(oldState); + view.onDataItemStateChanged(aDataItem, oldState); } catch (ex) { Cu.reportError(ex); } @@ -812,7 +812,7 @@ DownloadsDataCtor.prototype = { } for (let view of this._views) { - view.getViewItem(aDataItem).onProgressChange(); + view.onDataItemChanged(aDataItem); } }, @@ -1889,17 +1889,26 @@ const DownloadsViewPrototype = { }, /** - * Returns the view item associated with the provided data item for this view. + * Called when the "state" property of a DownloadsDataItem has changed. * - * @param aDataItem - * DownloadsDataItem object for which the view item is requested. + * The onDataItemChanged notification will be sent afterwards. * - * @return Object that can be used to notify item status events. + * @note Subclasses should override this. + */ + onDataItemStateChanged(aDataItem) { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + + /** + * Called every time any state property of a DownloadsDataItem may have + * changed, including progress properties and the "state" property. + * + * Note that progress notification changes are throttled at the Downloads.jsm + * API level, and there is no throttling mechanism in the front-end. * * @note Subclasses should override this. */ - getViewItem: function DID_getViewItem(aDataItem) - { + onDataItemChanged(aDataItem) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; }, @@ -2014,37 +2023,21 @@ DownloadsIndicatorDataCtor.prototype = { this._updateViews(); }, - /** - * Returns the view item associated with the provided data item for this view. - * - * @param aDataItem - * DownloadsDataItem object for which the view item is requested. - * - * @return Object that can be used to notify item status events. - */ - getViewItem: function DID_getViewItem(aDataItem) - { - let data = this._isPrivate ? PrivateDownloadsIndicatorData - : DownloadsIndicatorData; - return Object.freeze({ - onStateChange: function DIVI_onStateChange(aOldState) - { - if (aDataItem.state == nsIDM.DOWNLOAD_FINISHED || - aDataItem.state == nsIDM.DOWNLOAD_FAILED) { - data.attention = true; - } + // DownloadsView + onDataItemStateChanged(aDataItem, aOldState) { + if (aDataItem.state == nsIDM.DOWNLOAD_FINISHED || + aDataItem.state == nsIDM.DOWNLOAD_FAILED) { + this.attention = true; + } - // Since the state of a download changed, reset the estimated time left. - data._lastRawTimeLeft = -1; - data._lastTimeLeft = -1; + // Since the state of a download changed, reset the estimated time left. + this._lastRawTimeLeft = -1; + this._lastTimeLeft = -1; + }, - data._updateViews(); - }, - onProgressChange: function DIVI_onProgressChange() - { - data._updateViews(); - } - }); + // DownloadsView + onDataItemChanged() { + this._updateViews(); }, ////////////////////////////////////////////////////////////////////////////// @@ -2298,22 +2291,16 @@ DownloadsSummaryData.prototype = { this._updateViews(); }, - getViewItem: function DSD_getViewItem(aDataItem) - { - let self = this; - return Object.freeze({ - onStateChange: function DIVI_onStateChange(aOldState) - { - // Since the state of a download changed, reset the estimated time left. - self._lastRawTimeLeft = -1; - self._lastTimeLeft = -1; - self._updateViews(); - }, - onProgressChange: function DIVI_onProgressChange() - { - self._updateViews(); - } - }); + // DownloadsView + onDataItemStateChanged(aOldState) { + // Since the state of a download changed, reset the estimated time left. + this._lastRawTimeLeft = -1; + this._lastTimeLeft = -1; + }, + + // DownloadsView + onDataItemChanged() { + this._updateViews(); }, ////////////////////////////////////////////////////////////////////////////// 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) { diff --git a/application/palemoon/components/downloads/content/downloads.js b/application/palemoon/components/downloads/content/downloads.js index 833d7d72f..44457e711 100644 --- a/application/palemoon/components/downloads/content/downloads.js +++ b/application/palemoon/components/downloads/content/downloads.js @@ -570,7 +570,7 @@ const DownloadsPanel = { // still exist, and update the allowed items interactions accordingly. We // do these checks on a background thread, and don't prevent the panel to // be displayed while these checks are being performed. - for each (let viewItem in DownloadsView._viewItems) { + for (let viewItem of DownloadsView._visibleViewItems.values()) { viewItem.verifyTargetExists(); } @@ -711,11 +711,11 @@ const DownloadsView = { _dataItems: [], /** - * Object containing the available DownloadsViewItem objects, indexed by their - * numeric download identifier. There is a limited number of view items in - * the panel at any given time. + * Associates the visible DownloadsDataItem objects with their corresponding + * DownloadsViewItem object. There is a limited number of view items in the + * panel at any given time. */ - _viewItems: {}, + _visibleViewItems: new Map(), /** * Called when the number of items in the list changes. @@ -879,31 +879,21 @@ const DownloadsView = { this._itemCountChanged(); }, - /** - * Returns the view item associated with the provided data item for this view. - * - * @param aDataItem - * DownloadsDataItem object for which the view item is requested. - * - * @return Object that can be used to notify item status events. - */ - getViewItem: function DV_getViewItem(aDataItem) - { - // If the item is visible, just return it, otherwise return a mock object - // that doesn't react to notifications. - if (aDataItem.downloadGuid in this._viewItems) { - return this._viewItems[aDataItem.downloadGuid]; + // DownloadsView + onDataItemStateChanged(aDataItem, aOldState) { + let viewItem = this._visibleViewItems.get(aDataItem); + if (viewItem) { + viewItem.onStateChanged(aOldState); } - return this._invisibleViewItem; }, - /** - * Mock DownloadsDataItem object that doesn't react to notifications. - */ - _invisibleViewItem: Object.freeze({ - onStateChange: function () { }, - onProgressChange: function () { } - }), + // DownloadsView + onDataItemChanged(aDataItem) { + let viewItem = this._visibleViewItems.get(aDataItem); + if (viewItem) { + viewItem.onChanged(); + } + }, /** * Creates a new view item associated with the specified data item, and adds @@ -916,7 +906,7 @@ const DownloadsView = { let element = document.createElement("richlistitem"); let viewItem = new DownloadsViewItem(aDataItem, element); - this._viewItems[aDataItem.downloadGuid] = viewItem; + this._visibleViewItems.set(aDataItem, viewItem); if (aNewest) { this.richListBox.insertBefore(element, this.richListBox.firstChild); } else { @@ -930,14 +920,14 @@ const DownloadsView = { _removeViewItem: function DV_removeViewItem(aDataItem) { DownloadsCommon.log("Removing a DownloadsViewItem from the downloads list."); - let element = this.getViewItem(aDataItem)._element; + let element = this._visibleViewItems.get(aDataItem)._element; let previousSelectedIndex = this.richListBox.selectedIndex; this.richListBox.removeChild(element); if (previousSelectedIndex != -1) { this.richListBox.selectedIndex = Math.min(previousSelectedIndex, this.richListBox.itemCount - 1); } - delete this._viewItems[aDataItem.downloadGuid]; + this._visibleViewItems.delete(aDataItem); }, ////////////////////////////////////////////////////////////////////////////// @@ -1134,7 +1124,7 @@ DownloadsViewItem.prototype = { * the download might be the same as before, if the data layer received * multiple events for the same download. */ - onStateChange: function DVI_onStateChange(aOldState) + onStateChanged(aOldState) { { // 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 @@ -1155,14 +1145,12 @@ DownloadsViewItem.prototype = { // Update the user interface after switching states. this._element.setAttribute("state", this.dataItem.state); - this._updateProgress(); - this._updateStatusLine(); }, /** * Called when the download progress has changed. */ - onProgressChange: function DVI_onProgressChange() { + onChanged() { this._updateProgress(); this._updateStatusLine(); }, -- 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') 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') 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') 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') 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 --- .../components/downloads/DownloadsCommon.jsm | 172 ++++----------------- .../downloads/content/allDownloadsViewOverlay.js | 79 +++++----- .../components/downloads/content/downloads.js | 76 +++++---- 3 files changed, 118 insertions(+), 209 deletions(-) (limited to 'application/palemoon/components') diff --git a/application/palemoon/components/downloads/DownloadsCommon.jsm b/application/palemoon/components/downloads/DownloadsCommon.jsm index 0a3af7878..42864840b 100644 --- a/application/palemoon/components/downloads/DownloadsCommon.jsm +++ b/application/palemoon/components/downloads/DownloadsCommon.jsm @@ -57,6 +57,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper", "resource://gre/modules/DownloadUIHelper.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils", "resource://gre/modules/DownloadUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm") XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", @@ -94,11 +96,6 @@ const kDownloadsStringsRequiringPluralForm = { otherDownloads2: true }; -XPCOMUtils.defineLazyGetter(this, "DownloadsLocalFileCtor", function () { - return Components.Constructor("@mozilla.org/file/local;1", - "nsILocalFile", "initWithPath"); -}); - const kPartialDownloadSuffix = ".part"; const kPrefBranch = Services.prefs.getBranch("browser.download."); @@ -431,12 +428,12 @@ this.DownloadsCommon = { break; case nsIDM.DOWNLOAD_DOWNLOADING: summary.numDownloading++; - if (dataItem.maxBytes > 0 && dataItem.speed > 0) { - let sizeLeft = dataItem.maxBytes - dataItem.currBytes; + if (dataItem.maxBytes > 0 && dataItem.download.speed > 0) { + let sizeLeft = dataItem.maxBytes - dataItem.download.currentBytes; summary.rawTimeLeft = Math.max(summary.rawTimeLeft, - sizeLeft / dataItem.speed); + sizeLeft / dataItem.download.speed); summary.slowestSpeed = Math.min(summary.slowestSpeed, - dataItem.speed); + dataItem.download.speed); } break; } @@ -445,7 +442,7 @@ this.DownloadsCommon = { dataItem.state != nsIDM.DOWNLOAD_CANCELED && dataItem.state != nsIDM.DOWNLOAD_FAILED) { summary.totalSize += dataItem.maxBytes; - summary.totalTransferred += dataItem.currBytes; + summary.totalTransferred += dataItem.download.currentBytes; } } @@ -574,7 +571,6 @@ this.DownloadsCommon = { /** * Show a downloaded file in the system file manager. - * If you have a dataItem, use dataItem.showLocalFile. * * @param aFile * a downloaded file. @@ -793,7 +789,8 @@ DownloadsDataCtor.prototype = { // RRR: Annotation service throws here. commented out for now. /*PlacesUtils.annotations.setPageAnnotation( - NetUtil.newURI(aDataItem.uri), "downloads/metaData", + NetUtil.newURI(aDataItem.download.source.url), + "downloads/metaData", JSON.stringify(downloadMetaData), 0, PlacesUtils.annotations.EXPIRE_WITH_HISTORY);*/ } catch (ex) { @@ -873,7 +870,7 @@ DownloadsDataCtor.prototype = { } } - loadedItemsArray.sort(function(a, b) b.startTime - a.startTime); + loadedItemsArray.sort(function(a, b) b.download.startTime - a.download.startTime); loadedItemsArray.forEach( function (dataItem) aView.onDataItemAdded(dataItem, false) ); @@ -1364,12 +1361,9 @@ DownloadsDataItem.prototype = { */ _initFromJSDownload: function (aDownload) { - this._download = aDownload; + this.download = aDownload; this.downloadGuid = "id:" + this._autoIncrementId; - this.file = aDownload.target.path; - this.target = OS.Path.basename(aDownload.target.path); - this.uri = aDownload.source.url; this.endTime = Date.now(); this.updateFromJSDownload(); @@ -1381,41 +1375,35 @@ DownloadsDataItem.prototype = { updateFromJSDownload: function () { // Collapse state using the correct priority. - if (this._download.succeeded) { + if (this.download.succeeded) { this.state = nsIDM.DOWNLOAD_FINISHED; - } else if (this._download.error && - this._download.error.becauseBlockedByParentalControls) { + } else if (this.download.error && + this.download.error.becauseBlockedByParentalControls) { this.state = nsIDM.DOWNLOAD_BLOCKED_PARENTAL; - } else if (this._download.error) { + } else if (this.download.error) { this.state = nsIDM.DOWNLOAD_FAILED; - } else if (this._download.canceled && this._download.hasPartialData) { + } else if (this.download.canceled && this.download.hasPartialData) { this.state = nsIDM.DOWNLOAD_PAUSED; - } else if (this._download.canceled) { + } else if (this.download.canceled) { this.state = nsIDM.DOWNLOAD_CANCELED; - } else if (this._download.stopped) { + } else if (this.download.stopped) { this.state = nsIDM.DOWNLOAD_NOTSTARTED; } else { this.state = nsIDM.DOWNLOAD_DOWNLOADING; } - this.referrer = this._download.source.referrer; - this.startTime = this._download.startTime; - this.currBytes = this._download.currentBytes; - this.resumable = this._download.hasPartialData; - this.speed = this._download.speed; - - if (this._download.succeeded) { + if (this.download.succeeded) { // If the download succeeded, show the final size if available, otherwise // use the last known number of bytes transferred. The final size on disk // will be available when bug 941063 is resolved. - this.maxBytes = this._download.hasProgress ? - this._download.totalBytes : - this._download.currentBytes; + this.maxBytes = this.download.hasProgress ? + this.download.totalBytes : + this.download.currentBytes; this.percentComplete = 100; - } else if (this._download.hasProgress) { + } else if (this.download.hasProgress) { // If the final size and progress are known, use them. - this.maxBytes = this._download.totalBytes; - this.percentComplete = this._download.progress; + this.maxBytes = this.download.totalBytes; + this.percentComplete = this.download.progress; } else { // The download final size and progress percentage is unknown. this.maxBytes = -1; @@ -1580,14 +1568,6 @@ DownloadsDataItem.prototype = { ].indexOf(this.state) != -1; }, - /** - * Indicates whether the download is finished and can be opened. - */ - get openable() - { - return this.state == nsIDM.DOWNLOAD_FINISHED; - }, - /** * Indicates whether the download stopped because of an error, and can be * resumed manually. @@ -1604,10 +1584,13 @@ DownloadsDataItem.prototype = { * @throws if the native path is not valid. This can happen if the same * profile is used on different platforms, for example if a native * Windows path is stored and then the item is accessed on a Mac. + * + * @deprecated Callers should use OS.File and "download.target.path". */ get localFile() { - return this._getFile(this.file); + // We should remove should use this.download.target.partFilePath and check asyncrhonously. + return new FileUtils.File(this.download.target.path); }, /** @@ -1616,80 +1599,12 @@ DownloadsDataItem.prototype = { * @throws if the native path is not valid. This can happen if the same * profile is used on different platforms, for example if a native * Windows path is stored and then the item is accessed on a Mac. + * + * @deprecated Callers should use OS.File and "download.target.partFilePath". */ get partFile() { - return this._getFile(this.file + kPartialDownloadSuffix); - }, - - /** - * Returns an nsILocalFile for aFilename. aFilename might be a file URL or - * a native path. - * - * @param aFilename the filename of the file to retrieve. - * @return an nsILocalFile for the file. - * @throws if the native path is not valid. This can happen if the same - * profile is used on different platforms, for example if a native - * Windows path is stored and then the item is accessed on a Mac. - * @note This function makes no guarantees about the file's existence - - * callers should check that the returned file exists. - */ - _getFile: function DDI__getFile(aFilename) - { - // The download database may contain targets stored as file URLs or native - // paths. This can still be true for previously stored items, even if new - // items are stored using their file URL. See also bug 239948 comment 12. - if (aFilename.startsWith("file:")) { - // Assume the file URL we obtained from the downloads database or from the - // "spec" property of the target has the UTF-8 charset. - let fileUrl = NetUtil.newURI(aFilename).QueryInterface(Ci.nsIFileURL); - return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile); - } else { - // The downloads database contains a native path. Try to create a local - // file, though this may throw an exception if the path is invalid. - return new DownloadsLocalFileCtor(aFilename); - } - }, - - /** - * Open the target file for this download. - * - * @param aOwnerWindow - * The window with which the required action is associated. - * @throws if the file cannot be opened. - */ - openLocalFile: function DDI_openLocalFile(aOwnerWindow) { - this._download.launch().then(null, Cu.reportError); - return; - }, - - /** - * Show the downloaded file in the system file manager. - */ - showLocalFile: function DDI_showLocalFile() { - DownloadsCommon.showDownloadedFile(this.localFile); - }, - - /** - * Resumes the download if paused, pauses it if active. - * @throws if the download is not resumable or if has already done. - */ - togglePauseResume: function DDI_togglePauseResume() { - if (this._download.stopped) { - this._download.start(); - } else { - this._download.cancel(); - } - return; - }, - - /** - * Attempts to retry the download. - * @throws if we cannot. - */ - retry: function DDI_retry() { - this._download.start(); - return; + return new FileUtils.File(this.download.target.path + kPartialDownloadSuffix); }, /** @@ -1705,29 +1620,6 @@ DownloadsDataItem.prototype = { localFile.remove(false); } } catch (ex) { } - }, - - /** - * Cancels the download. - * @throws if the download is already done. - */ - cancel: function() { - this._download.cancel(); - this._download.removePartialData().then(null, Cu.reportError); - return; - }, - - /** - * Remove the download. - */ - remove: function DDI_remove() { - let promiseList = this._download.source.isPrivate - ? Downloads.getList(Downloads.PUBLIC) - : Downloads.getList(Downloads.PRIVATE); - promiseList.then(list => list.remove(this._download)) - .then(() => this._download.finalize(true)) - .then(null, Cu.reportError); - return; } }; 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 diff --git a/application/palemoon/components/downloads/content/downloads.js b/application/palemoon/components/downloads/content/downloads.js index 44457e711..fb63f4b17 100644 --- a/application/palemoon/components/downloads/content/downloads.js +++ b/application/palemoon/components/downloads/content/downloads.js @@ -1071,12 +1071,14 @@ function DownloadsViewItem(aDataItem, aElement) // as bug 239948 comment 12 is handled, the "file" property will be always a // file URL rather than a file name. At that point we should remove the "//" // (double slash) from the icon URI specification (see test_moz_icon_uri.js). - this.image = "moz-icon://" + this.dataItem.file + "?size=32"; + this.image = "moz-icon://" + this.dataItem.download.target.path + "?size=32"; let s = DownloadsCommon.strings; let [displayHost, fullHost] = - DownloadUtils.getURIHost(this.dataItem.referrer || this.dataItem.uri); + DownloadUtils.getURIHost(this.dataItem.download.source.referrer || + this.dataItem.download.source.url); + let target = OS.Path.basename(this.dataItem.download.target.path); let attributes = { "type": "download", "class": "download-state", @@ -1084,9 +1086,9 @@ function DownloadsViewItem(aDataItem, aElement) "downloadGuid": this.dataItem.downloadGuid, "state": this.dataItem.state, "progress": this.dataItem.inProgress ? this.dataItem.percentComplete : 100, - "displayName": this.dataItem.target, - "extendedDisplayName": s.statusSeparator(this.dataItem.target, displayHost), - "extendedDisplayNameTip": s.statusSeparator(this.dataItem.target, fullHost), + "displayName": target, + "extendedDisplayName": s.statusSeparator(target, displayHost), + "extendedDisplayNameTip": s.statusSeparator(target, fullHost), "image": this.image }; @@ -1203,7 +1205,7 @@ DownloadsViewItem.prototype = { let statusTip = ""; if (this.dataItem.paused) { - let transfer = DownloadUtils.getTransferTotal(this.dataItem.currBytes, + let transfer = DownloadUtils.getTransferTotal(this.dataItem.download.currentBytes, this.dataItem.maxBytes); // We use the same XUL label to display both the state and the amount @@ -1216,17 +1218,17 @@ DownloadsViewItem.prototype = { // The remaining time per download is likely enough information for the // panel. [status] = - DownloadUtils.getDownloadStatusNoRate(this.dataItem.currBytes, + DownloadUtils.getDownloadStatusNoRate(this.dataItem.download.currentBytes, this.dataItem.maxBytes, - this.dataItem.speed, + this.dataItem.download.speed, this.lastEstimatedSecondsLeft); // We are, however, OK with displaying the rate in the tooltip. let newEstimatedSecondsLeft; [statusTip, newEstimatedSecondsLeft] = - DownloadUtils.getDownloadStatus(this.dataItem.currBytes, + DownloadUtils.getDownloadStatus(this.dataItem.download.currentBytes, this.dataItem.maxBytes, - this.dataItem.speed, + this.dataItem.download.speed, this.lastEstimatedSecondsLeft); this.lastEstimatedSecondsLeft = newEstimatedSecondsLeft; } else if (this.dataItem.starting) { @@ -1248,7 +1250,8 @@ DownloadsViewItem.prototype = { }.apply(this); let [displayHost, fullHost] = - DownloadUtils.getURIHost(this.dataItem.referrer || this.dataItem.uri); + DownloadUtils.getURIHost(this.dataItem.download.source.referrer || + this.dataItem.download.source.url); let end = new Date(this.dataItem.endTime); let [displayDate, fullDate] = DownloadUtils.getReadableDates(end); @@ -1294,18 +1297,17 @@ DownloadsViewItem.prototype = { */ verifyTargetExists: function DVI_verifyTargetExists() { // We don't need to check if the download is not finished successfully. - if (!this.dataItem.openable) { + if (!this.dataItem.download.succeeded) { return; } - OS.File.exists(this.dataItem.localFile.path).then( - function DVI_RTE_onSuccess(aExists) { - if (aExists) { - this._element.setAttribute("exists", "true"); - } else { - this._element.removeAttribute("exists"); - } - }.bind(this), Cu.reportError); + OS.File.exists(this.dataItem.download.target.path).then(aExists => { + if (aExists) { + this._element.setAttribute("exists", "true"); + } else { + this._element.removeAttribute("exists"); + } + }).catch(Cu.reportError); }, }; @@ -1434,18 +1436,20 @@ DownloadsViewItemController.prototype = { { switch (aCommand) { case "downloadsCmd_open": { - return this.dataItem.openable && this.dataItem.localFile.exists(); + return this.dataItem.download.succeeded && + this.dataItem.localFile.exists(); } case "downloadsCmd_show": { return this.dataItem.localFile.exists() || this.dataItem.partFile.exists(); } case "downloadsCmd_pauseResume": - return this.dataItem.inProgress && this.dataItem.resumable; + return this.dataItem.inProgress && + this.dataItem.download.hasPartialData; case "downloadsCmd_retry": return this.dataItem.canRetry; case "downloadsCmd_openReferrer": - return !!this.dataItem.referrer; + return !!this.dataItem.download.source.referrer; case "cmd_delete": case "downloadsCmd_cancel": case "downloadsCmd_copyLocation": @@ -1474,20 +1478,22 @@ DownloadsViewItemController.prototype = { cmd_delete: function DVIC_cmd_delete() { Downloads.getList(Downloads.ALL) - .then(list => list.remove(this.dataItem._download)) - .then(() => this.dataItem._download.finalize(true)) + .then(list => list.remove(this.dataItem.download)) + .then(() => this.dataItem.download.finalize(true)) .catch(Cu.reportError); - PlacesUtils.bhistory.removePage(NetUtil.newURI(this.dataItem.uri)); + PlacesUtils.bhistory.removePage( + NetUtil.newURI(this.dataItem.download.source.url)); }, downloadsCmd_cancel: function DVIC_downloadsCmd_cancel() { - this.dataItem.cancel(); + this.dataItem.download.cancel().catch(() => {}); + this.dataItem.download.removePartialData().catch(Cu.reportError); }, downloadsCmd_open: function DVIC_downloadsCmd_open() { - this.dataItem.openLocalFile(window); + this.dataItem.download.launch().catch(Cu.reportError); // We explicitly close the panel here to give the user the feedback that // their click has been received, and we're handling the action. // Otherwise, we'd have to wait for the file-type handler to execute @@ -1498,7 +1504,7 @@ DownloadsViewItemController.prototype = { downloadsCmd_show: function DVIC_downloadsCmd_show() { - this.dataItem.showLocalFile(); + DownloadsCommon.showDownloadedFile(this.dataItem.localFile); // We explicitly close the panel here to give the user the feedback that // their click has been received, and we're handling the action. @@ -1510,24 +1516,28 @@ DownloadsViewItemController.prototype = { downloadsCmd_pauseResume: function DVIC_downloadsCmd_pauseResume() { - this.dataItem.togglePauseResume(); + if (this.dataItem.download.stopped) { + this.dataItem.download.start(); + } else { + this.dataItem.download.cancel(); + } }, downloadsCmd_retry: function DVIC_downloadsCmd_retry() { - this.dataItem.retry(); + this.dataItem.download.start().catch(() => {}); }, downloadsCmd_openReferrer: function DVIC_downloadsCmd_openReferrer() { - openURL(this.dataItem.referrer); + openURL(this.dataItem.download.source.referrer); }, downloadsCmd_copyLocation: function DVIC_downloadsCmd_copyLocation() { let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"] .getService(Ci.nsIClipboardHelper); - clipboard.copyString(this.dataItem.uri, document); + clipboard.copyString(this.dataItem.download.source.url, document); }, downloadsCmd_doDefault: function DVIC_downloadsCmd_doDefault() -- 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 --- .../components/downloads/DownloadsCommon.jsm | 8 +- .../downloads/content/allDownloadsViewOverlay.js | 797 ++++++++++----------- .../components/downloads/content/downloads.js | 10 +- 3 files changed, 393 insertions(+), 422 deletions(-) (limited to 'application/palemoon/components') diff --git a/application/palemoon/components/downloads/DownloadsCommon.jsm b/application/palemoon/components/downloads/DownloadsCommon.jsm index 42864840b..2bfd7b528 100644 --- a/application/palemoon/components/downloads/DownloadsCommon.jsm +++ b/application/palemoon/components/downloads/DownloadsCommon.jsm @@ -8,6 +8,7 @@ this.EXPORTED_SYMBOLS = [ "DownloadsCommon", + "DownloadsDataItem", ]; /** @@ -25,10 +26,9 @@ this.EXPORTED_SYMBOLS = [ * to build a consistent view of the available data. * * DownloadsDataItem - * Represents a single item in the list of downloads. This object either wraps - * an existing nsIDownload from the Download Manager, or provides the same - * information read directly from the downloads database, with the possibility - * of querying the nsIDownload lazily, for performance reasons. + * Represents a single item in the list of downloads. This object wraps the + * Download object from the JavaScript API for downloads. A specialized version + * of this object is implemented in the Places front-end view. * * DownloadsIndicatorData * This object registers itself with DownloadsData as a view, and transforms the 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; diff --git a/application/palemoon/components/downloads/content/downloads.js b/application/palemoon/components/downloads/content/downloads.js index fb63f4b17..af0c85535 100644 --- a/application/palemoon/components/downloads/content/downloads.js +++ b/application/palemoon/components/downloads/content/downloads.js @@ -880,10 +880,10 @@ const DownloadsView = { }, // DownloadsView - onDataItemStateChanged(aDataItem, aOldState) { + onDataItemStateChanged(aDataItem) { let viewItem = this._visibleViewItems.get(aDataItem); if (viewItem) { - viewItem.onStateChanged(aOldState); + viewItem.onStateChanged(); } }, @@ -1126,16 +1126,14 @@ DownloadsViewItem.prototype = { * the download might be the same as before, if the data layer received * multiple events for the same download. */ - onStateChanged(aOldState) { - { + 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 (aOldState != Ci.nsIDownloadManager.DOWNLOAD_FINISHED && - aOldState != this.dataItem.state) { + if (this.dataItem.state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) { this._element.setAttribute("image", this.image + "&state=normal"); // We assume the existence of the target of a download that just completed -- cgit v1.2.3 From 927853bde5ac254ce255a46b15266fbcf6046b09 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Jul 2018 09:59:35 +0200 Subject: [PALEMOON] Bug 1115379 - Streamline DownloadsViewItemController construction and remove now unneeded identifiers --- .../components/downloads/DownloadsCommon.jsm | 36 +++++----------------- .../components/downloads/content/downloads.js | 32 ++++++++++++------- 2 files changed, 29 insertions(+), 39 deletions(-) (limited to 'application/palemoon/components') diff --git a/application/palemoon/components/downloads/DownloadsCommon.jsm b/application/palemoon/components/downloads/DownloadsCommon.jsm index 2bfd7b528..aac43d197 100644 --- a/application/palemoon/components/downloads/DownloadsCommon.jsm +++ b/application/palemoon/components/downloads/DownloadsCommon.jsm @@ -647,12 +647,8 @@ XPCOMUtils.defineLazyGetter(DownloadsCommon, "useJSTransfer", function () { function DownloadsDataCtor(aPrivate) { this._isPrivate = aPrivate; - // This Object contains all the available DownloadsDataItem objects, indexed by - // their globally unique identifier. The identifiers of downloads that have - // been removed from the Download Manager data are still present, however the - // associated objects are replaced with the value "null". This is required to - // prevent race conditions when populating the list asynchronously. - this.dataItems = {}; + // Contains all the available DownloadsDataItem objects. + this.dataItems = new Set(); // Array of view objects that should be notified when the available download // data changes. @@ -690,8 +686,8 @@ DownloadsDataCtor.prototype = { */ get canRemoveFinished() { - for (let [, dataItem] of Iterator(this.dataItems)) { - if (dataItem && !dataItem.inProgress) { + for (let dataItem of this.dataItems) { + if (!dataItem.inProgress) { return true; } } @@ -716,7 +712,7 @@ DownloadsDataCtor.prototype = { { let dataItem = new DownloadsDataItem(aDownload); this._downloadToDataItemMap.set(aDownload, dataItem); - this.dataItems[dataItem.downloadGuid] = dataItem; + this.dataItems.add(dataItem); for (let view of this._views) { view.onDataItemAdded(dataItem, true); @@ -745,7 +741,7 @@ DownloadsDataCtor.prototype = { } this._downloadToDataItemMap.delete(aDownload); - this.dataItems[dataItem.downloadGuid] = null; + this.dataItems.delete(dataItem); for (let view of this._views) { view.onDataItemRemoved(dataItem); } @@ -861,14 +857,7 @@ DownloadsDataCtor.prototype = { //let loadedItemsArray = [dataItem // for each (dataItem in this.dataItems) // if (dataItem)]; - - let loadedItemsArray = []; - - for each (let dataItem in this.dataItems) { - if (dataItem) { - loadedItemsArray.push(dataItem); - } - } + let loadedItemsArray = [...this.dataItems]; loadedItemsArray.sort(function(a, b) b.download.startTime - a.download.startTime); loadedItemsArray.forEach( @@ -1344,13 +1333,6 @@ function DownloadsDataItem(aSource) } DownloadsDataItem.prototype = { - /** - * The JavaScript API does not need identifiers for Download objects, so they - * are generated sequentially for the corresponding DownloadDataItem. - */ - get _autoIncrementId() ++DownloadsDataItem.prototype.__lastId, - __lastId: 0, - /** * Initializes this object from the JavaScript API for downloads. * @@ -1362,8 +1344,6 @@ DownloadsDataItem.prototype = { _initFromJSDownload: function (aDownload) { this.download = aDownload; - - this.downloadGuid = "id:" + this._autoIncrementId; this.endTime = Date.now(); this.updateFromJSDownload(); @@ -2029,7 +2009,7 @@ DownloadsIndicatorDataCtor.prototype = { { let dataItems = this._isPrivate ? PrivateDownloadsData.dataItems : DownloadsData.dataItems; - for each (let dataItem in dataItems) { + for (let dataItem of dataItems) { if (dataItem && dataItem.inProgress) { yield dataItem; } diff --git a/application/palemoon/components/downloads/content/downloads.js b/application/palemoon/components/downloads/content/downloads.js index af0c85535..a6c716b8c 100644 --- a/application/palemoon/components/downloads/content/downloads.js +++ b/application/palemoon/components/downloads/content/downloads.js @@ -895,6 +895,16 @@ const DownloadsView = { } }, + /** + * Associates each richlistitem for a download with its corresponding + * DownloadsViewItemController object. + */ + _controllersForElements: new Map(), + + controllerForElement(element) { + return this._controllersForElements.get(element); + }, + /** * Creates a new view item associated with the specified data item, and adds * it to the top or the bottom of the list. @@ -907,6 +917,8 @@ const DownloadsView = { let element = document.createElement("richlistitem"); let viewItem = new DownloadsViewItem(aDataItem, element); this._visibleViewItems.set(aDataItem, viewItem); + let viewItemController = new DownloadsViewItemController(aDataItem); + this._controllersForElements.set(element, viewItemController); if (aNewest) { this.richListBox.insertBefore(element, this.richListBox.firstChild); } else { @@ -928,6 +940,7 @@ const DownloadsView = { this.richListBox.itemCount - 1); } this._visibleViewItems.delete(aDataItem); + this._controllersForElements.delete(element); }, ////////////////////////////////////////////////////////////////////////////// @@ -949,7 +962,7 @@ const DownloadsView = { while (target.nodeName != "richlistitem") { target = target.parentNode; } - new DownloadsViewItemController(target).doCommand(aCommand); + DownloadsView.controllerForElement(target).doCommand(aCommand); }, onDownloadClick: function DV_onDownloadClick(aEvent) @@ -1028,8 +1041,8 @@ const DownloadsView = { return; } - let controller = new DownloadsViewItemController(element); - let localFile = controller.dataItem.localFile; + let localFile = DownloadsView.controllerForElement(element) + .dataItem.localFile; if (!localFile.exists()) { return; } @@ -1082,8 +1095,6 @@ function DownloadsViewItem(aDataItem, aElement) let attributes = { "type": "download", "class": "download-state", - "id": "downloadsItem_" + this.dataItem.downloadGuid, - "downloadGuid": this.dataItem.downloadGuid, "state": this.dataItem.state, "progress": this.dataItem.inProgress ? this.dataItem.percentComplete : 100, "displayName": target, @@ -1360,8 +1371,8 @@ const DownloadsViewController = { // Other commands are selection-specific. let element = DownloadsView.richListBox.selectedItem; - return element && - new DownloadsViewItemController(element).isCommandEnabled(aCommand); + return element && DownloadsView.controllerForElement(element) + .isCommandEnabled(aCommand); }, doCommand: function DVC_doCommand(aCommand) @@ -1376,7 +1387,7 @@ const DownloadsViewController = { let element = DownloadsView.richListBox.selectedItem; if (element) { // The doCommand function also checks if the command is enabled. - new DownloadsViewItemController(element).doCommand(aCommand); + DownloadsView.controllerForElement(element).doCommand(aCommand); } }, @@ -1416,9 +1427,8 @@ XPCOMUtils.defineConstant(this, "DownloadsViewController", DownloadsViewControll * Handles all the user interaction events, in particular the "commands", * related to a single item in the downloads list widgets. */ -function DownloadsViewItemController(aElement) { - let downloadGuid = aElement.getAttribute("downloadGuid"); - this.dataItem = DownloadsCommon.getData(window).dataItems[downloadGuid]; +function DownloadsViewItemController(aDataItem) { + this.dataItem = aDataItem; } DownloadsViewItemController.prototype = { -- 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 +++------------------ .../downloads/content/allDownloadsViewOverlay.xul | 2 + .../components/downloads/content/downloads.js | 220 ++----------------- application/palemoon/components/downloads/jar.mn | 1 + 4 files changed, 46 insertions(+), 416 deletions(-) (limited to 'application/palemoon/components') 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); diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.xul b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.xul index 4e9bfd15b..d8e797ed1 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.xul +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.xul @@ -34,6 +34,8 @@ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +