|
1 /* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
|
2 * vim: sw=2 ts=2 sts=2 et filetype=javascript |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 this.EXPORTED_SYMBOLS = [ |
|
8 "DownloadTaskbarProgress", |
|
9 ]; |
|
10 |
|
11 //////////////////////////////////////////////////////////////////////////////// |
|
12 //// Constants |
|
13 |
|
14 const Cc = Components.classes; |
|
15 const Ci = Components.interfaces; |
|
16 const Cu = Components.utils; |
|
17 const Cr = Components.results; |
|
18 |
|
19 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
20 XPCOMUtils.defineLazyModuleGetter(this, "Services", |
|
21 "resource://gre/modules/Services.jsm"); |
|
22 |
|
23 const kTaskbarIDWin = "@mozilla.org/windows-taskbar;1"; |
|
24 const kTaskbarIDMac = "@mozilla.org/widget/macdocksupport;1"; |
|
25 |
|
26 //////////////////////////////////////////////////////////////////////////////// |
|
27 //// DownloadTaskbarProgress Object |
|
28 |
|
29 this.DownloadTaskbarProgress = |
|
30 { |
|
31 init: function DTP_init() |
|
32 { |
|
33 if (DownloadTaskbarProgressUpdater) { |
|
34 DownloadTaskbarProgressUpdater._init(); |
|
35 } |
|
36 }, |
|
37 |
|
38 /** |
|
39 * Called when a browser window appears. This has an effect only when we |
|
40 * don't already have an active window. |
|
41 * |
|
42 * @param aWindow |
|
43 * The browser window that we'll potentially use to display the |
|
44 * progress. |
|
45 */ |
|
46 onBrowserWindowLoad: function DTP_onBrowserWindowLoad(aWindow) |
|
47 { |
|
48 this.init(); |
|
49 if (!DownloadTaskbarProgressUpdater) { |
|
50 return; |
|
51 } |
|
52 if (!DownloadTaskbarProgressUpdater._activeTaskbarProgress) { |
|
53 DownloadTaskbarProgressUpdater._setActiveWindow(aWindow, false); |
|
54 } |
|
55 }, |
|
56 |
|
57 /** |
|
58 * Called when the download window appears. The download window will take |
|
59 * over as the active window. |
|
60 */ |
|
61 onDownloadWindowLoad: function DTP_onDownloadWindowLoad(aWindow) |
|
62 { |
|
63 if (!DownloadTaskbarProgressUpdater) { |
|
64 return; |
|
65 } |
|
66 DownloadTaskbarProgressUpdater._setActiveWindow(aWindow, true); |
|
67 }, |
|
68 |
|
69 /** |
|
70 * Getters for internal DownloadTaskbarProgressUpdater values |
|
71 */ |
|
72 |
|
73 get activeTaskbarProgress() { |
|
74 if (!DownloadTaskbarProgressUpdater) { |
|
75 return null; |
|
76 } |
|
77 return DownloadTaskbarProgressUpdater._activeTaskbarProgress; |
|
78 }, |
|
79 |
|
80 get activeWindowIsDownloadWindow() { |
|
81 if (!DownloadTaskbarProgressUpdater) { |
|
82 return null; |
|
83 } |
|
84 return DownloadTaskbarProgressUpdater._activeWindowIsDownloadWindow; |
|
85 }, |
|
86 |
|
87 get taskbarState() { |
|
88 if (!DownloadTaskbarProgressUpdater) { |
|
89 return null; |
|
90 } |
|
91 return DownloadTaskbarProgressUpdater._taskbarState; |
|
92 }, |
|
93 |
|
94 }; |
|
95 |
|
96 //////////////////////////////////////////////////////////////////////////////// |
|
97 //// DownloadTaskbarProgressUpdater Object |
|
98 |
|
99 var DownloadTaskbarProgressUpdater = |
|
100 { |
|
101 /// Whether the taskbar is initialized. |
|
102 _initialized: false, |
|
103 |
|
104 /// Reference to the taskbar. |
|
105 _taskbar: null, |
|
106 |
|
107 /// Reference to the download manager. |
|
108 _dm: null, |
|
109 |
|
110 /** |
|
111 * Initialize and register ourselves as a download progress listener. |
|
112 */ |
|
113 _init: function DTPU_init() |
|
114 { |
|
115 if (this._initialized) { |
|
116 return; // Already initialized |
|
117 } |
|
118 this._initialized = true; |
|
119 |
|
120 if (kTaskbarIDWin in Cc) { |
|
121 this._taskbar = Cc[kTaskbarIDWin].getService(Ci.nsIWinTaskbar); |
|
122 if (!this._taskbar.available) { |
|
123 // The Windows version is probably too old |
|
124 DownloadTaskbarProgressUpdater = null; |
|
125 return; |
|
126 } |
|
127 } else if (kTaskbarIDMac in Cc) { |
|
128 this._activeTaskbarProgress = Cc[kTaskbarIDMac]. |
|
129 getService(Ci.nsITaskbarProgress); |
|
130 } else { |
|
131 DownloadTaskbarProgressUpdater = null; |
|
132 return; |
|
133 } |
|
134 |
|
135 this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS; |
|
136 |
|
137 this._dm = Cc["@mozilla.org/download-manager;1"]. |
|
138 getService(Ci.nsIDownloadManager); |
|
139 this._dm.addPrivacyAwareListener(this); |
|
140 |
|
141 this._os = Cc["@mozilla.org/observer-service;1"]. |
|
142 getService(Ci.nsIObserverService); |
|
143 this._os.addObserver(this, "quit-application-granted", false); |
|
144 |
|
145 this._updateStatus(); |
|
146 // onBrowserWindowLoad/onDownloadWindowLoad are going to set the active |
|
147 // window, so don't do it here. |
|
148 }, |
|
149 |
|
150 /** |
|
151 * Unregisters ourselves as a download progress listener. |
|
152 */ |
|
153 _uninit: function DTPU_uninit() { |
|
154 this._dm.removeListener(this); |
|
155 this._os.removeObserver(this, "quit-application-granted"); |
|
156 this._activeTaskbarProgress = null; |
|
157 this._initialized = false; |
|
158 }, |
|
159 |
|
160 /** |
|
161 * This holds a reference to the taskbar progress for the window we're |
|
162 * working with. This window would preferably be download window, but can be |
|
163 * another window if it isn't open. |
|
164 */ |
|
165 _activeTaskbarProgress: null, |
|
166 |
|
167 /// Whether the active window is the download window |
|
168 _activeWindowIsDownloadWindow: false, |
|
169 |
|
170 /** |
|
171 * Sets the active window, and whether it's the download window. This takes |
|
172 * care of clearing out the previous active window's taskbar item, updating |
|
173 * the taskbar, and setting an onunload listener. |
|
174 * |
|
175 * @param aWindow |
|
176 * The window to set as active. |
|
177 * @param aIsDownloadWindow |
|
178 * Whether this window is a download window. |
|
179 */ |
|
180 _setActiveWindow: function DTPU_setActiveWindow(aWindow, aIsDownloadWindow) |
|
181 { |
|
182 #ifdef XP_WIN |
|
183 // Clear out the taskbar for the old active window. (If there was no active |
|
184 // window, this is a no-op.) |
|
185 this._clearTaskbar(); |
|
186 |
|
187 this._activeWindowIsDownloadWindow = aIsDownloadWindow; |
|
188 if (aWindow) { |
|
189 // Get the taskbar progress for this window |
|
190 let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor). |
|
191 getInterface(Ci.nsIWebNavigation). |
|
192 QueryInterface(Ci.nsIDocShellTreeItem).treeOwner. |
|
193 QueryInterface(Ci.nsIInterfaceRequestor). |
|
194 getInterface(Ci.nsIXULWindow).docShell; |
|
195 let taskbarProgress = this._taskbar.getTaskbarProgress(docShell); |
|
196 this._activeTaskbarProgress = taskbarProgress; |
|
197 |
|
198 this._updateTaskbar(); |
|
199 // _onActiveWindowUnload is idempotent, so we don't need to check whether |
|
200 // we've already set this before or not. |
|
201 aWindow.addEventListener("unload", function () { |
|
202 DownloadTaskbarProgressUpdater._onActiveWindowUnload(taskbarProgress); |
|
203 }, false); |
|
204 } |
|
205 else { |
|
206 this._activeTaskbarProgress = null; |
|
207 } |
|
208 #endif |
|
209 }, |
|
210 |
|
211 /// Current state displayed on the active window's taskbar item |
|
212 _taskbarState: null, |
|
213 _totalSize: 0, |
|
214 _totalTransferred: 0, |
|
215 |
|
216 _shouldSetState: function DTPU_shouldSetState() |
|
217 { |
|
218 #ifdef XP_WIN |
|
219 // If the active window is not the download manager window, set the state |
|
220 // only if it is normal or indeterminate. |
|
221 return this._activeWindowIsDownloadWindow || |
|
222 (this._taskbarState == Ci.nsITaskbarProgress.STATE_NORMAL || |
|
223 this._taskbarState == Ci.nsITaskbarProgress.STATE_INDETERMINATE); |
|
224 #else |
|
225 return true; |
|
226 #endif |
|
227 }, |
|
228 |
|
229 /** |
|
230 * Update the active window's taskbar indicator with the current state. There |
|
231 * are two cases here: |
|
232 * 1. If the active window is the download window, then we always update |
|
233 * the taskbar indicator. |
|
234 * 2. If the active window isn't the download window, then we update only if |
|
235 * the status is normal or indeterminate. i.e. one or more downloads are |
|
236 * currently progressing or in scan mode. If we aren't, then we clear the |
|
237 * indicator. |
|
238 */ |
|
239 _updateTaskbar: function DTPU_updateTaskbar() |
|
240 { |
|
241 if (!this._activeTaskbarProgress) { |
|
242 return; |
|
243 } |
|
244 |
|
245 if (this._shouldSetState()) { |
|
246 this._activeTaskbarProgress.setProgressState(this._taskbarState, |
|
247 this._totalTransferred, |
|
248 this._totalSize); |
|
249 } |
|
250 // Clear any state otherwise |
|
251 else { |
|
252 this._clearTaskbar(); |
|
253 } |
|
254 }, |
|
255 |
|
256 /** |
|
257 * Clear taskbar state. This is needed: |
|
258 * - to transfer the indicator off a window before transferring it onto |
|
259 * another one |
|
260 * - whenever we don't want to show it for a non-download window. |
|
261 */ |
|
262 _clearTaskbar: function DTPU_clearTaskbar() |
|
263 { |
|
264 if (this._activeTaskbarProgress) { |
|
265 this._activeTaskbarProgress.setProgressState( |
|
266 Ci.nsITaskbarProgress.STATE_NO_PROGRESS |
|
267 ); |
|
268 } |
|
269 }, |
|
270 |
|
271 /** |
|
272 * Update this._taskbarState, this._totalSize and this._totalTransferred. |
|
273 * This is called when the download manager is initialized or when the |
|
274 * progress or state of a download changes. |
|
275 * We compute the number of active and paused downloads, and the total size |
|
276 * and total amount already transferred across whichever downloads we have |
|
277 * the data for. |
|
278 * - If there are no active downloads, then we don't want to show any |
|
279 * progress. |
|
280 * - If the number of active downloads is equal to the number of paused |
|
281 * downloads, then we show a paused indicator if we know the size of at |
|
282 * least one download, and no indicator if we don't. |
|
283 * - If the number of active downloads is more than the number of paused |
|
284 * downloads, then we show a "normal" indicator if we know the size of at |
|
285 * least one download, and an indeterminate indicator if we don't. |
|
286 */ |
|
287 _updateStatus: function DTPU_updateStatus() |
|
288 { |
|
289 let numActive = this._dm.activeDownloadCount + this._dm.activePrivateDownloadCount; |
|
290 let totalSize = 0, totalTransferred = 0; |
|
291 |
|
292 if (numActive == 0) { |
|
293 this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS; |
|
294 } |
|
295 else { |
|
296 let numPaused = 0, numScanning = 0; |
|
297 |
|
298 // Enumerate all active downloads |
|
299 [this._dm.activeDownloads, this._dm.activePrivateDownloads].forEach(function(downloads) { |
|
300 while (downloads.hasMoreElements()) { |
|
301 let download = downloads.getNext().QueryInterface(Ci.nsIDownload); |
|
302 // Only set values if we actually know the download size |
|
303 if (download.percentComplete != -1) { |
|
304 totalSize += download.size; |
|
305 totalTransferred += download.amountTransferred; |
|
306 } |
|
307 // We might need to display a paused state, so track this |
|
308 if (download.state == this._dm.DOWNLOAD_PAUSED) { |
|
309 numPaused++; |
|
310 } else if (download.state == this._dm.DOWNLOAD_SCANNING) { |
|
311 numScanning++; |
|
312 } |
|
313 } |
|
314 }.bind(this)); |
|
315 |
|
316 // If all downloads are paused, show the progress as paused, unless we |
|
317 // don't have any information about sizes, in which case we don't |
|
318 // display anything |
|
319 if (numActive == numPaused) { |
|
320 if (totalSize == 0) { |
|
321 this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS; |
|
322 totalTransferred = 0; |
|
323 } |
|
324 else { |
|
325 this._taskbarState = Ci.nsITaskbarProgress.STATE_PAUSED; |
|
326 } |
|
327 } |
|
328 // If at least one download is not paused, and we don't have any |
|
329 // information about download sizes, display an indeterminate indicator |
|
330 else if (totalSize == 0 || numActive == numScanning) { |
|
331 this._taskbarState = Ci.nsITaskbarProgress.STATE_INDETERMINATE; |
|
332 totalSize = 0; |
|
333 totalTransferred = 0; |
|
334 } |
|
335 // Otherwise display a normal progress bar |
|
336 else { |
|
337 this._taskbarState = Ci.nsITaskbarProgress.STATE_NORMAL; |
|
338 } |
|
339 } |
|
340 |
|
341 this._totalSize = totalSize; |
|
342 this._totalTransferred = totalTransferred; |
|
343 }, |
|
344 |
|
345 /** |
|
346 * Called when a window that at one point has been an active window is |
|
347 * closed. If this window is currently the active window, we need to look for |
|
348 * another window and make that our active window. |
|
349 * |
|
350 * This function is idempotent, so multiple calls for the same window are not |
|
351 * a problem. |
|
352 * |
|
353 * @param aTaskbarProgress |
|
354 * The taskbar progress for the window that is being unloaded. |
|
355 */ |
|
356 _onActiveWindowUnload: function DTPU_onActiveWindowUnload(aTaskbarProgress) |
|
357 { |
|
358 if (this._activeTaskbarProgress == aTaskbarProgress) { |
|
359 let windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]. |
|
360 getService(Ci.nsIWindowMediator); |
|
361 let windows = windowMediator.getEnumerator(null); |
|
362 let newActiveWindow = null; |
|
363 if (windows.hasMoreElements()) { |
|
364 newActiveWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow); |
|
365 } |
|
366 |
|
367 // We aren't ever going to reach this point while the download manager is |
|
368 // open, so it's safe to assume false for the second operand |
|
369 this._setActiveWindow(newActiveWindow, false); |
|
370 } |
|
371 }, |
|
372 |
|
373 ////////////////////////////////////////////////////////////////////////////// |
|
374 //// nsIDownloadProgressListener |
|
375 |
|
376 /** |
|
377 * Update status if a download's progress has changed. |
|
378 */ |
|
379 onProgressChange: function DTPU_onProgressChange() |
|
380 { |
|
381 this._updateStatus(); |
|
382 this._updateTaskbar(); |
|
383 }, |
|
384 |
|
385 /** |
|
386 * Update status if a download's state has changed. |
|
387 */ |
|
388 onDownloadStateChange: function DTPU_onDownloadStateChange() |
|
389 { |
|
390 this._updateStatus(); |
|
391 this._updateTaskbar(); |
|
392 }, |
|
393 |
|
394 onSecurityChange: function() { }, |
|
395 |
|
396 onStateChange: function() { }, |
|
397 |
|
398 observe: function DTPU_observe(aSubject, aTopic, aData) { |
|
399 if (aTopic == "quit-application-granted") { |
|
400 this._uninit(); |
|
401 } |
|
402 } |
|
403 }; |