/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const EXPORTED_SYMBOLS = ["S4EDownloadService"]; const CC = Components.classes; const CI = Components.interfaces; const CU = Components.utils; CU.import("resource://gre/modules/Services.jsm"); CU.import("resource://gre/modules/PluralForm.jsm"); CU.import("resource://gre/modules/DownloadUtils.jsm"); CU.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); CU.import("resource://gre/modules/XPCOMUtils.jsm"); function S4EDownloadService(window, gBrowser, service, getters) { this._window = window; this._gBrowser = gBrowser; this._service = service; this._getters = getters; this._handler = new JSTransferHandler(this._window, this); } S4EDownloadService.prototype = { _window: null, _gBrowser: null, _service: null, _getters: null, _handler: null, _listening: false, _binding: false, _customizing: false, _lastTime: Infinity, _dlActive: false, _dlPaused: false, _dlFinished: false, _dlCountStr: null, _dlTimeStr: null, _dlProgressAvg: 0, _dlProgressMax: 0, _dlProgressMin: 0, _dlProgressType: "active", _dlNotifyTimer: 0, _dlNotifyGlowTimer: 0, init: function() { if(!this._getters.downloadButton) { this.uninit(); return; } if(this._listening) { return; } this._handler.start(); this._listening = true; this._lastTime = Infinity; this.updateBinding(); this.updateStatus(); }, uninit: function() { if(!this._listening) { return; } this._listening = false; this._handler.stop(); this.releaseBinding(); }, destroy: function() { this.uninit(); this._handler.destroy(); ["_window", "_gBrowser", "_service", "_getters", "_handler"].forEach(function(prop) { delete this[prop]; }, this); }, updateBinding: function() { if(!this._listening) { this.releaseBinding(); return; } switch(this._service.downloadButtonAction) { case 1: // Default this.attachBinding(); break; default: this.releaseBinding(); break; } }, attachBinding: function() { if(this._binding) { return; } let db = this._window.DownloadsButton; db._getAnchorS4EBackup = db.getAnchor; db.getAnchor = this.getAnchor.bind(this); db._releaseAnchorS4EBackup = db.releaseAnchor; db.releaseAnchor = function() {}; this._binding = true; }, releaseBinding: function() { if(!this._binding) { return; } let db = this._window.DownloadsButton; db.getAnchor = db._getAnchorS4EBackup; db.releaseAnchor = db._releaseAnchorS4EBackup; this._binding = false; }, customizing: function(val) { this._customizing = val; }, updateStatus: function(lastFinished) { if(!this._getters.downloadButton) { this.uninit(); return; } let numActive = 0; let numPaused = 0; let activeTotalSize = 0; let activeTransferred = 0; let activeMaxProgress = -Infinity; let activeMinProgress = Infinity; let pausedTotalSize = 0; let pausedTransferred = 0; let pausedMaxProgress = -Infinity; let pausedMinProgress = Infinity; let maxTime = -Infinity; let dls = ((this.isPrivateWindow) ? this._handler.activePrivateEntries() : this._handler.activeEntries()); for(let dl of dls) { if(dl.state == CI.nsIDownloadManager.DOWNLOAD_DOWNLOADING) { numActive++; if(dl.size > 0) { if(dl.speed > 0) { maxTime = Math.max(maxTime, (dl.size - dl.transferred) / dl.speed); } activeTotalSize += dl.size; activeTransferred += dl.transferred; let currentProgress = ((dl.transferred * 100) / dl.size); activeMaxProgress = Math.max(activeMaxProgress, currentProgress); activeMinProgress = Math.min(activeMinProgress, currentProgress); } } else if(dl.state == CI.nsIDownloadManager.DOWNLOAD_PAUSED) { numPaused++; if(dl.size > 0) { pausedTotalSize += dl.size; pausedTransferred += dl.transferred; let currentProgress = ((dl.transferred * 100) / dl.size); pausedMaxProgress = Math.max(pausedMaxProgress, currentProgress); pausedMinProgress = Math.min(pausedMinProgress, currentProgress); } } } if((numActive + numPaused) == 0) { this._dlActive = false; this._dlFinished = lastFinished; this.updateButton(); this._lastTime = Infinity; return; } let dlPaused = (numActive == 0); let dlStatus = ((dlPaused) ? this._getters.strings.getString("pausedDownloads") : this._getters.strings.getString("activeDownloads")); let dlCount = ((dlPaused) ? numPaused : numActive); let dlTotalSize = ((dlPaused) ? pausedTotalSize : activeTotalSize); let dlTransferred = ((dlPaused) ? pausedTransferred : activeTransferred); let dlMaxProgress = ((dlPaused) ? pausedMaxProgress : activeMaxProgress); let dlMinProgress = ((dlPaused) ? pausedMinProgress : activeMinProgress); let dlProgressType = ((dlPaused) ? "paused" : "active"); [this._dlTimeStr, this._lastTime] = DownloadUtils.getTimeLeft(maxTime, this._lastTime); this._dlCountStr = PluralForm.get(dlCount, dlStatus).replace("#1", dlCount); this._dlProgressAvg = ((dlTotalSize == 0) ? 100 : ((dlTransferred * 100) / dlTotalSize)); this._dlProgressMax = ((dlTotalSize == 0) ? 100 : dlMaxProgress); this._dlProgressMin = ((dlTotalSize == 0) ? 100 : dlMinProgress); this._dlProgressType = dlProgressType + ((dlTotalSize == 0) ? "-unknown" : ""); this._dlPaused = dlPaused; this._dlActive = true; this._dlFinished = false; this.updateButton(); }, updateButton: function() { let download_button = this._getters.downloadButton; let download_tooltip = this._getters.downloadButtonTooltip; let download_progress = this._getters.downloadButtonProgress; let download_label = this._getters.downloadButtonLabel; if(!download_button) { return; } if(!this._dlActive) { download_button.collapsed = true; download_label.value = download_tooltip.label = this._getters.strings.getString("noDownloads"); download_progress.collapsed = true; download_progress.value = 0; if(this._dlFinished && this._handler.hasPBAPI && !this.isUIShowing) { this.callAttention(download_button); } return; } switch(this._service.downloadProgress) { case 2: download_progress.value = this._dlProgressMax; break; case 3: download_progress.value = this._dlProgressMin; break; default: download_progress.value = this._dlProgressAvg; break; } download_progress.setAttribute("pmType", this._dlProgressType); download_progress.collapsed = (this._service.downloadProgress == 0); download_label.value = this.buildString(this._service.downloadLabel); download_tooltip.label = this.buildString(this._service.downloadTooltip); this.clearAttention(download_button); download_button.collapsed = false; }, callAttention: function(download_button) { if(this._dlNotifyGlowTimer != 0) { this._window.clearTimeout(this._dlNotifyGlowTimer); this._dlNotifyGlowTimer = 0; } download_button.setAttribute("attention", "true"); if(this._service.downloadNotifyTimeout) { this._dlNotifyGlowTimer = this._window.setTimeout(function(self, button) { self._dlNotifyGlowTimer = 0; button.removeAttribute("attention"); }, this._service.downloadNotifyTimeout, this, download_button); } }, clearAttention: function(download_button) { if(this._dlNotifyGlowTimer != 0) { this._window.clearTimeout(this._dlNotifyGlowTimer); this._dlNotifyGlowTimer = 0; } download_button.removeAttribute("attention"); }, notify: function() { if(this._dlNotifyTimer == 0 && this._service.downloadNotifyAnimate) { let download_button_anchor = this._getters.downloadButtonAnchor; let download_notify_anchor = this._getters.downloadNotifyAnchor; if(download_button_anchor) { if(!download_notify_anchor.style.transform) { let bAnchorRect = download_button_anchor.getBoundingClientRect(); let nAnchorRect = download_notify_anchor.getBoundingClientRect(); let translateX = bAnchorRect.left - nAnchorRect.left; translateX += .5 * (bAnchorRect.width - nAnchorRect.width); let translateY = bAnchorRect.top - nAnchorRect.top; translateY += .5 * (bAnchorRect.height - nAnchorRect.height); download_notify_anchor.style.transform = "translate(" + translateX + "px, " + translateY + "px)"; } download_notify_anchor.setAttribute("notification", "finish"); this._dlNotifyTimer = this._window.setTimeout(function(self, anchor) { self._dlNotifyTimer = 0; anchor.removeAttribute("notification"); anchor.style.transform = ""; }, 1000, this, download_notify_anchor); } } }, clearFinished: function() { this._dlFinished = false; let download_button = this._getters.downloadButton; if(download_button) { this.clearAttention(download_button); } }, getAnchor: function(aCallback) { if(this._customizing) { aCallback(null); return; } aCallback(this._getters.downloadButtonAnchor); }, openUI: function(aEvent) { this.clearFinished(); switch(this._service.downloadButtonAction) { case 1: // Firefox Default this._handler.openUINative(); break; case 2: // Show Library this._window.PlacesCommandHook.showPlacesOrganizer("Downloads"); break; case 3: // Show Tab let found = this._gBrowser.browsers.some(function(browser, index) { if("about:downloads" == browser.currentURI.spec) { this._gBrowser.selectedTab = this._gBrowser.tabContainer.childNodes[index]; return true; } }, this); if(!found) { this._window.openUILinkIn("about:downloads", "tab"); } break; case 4: // External Command let command = this._service.downloadButtonActionCommand; if(commend) { this._window.goDoCommand(command); } break; default: // Nothing break; } aEvent.stopPropagation(); }, get isPrivateWindow() { return this._handler.hasPBAPI && PrivateBrowsingUtils.isWindowPrivate(this._window); }, get isUIShowing() { switch(this._service.downloadButtonAction) { case 1: // Firefox Default return this._handler.isUIShowingNative; case 2: // Show Library var organizer = Services.wm.getMostRecentWindow("Places:Organizer"); if(organizer) { let selectedNode = organizer.PlacesOrganizer._places.selectedNode; let downloadsItemId = organizer.PlacesUIUtils.leftPaneQueries["Downloads"]; return selectedNode && selectedNode.itemId === downloadsItemId; } return false; case 3: // Show tab let currentURI = this._gBrowser.currentURI; return currentURI && currentURI.spec == "about:downloads"; default: // Nothing return false; } }, buildString: function(mode) { switch(mode) { case 0: return this._dlCountStr; case 1: return ((this._dlPaused) ? this._dlCountStr : this._dlTimeStr); default: let compStr = this._dlCountStr; if(!this._dlPaused) { compStr += " (" + this._dlTimeStr + ")"; } return compStr; } } }; function JSTransferHandler(window, downloadService) { this._window = window; let api = CU.import("resource://gre/modules/Downloads.jsm", {}).Downloads; this._activePublic = new JSTransferListener(downloadService, api.getList(api.PUBLIC), false); this._activePrivate = new JSTransferListener(downloadService, api.getList(api.PRIVATE), true); } JSTransferHandler.prototype = { _window: null, _activePublic: null, _activePrivate: null, destroy: function() { this._activePublic.destroy(); this._activePrivate.destroy(); ["_window", "_activePublic", "_activePrivate"].forEach(function(prop) { delete this[prop]; }, this); }, start: function() { this._activePublic.start(); this._activePrivate.start(); }, stop: function() { this._activePublic.stop(); this._activePrivate.stop(); }, get hasPBAPI() { return true; }, openUINative: function() { this._window.DownloadsPanel.showPanel(); }, get isUIShowingNative() { return this._window.DownloadsPanel.isPanelShowing; }, activeEntries: function() { return this._activePublic.downloads(); }, activePrivateEntries: function() { return this._activePrivate.downloads(); } }; function JSTransferListener(downloadService, listPromise, isPrivate) { this._downloadService = downloadService; this._isPrivate = isPrivate; this._downloads = new Map(); listPromise.then(this.initList.bind(this)).then(null, CU.reportError); } JSTransferListener.prototype = { _downloadService: null, _list: null, _downloads: null, _isPrivate: false, _wantsStart: false, initList: function(list) { this._list = list; if(this._wantsStart) { this.start(); } this._list.getAll().then(this.initDownloads.bind(this)).then(null, CU.reportError); }, initDownloads: function(downloads) { downloads.forEach(function(download) { this.onDownloadAdded(download); }, this); }, destroy: function() { this._downloads.clear(); ["_downloadService", "_list", "_downloads"].forEach(function(prop) { delete this[prop]; }, this); }, start: function() { if(!this._list) { this._wantsStart = true; return; } this._list.addView(this); }, stop: function() { if(!this._list) { this._wantsStart = false; return; } this._list.removeView(this); }, downloads: function() { return this._downloads.values(); }, convertToState: function(dl) { if(dl.succeeded) { return CI.nsIDownloadManager.DOWNLOAD_FINISHED; } if(dl.error && dl.error.becauseBlockedByParentalControls) { return CI.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL; } if(dl.error) { return CI.nsIDownloadManager.DOWNLOAD_FAILED; } if(dl.canceled && dl.hasPartialData) { return CI.nsIDownloadManager.DOWNLOAD_PAUSED; } if(dl.canceled) { return CI.nsIDownloadManager.DOWNLOAD_CANCELED; } if(dl.stopped) { return CI.nsIDownloadManager.DOWNLOAD_NOTSTARTED; } return CI.nsIDownloadManager.DOWNLOAD_DOWNLOADING; }, onDownloadAdded: function(aDownload) { let dl = this._downloads.get(aDownload); if(!dl) { dl = {}; this._downloads.set(aDownload, dl); } dl.state = this.convertToState(aDownload); dl.size = aDownload.totalBytes; dl.speed = aDownload.speed; dl.transferred = aDownload.currentBytes; }, onDownloadChanged: function(aDownload) { this.onDownloadAdded(aDownload); if(this._isPrivate != this._downloadService.isPrivateWindow) { return; } this._downloadService.updateStatus(aDownload.succeeded); if(aDownload.succeeded) { this._downloadService.notify() } }, onDownloadRemoved: function(aDownload) { this._downloads.delete(aDownload); } };