michael@0: /* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * vim: sw=2 ts=2 sts=2 et filetype=javascript michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: this.EXPORTED_SYMBOLS = [ michael@0: "DownloadTaskbarProgress", michael@0: ]; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Constants michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: const Cr = Components.results; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Services", michael@0: "resource://gre/modules/Services.jsm"); michael@0: michael@0: const kTaskbarIDWin = "@mozilla.org/windows-taskbar;1"; michael@0: const kTaskbarIDMac = "@mozilla.org/widget/macdocksupport;1"; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// DownloadTaskbarProgress Object michael@0: michael@0: this.DownloadTaskbarProgress = michael@0: { michael@0: init: function DTP_init() michael@0: { michael@0: if (DownloadTaskbarProgressUpdater) { michael@0: DownloadTaskbarProgressUpdater._init(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Called when a browser window appears. This has an effect only when we michael@0: * don't already have an active window. michael@0: * michael@0: * @param aWindow michael@0: * The browser window that we'll potentially use to display the michael@0: * progress. michael@0: */ michael@0: onBrowserWindowLoad: function DTP_onBrowserWindowLoad(aWindow) michael@0: { michael@0: this.init(); michael@0: if (!DownloadTaskbarProgressUpdater) { michael@0: return; michael@0: } michael@0: if (!DownloadTaskbarProgressUpdater._activeTaskbarProgress) { michael@0: DownloadTaskbarProgressUpdater._setActiveWindow(aWindow, false); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Called when the download window appears. The download window will take michael@0: * over as the active window. michael@0: */ michael@0: onDownloadWindowLoad: function DTP_onDownloadWindowLoad(aWindow) michael@0: { michael@0: if (!DownloadTaskbarProgressUpdater) { michael@0: return; michael@0: } michael@0: DownloadTaskbarProgressUpdater._setActiveWindow(aWindow, true); michael@0: }, michael@0: michael@0: /** michael@0: * Getters for internal DownloadTaskbarProgressUpdater values michael@0: */ michael@0: michael@0: get activeTaskbarProgress() { michael@0: if (!DownloadTaskbarProgressUpdater) { michael@0: return null; michael@0: } michael@0: return DownloadTaskbarProgressUpdater._activeTaskbarProgress; michael@0: }, michael@0: michael@0: get activeWindowIsDownloadWindow() { michael@0: if (!DownloadTaskbarProgressUpdater) { michael@0: return null; michael@0: } michael@0: return DownloadTaskbarProgressUpdater._activeWindowIsDownloadWindow; michael@0: }, michael@0: michael@0: get taskbarState() { michael@0: if (!DownloadTaskbarProgressUpdater) { michael@0: return null; michael@0: } michael@0: return DownloadTaskbarProgressUpdater._taskbarState; michael@0: }, michael@0: michael@0: }; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// DownloadTaskbarProgressUpdater Object michael@0: michael@0: var DownloadTaskbarProgressUpdater = michael@0: { michael@0: /// Whether the taskbar is initialized. michael@0: _initialized: false, michael@0: michael@0: /// Reference to the taskbar. michael@0: _taskbar: null, michael@0: michael@0: /// Reference to the download manager. michael@0: _dm: null, michael@0: michael@0: /** michael@0: * Initialize and register ourselves as a download progress listener. michael@0: */ michael@0: _init: function DTPU_init() michael@0: { michael@0: if (this._initialized) { michael@0: return; // Already initialized michael@0: } michael@0: this._initialized = true; michael@0: michael@0: if (kTaskbarIDWin in Cc) { michael@0: this._taskbar = Cc[kTaskbarIDWin].getService(Ci.nsIWinTaskbar); michael@0: if (!this._taskbar.available) { michael@0: // The Windows version is probably too old michael@0: DownloadTaskbarProgressUpdater = null; michael@0: return; michael@0: } michael@0: } else if (kTaskbarIDMac in Cc) { michael@0: this._activeTaskbarProgress = Cc[kTaskbarIDMac]. michael@0: getService(Ci.nsITaskbarProgress); michael@0: } else { michael@0: DownloadTaskbarProgressUpdater = null; michael@0: return; michael@0: } michael@0: michael@0: this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS; michael@0: michael@0: this._dm = Cc["@mozilla.org/download-manager;1"]. michael@0: getService(Ci.nsIDownloadManager); michael@0: this._dm.addPrivacyAwareListener(this); michael@0: michael@0: this._os = Cc["@mozilla.org/observer-service;1"]. michael@0: getService(Ci.nsIObserverService); michael@0: this._os.addObserver(this, "quit-application-granted", false); michael@0: michael@0: this._updateStatus(); michael@0: // onBrowserWindowLoad/onDownloadWindowLoad are going to set the active michael@0: // window, so don't do it here. michael@0: }, michael@0: michael@0: /** michael@0: * Unregisters ourselves as a download progress listener. michael@0: */ michael@0: _uninit: function DTPU_uninit() { michael@0: this._dm.removeListener(this); michael@0: this._os.removeObserver(this, "quit-application-granted"); michael@0: this._activeTaskbarProgress = null; michael@0: this._initialized = false; michael@0: }, michael@0: michael@0: /** michael@0: * This holds a reference to the taskbar progress for the window we're michael@0: * working with. This window would preferably be download window, but can be michael@0: * another window if it isn't open. michael@0: */ michael@0: _activeTaskbarProgress: null, michael@0: michael@0: /// Whether the active window is the download window michael@0: _activeWindowIsDownloadWindow: false, michael@0: michael@0: /** michael@0: * Sets the active window, and whether it's the download window. This takes michael@0: * care of clearing out the previous active window's taskbar item, updating michael@0: * the taskbar, and setting an onunload listener. michael@0: * michael@0: * @param aWindow michael@0: * The window to set as active. michael@0: * @param aIsDownloadWindow michael@0: * Whether this window is a download window. michael@0: */ michael@0: _setActiveWindow: function DTPU_setActiveWindow(aWindow, aIsDownloadWindow) michael@0: { michael@0: #ifdef XP_WIN michael@0: // Clear out the taskbar for the old active window. (If there was no active michael@0: // window, this is a no-op.) michael@0: this._clearTaskbar(); michael@0: michael@0: this._activeWindowIsDownloadWindow = aIsDownloadWindow; michael@0: if (aWindow) { michael@0: // Get the taskbar progress for this window michael@0: let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIWebNavigation). michael@0: QueryInterface(Ci.nsIDocShellTreeItem).treeOwner. michael@0: QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIXULWindow).docShell; michael@0: let taskbarProgress = this._taskbar.getTaskbarProgress(docShell); michael@0: this._activeTaskbarProgress = taskbarProgress; michael@0: michael@0: this._updateTaskbar(); michael@0: // _onActiveWindowUnload is idempotent, so we don't need to check whether michael@0: // we've already set this before or not. michael@0: aWindow.addEventListener("unload", function () { michael@0: DownloadTaskbarProgressUpdater._onActiveWindowUnload(taskbarProgress); michael@0: }, false); michael@0: } michael@0: else { michael@0: this._activeTaskbarProgress = null; michael@0: } michael@0: #endif michael@0: }, michael@0: michael@0: /// Current state displayed on the active window's taskbar item michael@0: _taskbarState: null, michael@0: _totalSize: 0, michael@0: _totalTransferred: 0, michael@0: michael@0: _shouldSetState: function DTPU_shouldSetState() michael@0: { michael@0: #ifdef XP_WIN michael@0: // If the active window is not the download manager window, set the state michael@0: // only if it is normal or indeterminate. michael@0: return this._activeWindowIsDownloadWindow || michael@0: (this._taskbarState == Ci.nsITaskbarProgress.STATE_NORMAL || michael@0: this._taskbarState == Ci.nsITaskbarProgress.STATE_INDETERMINATE); michael@0: #else michael@0: return true; michael@0: #endif michael@0: }, michael@0: michael@0: /** michael@0: * Update the active window's taskbar indicator with the current state. There michael@0: * are two cases here: michael@0: * 1. If the active window is the download window, then we always update michael@0: * the taskbar indicator. michael@0: * 2. If the active window isn't the download window, then we update only if michael@0: * the status is normal or indeterminate. i.e. one or more downloads are michael@0: * currently progressing or in scan mode. If we aren't, then we clear the michael@0: * indicator. michael@0: */ michael@0: _updateTaskbar: function DTPU_updateTaskbar() michael@0: { michael@0: if (!this._activeTaskbarProgress) { michael@0: return; michael@0: } michael@0: michael@0: if (this._shouldSetState()) { michael@0: this._activeTaskbarProgress.setProgressState(this._taskbarState, michael@0: this._totalTransferred, michael@0: this._totalSize); michael@0: } michael@0: // Clear any state otherwise michael@0: else { michael@0: this._clearTaskbar(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Clear taskbar state. This is needed: michael@0: * - to transfer the indicator off a window before transferring it onto michael@0: * another one michael@0: * - whenever we don't want to show it for a non-download window. michael@0: */ michael@0: _clearTaskbar: function DTPU_clearTaskbar() michael@0: { michael@0: if (this._activeTaskbarProgress) { michael@0: this._activeTaskbarProgress.setProgressState( michael@0: Ci.nsITaskbarProgress.STATE_NO_PROGRESS michael@0: ); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Update this._taskbarState, this._totalSize and this._totalTransferred. michael@0: * This is called when the download manager is initialized or when the michael@0: * progress or state of a download changes. michael@0: * We compute the number of active and paused downloads, and the total size michael@0: * and total amount already transferred across whichever downloads we have michael@0: * the data for. michael@0: * - If there are no active downloads, then we don't want to show any michael@0: * progress. michael@0: * - If the number of active downloads is equal to the number of paused michael@0: * downloads, then we show a paused indicator if we know the size of at michael@0: * least one download, and no indicator if we don't. michael@0: * - If the number of active downloads is more than the number of paused michael@0: * downloads, then we show a "normal" indicator if we know the size of at michael@0: * least one download, and an indeterminate indicator if we don't. michael@0: */ michael@0: _updateStatus: function DTPU_updateStatus() michael@0: { michael@0: let numActive = this._dm.activeDownloadCount + this._dm.activePrivateDownloadCount; michael@0: let totalSize = 0, totalTransferred = 0; michael@0: michael@0: if (numActive == 0) { michael@0: this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS; michael@0: } michael@0: else { michael@0: let numPaused = 0, numScanning = 0; michael@0: michael@0: // Enumerate all active downloads michael@0: [this._dm.activeDownloads, this._dm.activePrivateDownloads].forEach(function(downloads) { michael@0: while (downloads.hasMoreElements()) { michael@0: let download = downloads.getNext().QueryInterface(Ci.nsIDownload); michael@0: // Only set values if we actually know the download size michael@0: if (download.percentComplete != -1) { michael@0: totalSize += download.size; michael@0: totalTransferred += download.amountTransferred; michael@0: } michael@0: // We might need to display a paused state, so track this michael@0: if (download.state == this._dm.DOWNLOAD_PAUSED) { michael@0: numPaused++; michael@0: } else if (download.state == this._dm.DOWNLOAD_SCANNING) { michael@0: numScanning++; michael@0: } michael@0: } michael@0: }.bind(this)); michael@0: michael@0: // If all downloads are paused, show the progress as paused, unless we michael@0: // don't have any information about sizes, in which case we don't michael@0: // display anything michael@0: if (numActive == numPaused) { michael@0: if (totalSize == 0) { michael@0: this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS; michael@0: totalTransferred = 0; michael@0: } michael@0: else { michael@0: this._taskbarState = Ci.nsITaskbarProgress.STATE_PAUSED; michael@0: } michael@0: } michael@0: // If at least one download is not paused, and we don't have any michael@0: // information about download sizes, display an indeterminate indicator michael@0: else if (totalSize == 0 || numActive == numScanning) { michael@0: this._taskbarState = Ci.nsITaskbarProgress.STATE_INDETERMINATE; michael@0: totalSize = 0; michael@0: totalTransferred = 0; michael@0: } michael@0: // Otherwise display a normal progress bar michael@0: else { michael@0: this._taskbarState = Ci.nsITaskbarProgress.STATE_NORMAL; michael@0: } michael@0: } michael@0: michael@0: this._totalSize = totalSize; michael@0: this._totalTransferred = totalTransferred; michael@0: }, michael@0: michael@0: /** michael@0: * Called when a window that at one point has been an active window is michael@0: * closed. If this window is currently the active window, we need to look for michael@0: * another window and make that our active window. michael@0: * michael@0: * This function is idempotent, so multiple calls for the same window are not michael@0: * a problem. michael@0: * michael@0: * @param aTaskbarProgress michael@0: * The taskbar progress for the window that is being unloaded. michael@0: */ michael@0: _onActiveWindowUnload: function DTPU_onActiveWindowUnload(aTaskbarProgress) michael@0: { michael@0: if (this._activeTaskbarProgress == aTaskbarProgress) { michael@0: let windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]. michael@0: getService(Ci.nsIWindowMediator); michael@0: let windows = windowMediator.getEnumerator(null); michael@0: let newActiveWindow = null; michael@0: if (windows.hasMoreElements()) { michael@0: newActiveWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow); michael@0: } michael@0: michael@0: // We aren't ever going to reach this point while the download manager is michael@0: // open, so it's safe to assume false for the second operand michael@0: this._setActiveWindow(newActiveWindow, false); michael@0: } michael@0: }, michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// nsIDownloadProgressListener michael@0: michael@0: /** michael@0: * Update status if a download's progress has changed. michael@0: */ michael@0: onProgressChange: function DTPU_onProgressChange() michael@0: { michael@0: this._updateStatus(); michael@0: this._updateTaskbar(); michael@0: }, michael@0: michael@0: /** michael@0: * Update status if a download's state has changed. michael@0: */ michael@0: onDownloadStateChange: function DTPU_onDownloadStateChange() michael@0: { michael@0: this._updateStatus(); michael@0: this._updateTaskbar(); michael@0: }, michael@0: michael@0: onSecurityChange: function() { }, michael@0: michael@0: onStateChange: function() { }, michael@0: michael@0: observe: function DTPU_observe(aSubject, aTopic, aData) { michael@0: if (aTopic == "quit-application-granted") { michael@0: this._uninit(); michael@0: } michael@0: } michael@0: };