1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/mozapps/downloads/DownloadTaskbarProgress.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,403 @@ 1.4 +/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * vim: sw=2 ts=2 sts=2 et filetype=javascript 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +this.EXPORTED_SYMBOLS = [ 1.11 + "DownloadTaskbarProgress", 1.12 +]; 1.13 + 1.14 +//////////////////////////////////////////////////////////////////////////////// 1.15 +//// Constants 1.16 + 1.17 +const Cc = Components.classes; 1.18 +const Ci = Components.interfaces; 1.19 +const Cu = Components.utils; 1.20 +const Cr = Components.results; 1.21 + 1.22 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.23 +XPCOMUtils.defineLazyModuleGetter(this, "Services", 1.24 + "resource://gre/modules/Services.jsm"); 1.25 + 1.26 +const kTaskbarIDWin = "@mozilla.org/windows-taskbar;1"; 1.27 +const kTaskbarIDMac = "@mozilla.org/widget/macdocksupport;1"; 1.28 + 1.29 +//////////////////////////////////////////////////////////////////////////////// 1.30 +//// DownloadTaskbarProgress Object 1.31 + 1.32 +this.DownloadTaskbarProgress = 1.33 +{ 1.34 + init: function DTP_init() 1.35 + { 1.36 + if (DownloadTaskbarProgressUpdater) { 1.37 + DownloadTaskbarProgressUpdater._init(); 1.38 + } 1.39 + }, 1.40 + 1.41 + /** 1.42 + * Called when a browser window appears. This has an effect only when we 1.43 + * don't already have an active window. 1.44 + * 1.45 + * @param aWindow 1.46 + * The browser window that we'll potentially use to display the 1.47 + * progress. 1.48 + */ 1.49 + onBrowserWindowLoad: function DTP_onBrowserWindowLoad(aWindow) 1.50 + { 1.51 + this.init(); 1.52 + if (!DownloadTaskbarProgressUpdater) { 1.53 + return; 1.54 + } 1.55 + if (!DownloadTaskbarProgressUpdater._activeTaskbarProgress) { 1.56 + DownloadTaskbarProgressUpdater._setActiveWindow(aWindow, false); 1.57 + } 1.58 + }, 1.59 + 1.60 + /** 1.61 + * Called when the download window appears. The download window will take 1.62 + * over as the active window. 1.63 + */ 1.64 + onDownloadWindowLoad: function DTP_onDownloadWindowLoad(aWindow) 1.65 + { 1.66 + if (!DownloadTaskbarProgressUpdater) { 1.67 + return; 1.68 + } 1.69 + DownloadTaskbarProgressUpdater._setActiveWindow(aWindow, true); 1.70 + }, 1.71 + 1.72 + /** 1.73 + * Getters for internal DownloadTaskbarProgressUpdater values 1.74 + */ 1.75 + 1.76 + get activeTaskbarProgress() { 1.77 + if (!DownloadTaskbarProgressUpdater) { 1.78 + return null; 1.79 + } 1.80 + return DownloadTaskbarProgressUpdater._activeTaskbarProgress; 1.81 + }, 1.82 + 1.83 + get activeWindowIsDownloadWindow() { 1.84 + if (!DownloadTaskbarProgressUpdater) { 1.85 + return null; 1.86 + } 1.87 + return DownloadTaskbarProgressUpdater._activeWindowIsDownloadWindow; 1.88 + }, 1.89 + 1.90 + get taskbarState() { 1.91 + if (!DownloadTaskbarProgressUpdater) { 1.92 + return null; 1.93 + } 1.94 + return DownloadTaskbarProgressUpdater._taskbarState; 1.95 + }, 1.96 + 1.97 +}; 1.98 + 1.99 +//////////////////////////////////////////////////////////////////////////////// 1.100 +//// DownloadTaskbarProgressUpdater Object 1.101 + 1.102 +var DownloadTaskbarProgressUpdater = 1.103 +{ 1.104 + /// Whether the taskbar is initialized. 1.105 + _initialized: false, 1.106 + 1.107 + /// Reference to the taskbar. 1.108 + _taskbar: null, 1.109 + 1.110 + /// Reference to the download manager. 1.111 + _dm: null, 1.112 + 1.113 + /** 1.114 + * Initialize and register ourselves as a download progress listener. 1.115 + */ 1.116 + _init: function DTPU_init() 1.117 + { 1.118 + if (this._initialized) { 1.119 + return; // Already initialized 1.120 + } 1.121 + this._initialized = true; 1.122 + 1.123 + if (kTaskbarIDWin in Cc) { 1.124 + this._taskbar = Cc[kTaskbarIDWin].getService(Ci.nsIWinTaskbar); 1.125 + if (!this._taskbar.available) { 1.126 + // The Windows version is probably too old 1.127 + DownloadTaskbarProgressUpdater = null; 1.128 + return; 1.129 + } 1.130 + } else if (kTaskbarIDMac in Cc) { 1.131 + this._activeTaskbarProgress = Cc[kTaskbarIDMac]. 1.132 + getService(Ci.nsITaskbarProgress); 1.133 + } else { 1.134 + DownloadTaskbarProgressUpdater = null; 1.135 + return; 1.136 + } 1.137 + 1.138 + this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS; 1.139 + 1.140 + this._dm = Cc["@mozilla.org/download-manager;1"]. 1.141 + getService(Ci.nsIDownloadManager); 1.142 + this._dm.addPrivacyAwareListener(this); 1.143 + 1.144 + this._os = Cc["@mozilla.org/observer-service;1"]. 1.145 + getService(Ci.nsIObserverService); 1.146 + this._os.addObserver(this, "quit-application-granted", false); 1.147 + 1.148 + this._updateStatus(); 1.149 + // onBrowserWindowLoad/onDownloadWindowLoad are going to set the active 1.150 + // window, so don't do it here. 1.151 + }, 1.152 + 1.153 + /** 1.154 + * Unregisters ourselves as a download progress listener. 1.155 + */ 1.156 + _uninit: function DTPU_uninit() { 1.157 + this._dm.removeListener(this); 1.158 + this._os.removeObserver(this, "quit-application-granted"); 1.159 + this._activeTaskbarProgress = null; 1.160 + this._initialized = false; 1.161 + }, 1.162 + 1.163 + /** 1.164 + * This holds a reference to the taskbar progress for the window we're 1.165 + * working with. This window would preferably be download window, but can be 1.166 + * another window if it isn't open. 1.167 + */ 1.168 + _activeTaskbarProgress: null, 1.169 + 1.170 + /// Whether the active window is the download window 1.171 + _activeWindowIsDownloadWindow: false, 1.172 + 1.173 + /** 1.174 + * Sets the active window, and whether it's the download window. This takes 1.175 + * care of clearing out the previous active window's taskbar item, updating 1.176 + * the taskbar, and setting an onunload listener. 1.177 + * 1.178 + * @param aWindow 1.179 + * The window to set as active. 1.180 + * @param aIsDownloadWindow 1.181 + * Whether this window is a download window. 1.182 + */ 1.183 + _setActiveWindow: function DTPU_setActiveWindow(aWindow, aIsDownloadWindow) 1.184 + { 1.185 +#ifdef XP_WIN 1.186 + // Clear out the taskbar for the old active window. (If there was no active 1.187 + // window, this is a no-op.) 1.188 + this._clearTaskbar(); 1.189 + 1.190 + this._activeWindowIsDownloadWindow = aIsDownloadWindow; 1.191 + if (aWindow) { 1.192 + // Get the taskbar progress for this window 1.193 + let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor). 1.194 + getInterface(Ci.nsIWebNavigation). 1.195 + QueryInterface(Ci.nsIDocShellTreeItem).treeOwner. 1.196 + QueryInterface(Ci.nsIInterfaceRequestor). 1.197 + getInterface(Ci.nsIXULWindow).docShell; 1.198 + let taskbarProgress = this._taskbar.getTaskbarProgress(docShell); 1.199 + this._activeTaskbarProgress = taskbarProgress; 1.200 + 1.201 + this._updateTaskbar(); 1.202 + // _onActiveWindowUnload is idempotent, so we don't need to check whether 1.203 + // we've already set this before or not. 1.204 + aWindow.addEventListener("unload", function () { 1.205 + DownloadTaskbarProgressUpdater._onActiveWindowUnload(taskbarProgress); 1.206 + }, false); 1.207 + } 1.208 + else { 1.209 + this._activeTaskbarProgress = null; 1.210 + } 1.211 +#endif 1.212 + }, 1.213 + 1.214 + /// Current state displayed on the active window's taskbar item 1.215 + _taskbarState: null, 1.216 + _totalSize: 0, 1.217 + _totalTransferred: 0, 1.218 + 1.219 + _shouldSetState: function DTPU_shouldSetState() 1.220 + { 1.221 +#ifdef XP_WIN 1.222 + // If the active window is not the download manager window, set the state 1.223 + // only if it is normal or indeterminate. 1.224 + return this._activeWindowIsDownloadWindow || 1.225 + (this._taskbarState == Ci.nsITaskbarProgress.STATE_NORMAL || 1.226 + this._taskbarState == Ci.nsITaskbarProgress.STATE_INDETERMINATE); 1.227 +#else 1.228 + return true; 1.229 +#endif 1.230 + }, 1.231 + 1.232 + /** 1.233 + * Update the active window's taskbar indicator with the current state. There 1.234 + * are two cases here: 1.235 + * 1. If the active window is the download window, then we always update 1.236 + * the taskbar indicator. 1.237 + * 2. If the active window isn't the download window, then we update only if 1.238 + * the status is normal or indeterminate. i.e. one or more downloads are 1.239 + * currently progressing or in scan mode. If we aren't, then we clear the 1.240 + * indicator. 1.241 + */ 1.242 + _updateTaskbar: function DTPU_updateTaskbar() 1.243 + { 1.244 + if (!this._activeTaskbarProgress) { 1.245 + return; 1.246 + } 1.247 + 1.248 + if (this._shouldSetState()) { 1.249 + this._activeTaskbarProgress.setProgressState(this._taskbarState, 1.250 + this._totalTransferred, 1.251 + this._totalSize); 1.252 + } 1.253 + // Clear any state otherwise 1.254 + else { 1.255 + this._clearTaskbar(); 1.256 + } 1.257 + }, 1.258 + 1.259 + /** 1.260 + * Clear taskbar state. This is needed: 1.261 + * - to transfer the indicator off a window before transferring it onto 1.262 + * another one 1.263 + * - whenever we don't want to show it for a non-download window. 1.264 + */ 1.265 + _clearTaskbar: function DTPU_clearTaskbar() 1.266 + { 1.267 + if (this._activeTaskbarProgress) { 1.268 + this._activeTaskbarProgress.setProgressState( 1.269 + Ci.nsITaskbarProgress.STATE_NO_PROGRESS 1.270 + ); 1.271 + } 1.272 + }, 1.273 + 1.274 + /** 1.275 + * Update this._taskbarState, this._totalSize and this._totalTransferred. 1.276 + * This is called when the download manager is initialized or when the 1.277 + * progress or state of a download changes. 1.278 + * We compute the number of active and paused downloads, and the total size 1.279 + * and total amount already transferred across whichever downloads we have 1.280 + * the data for. 1.281 + * - If there are no active downloads, then we don't want to show any 1.282 + * progress. 1.283 + * - If the number of active downloads is equal to the number of paused 1.284 + * downloads, then we show a paused indicator if we know the size of at 1.285 + * least one download, and no indicator if we don't. 1.286 + * - If the number of active downloads is more than the number of paused 1.287 + * downloads, then we show a "normal" indicator if we know the size of at 1.288 + * least one download, and an indeterminate indicator if we don't. 1.289 + */ 1.290 + _updateStatus: function DTPU_updateStatus() 1.291 + { 1.292 + let numActive = this._dm.activeDownloadCount + this._dm.activePrivateDownloadCount; 1.293 + let totalSize = 0, totalTransferred = 0; 1.294 + 1.295 + if (numActive == 0) { 1.296 + this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS; 1.297 + } 1.298 + else { 1.299 + let numPaused = 0, numScanning = 0; 1.300 + 1.301 + // Enumerate all active downloads 1.302 + [this._dm.activeDownloads, this._dm.activePrivateDownloads].forEach(function(downloads) { 1.303 + while (downloads.hasMoreElements()) { 1.304 + let download = downloads.getNext().QueryInterface(Ci.nsIDownload); 1.305 + // Only set values if we actually know the download size 1.306 + if (download.percentComplete != -1) { 1.307 + totalSize += download.size; 1.308 + totalTransferred += download.amountTransferred; 1.309 + } 1.310 + // We might need to display a paused state, so track this 1.311 + if (download.state == this._dm.DOWNLOAD_PAUSED) { 1.312 + numPaused++; 1.313 + } else if (download.state == this._dm.DOWNLOAD_SCANNING) { 1.314 + numScanning++; 1.315 + } 1.316 + } 1.317 + }.bind(this)); 1.318 + 1.319 + // If all downloads are paused, show the progress as paused, unless we 1.320 + // don't have any information about sizes, in which case we don't 1.321 + // display anything 1.322 + if (numActive == numPaused) { 1.323 + if (totalSize == 0) { 1.324 + this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS; 1.325 + totalTransferred = 0; 1.326 + } 1.327 + else { 1.328 + this._taskbarState = Ci.nsITaskbarProgress.STATE_PAUSED; 1.329 + } 1.330 + } 1.331 + // If at least one download is not paused, and we don't have any 1.332 + // information about download sizes, display an indeterminate indicator 1.333 + else if (totalSize == 0 || numActive == numScanning) { 1.334 + this._taskbarState = Ci.nsITaskbarProgress.STATE_INDETERMINATE; 1.335 + totalSize = 0; 1.336 + totalTransferred = 0; 1.337 + } 1.338 + // Otherwise display a normal progress bar 1.339 + else { 1.340 + this._taskbarState = Ci.nsITaskbarProgress.STATE_NORMAL; 1.341 + } 1.342 + } 1.343 + 1.344 + this._totalSize = totalSize; 1.345 + this._totalTransferred = totalTransferred; 1.346 + }, 1.347 + 1.348 + /** 1.349 + * Called when a window that at one point has been an active window is 1.350 + * closed. If this window is currently the active window, we need to look for 1.351 + * another window and make that our active window. 1.352 + * 1.353 + * This function is idempotent, so multiple calls for the same window are not 1.354 + * a problem. 1.355 + * 1.356 + * @param aTaskbarProgress 1.357 + * The taskbar progress for the window that is being unloaded. 1.358 + */ 1.359 + _onActiveWindowUnload: function DTPU_onActiveWindowUnload(aTaskbarProgress) 1.360 + { 1.361 + if (this._activeTaskbarProgress == aTaskbarProgress) { 1.362 + let windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]. 1.363 + getService(Ci.nsIWindowMediator); 1.364 + let windows = windowMediator.getEnumerator(null); 1.365 + let newActiveWindow = null; 1.366 + if (windows.hasMoreElements()) { 1.367 + newActiveWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow); 1.368 + } 1.369 + 1.370 + // We aren't ever going to reach this point while the download manager is 1.371 + // open, so it's safe to assume false for the second operand 1.372 + this._setActiveWindow(newActiveWindow, false); 1.373 + } 1.374 + }, 1.375 + 1.376 + ////////////////////////////////////////////////////////////////////////////// 1.377 + //// nsIDownloadProgressListener 1.378 + 1.379 + /** 1.380 + * Update status if a download's progress has changed. 1.381 + */ 1.382 + onProgressChange: function DTPU_onProgressChange() 1.383 + { 1.384 + this._updateStatus(); 1.385 + this._updateTaskbar(); 1.386 + }, 1.387 + 1.388 + /** 1.389 + * Update status if a download's state has changed. 1.390 + */ 1.391 + onDownloadStateChange: function DTPU_onDownloadStateChange() 1.392 + { 1.393 + this._updateStatus(); 1.394 + this._updateTaskbar(); 1.395 + }, 1.396 + 1.397 + onSecurityChange: function() { }, 1.398 + 1.399 + onStateChange: function() { }, 1.400 + 1.401 + observe: function DTPU_observe(aSubject, aTopic, aData) { 1.402 + if (aTopic == "quit-application-granted") { 1.403 + this._uninit(); 1.404 + } 1.405 + } 1.406 +};