Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 const URI_GENERIC_ICON_DOWNLOAD = "chrome://browser/skin/images/alert-downloads-30.png";
8 var MetroDownloadsView = {
9 /**
10 * _downloadCount keeps track of the number of downloads that a single
11 * notification bar groups together. A download is grouped with other
12 * downloads if it starts before other downloads have completed.
13 */
14 _downloadCount: 0,
15 _downloadsInProgress: 0,
16 _lastDownload: null,
17 _inited: false,
18 _progressAlert: null,
19 _lastSec: Infinity,
21 _progressNotificationInfo: new Map(),
22 _runDownloadBooleanMap: new Map(),
24 get manager() {
25 return Cc["@mozilla.org/download-manager;1"]
26 .getService(Ci.nsIDownloadManager);
27 },
29 _getReferrerOrSource: function dh__getReferrerOrSource(aDownload) {
30 return aDownload.referrer.spec || aDownload.source.spec;
31 },
33 _getLocalFile: function dh__getLocalFile(aFileURI) {
34 // XXX it's possible that using a null char-set here is bad
35 let spec = ('string' == typeof aFileURI) ? aFileURI : aFileURI.spec;
36 let fileUrl;
37 try {
38 fileUrl = Services.io.newURI(spec, null, null).QueryInterface(Ci.nsIFileURL);
39 } catch (ex) {
40 Util.dumpLn("_getLocalFile: Caught exception creating newURI from file spec: "+aFileURI.spec+": " + ex.message);
41 return;
42 }
43 return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile);
44 },
46 init: function dh_init() {
47 if (this._inited)
48 return;
50 this._inited = true;
52 Services.obs.addObserver(this, "dl-start", true);
53 Services.obs.addObserver(this, "dl-done", true);
54 Services.obs.addObserver(this, "dl-run", true);
55 Services.obs.addObserver(this, "dl-failed", true);
58 this._progress = new DownloadProgressListener(this);
59 this.manager.addListener(this._progress);
61 Elements.tabList.addEventListener("TabClose", this, false);
63 this._downloadProgressIndicator = document.getElementById("download-progress");
65 if (this.manager.activeDownloadCount) {
66 setTimeout (this._restartWithActiveDownloads.bind(this), 0);
67 }
68 },
70 uninit: function dh_uninit() {
71 if (this._inited) {
72 Services.obs.removeObserver(this, "dl-start");
73 Services.obs.removeObserver(this, "dl-done");
74 Services.obs.removeObserver(this, "dl-run");
75 Services.obs.removeObserver(this, "dl-failed");
76 if (Elements && Elements.tabList)
77 Elements.tabList.removeEventListener("TabClose", this);
78 }
79 },
81 get _notificationBox() {
82 return Browser.getNotificationBox(Browser.selectedBrowser);
83 },
85 get _notificationBoxes() {
86 let currentBox = this._notificationBox;
87 let boxes = [
88 currentBox
89 ];
90 for (let { linkedBrowser } of Elements.tabList.children) {
91 if (linkedBrowser !== Browser.selectedBrowser) {
92 let notificationBox = Browser.getNotificationBox(linkedBrowser);
93 if (notificationBox)
94 boxes.push(notificationBox);
95 }
96 }
97 return boxes;
98 },
100 get _progressNotification() {
101 let notn = this._getNotificationWithValue("download-progress");
102 let currentBox = this._notificationBox;
103 // move the progress notification if attached to a different browser
104 if (notn && notn.parentNode !== currentBox) {
105 notn.parentNode.removeNotification(notn);
106 currentBox.insertBefore(notn, currentBox.firstChild);
107 }
108 return notn;
109 },
111 _getNotificationWithValue: function(aValue) {
112 let notn;
113 let allNotificationBoxes = this._notificationBoxes;
114 for(let box of allNotificationBoxes) {
115 notn = box.getNotificationWithValue(aValue);
116 if (notn) {
117 break;
118 }
119 }
120 return notn;
121 },
123 _restartWithActiveDownloads: function() {
124 let activeDownloads = this.manager.activeDownloads;
126 while (activeDownloads.hasMoreElements()) {
127 let dl = activeDownloads.getNext();
128 switch (dl.state) {
129 case 0: // Downloading
130 case 5: // Queued
131 this.watchDownload(dl);
132 this.updateInfobar();
133 break;
134 }
135 }
136 if (this.manager.activeDownloadCount) {
137 ContextUI.displayNavbar();
138 }
139 },
141 openDownload: function dh_openDownload(aDownload) {
142 let fileURI = aDownload.target
144 if (!(fileURI && fileURI.spec)) {
145 Util.dumpLn("Cant open download "+id+", fileURI is invalid");
146 return;
147 }
149 let file = this._getLocalFile(fileURI);
150 try {
151 file && Services.metro.launchInDesktop(aDownload.target.spec, "");
152 } catch (ex) {
153 Util.dumpLn("Failed to open download, with id: "+id+", download target URI spec: " + fileURI.spec);
154 Util.dumpLn("Failed download source:"+(aDownload.source && aDownload.source.spec));
155 }
156 },
158 removeDownload: function dh_removeDownload(aDownload) {
159 // aDownload is the XUL element here,
160 // and .target currently returns the target attribute (string value)
161 let id = aDownload.getAttribute("downloadId");
162 let download = this.manager.getDownload(id);
164 if (download) {
165 this.manager.removeDownload(id);
166 }
167 },
169 cancelDownload: function dh_cancelDownload(aDownload) {
170 let fileURI = aDownload.target;
171 if (!(fileURI && fileURI.spec)) {
172 Util.dumpLn("Cant remove download file for: "+aDownload.id+", fileURI is invalid");
173 }
175 try {
176 let file = this._getLocalFile(fileURI);
177 if (file && file.exists())
178 file.remove(false);
179 this.manager.cancelDownload(aDownload.id);
181 // If cancelling was successful, stop tracking the download.
182 this._progressNotificationInfo.delete(aDownload.guid);
183 this._runDownloadBooleanMap.delete(aDownload.targetFile.path);
184 this._downloadCount--;
185 this._downloadsInProgress--;
186 let notn = this._progressNotification;
187 if (notn && this._downloadsInProgress <= 0) {
188 this._notificationBox.removeNotification(notn);
189 }
190 } catch (ex) {
191 Util.dumpLn("Failed to cancel download, with id: "+aDownload.id+", download target URI spec: " + fileURI.spec);
192 Util.dumpLn("Failed download source:"+(aDownload.source && aDownload.source.spec));
193 }
194 },
196 // Cancels all downloads.
197 cancelDownloads: function dh_cancelDownloads() {
198 for (let [guid, info] of this._progressNotificationInfo) {
199 this.cancelDownload(info.download);
200 }
201 this._downloadCount = 0;
202 this._progressNotificationInfo.clear();
203 this._runDownloadBooleanMap.clear();
204 },
206 pauseDownload: function dh_pauseDownload(aDownload) {
207 let id = aDownload.getAttribute("downloadId");
208 this.manager.pauseDownload(id);
209 },
211 resumeDownload: function dh_resumeDownload(aDownload) {
212 let id = aDownload.getAttribute("downloadId");
213 this.manager.resumeDownload(id);
214 },
216 showPage: function dh_showPage(aDownload) {
217 let id = aDownload.getAttribute("downloadId");
218 let download = this.manager.getDownload(id);
219 let uri = this._getReferrerOrSource(download);
220 if (uri)
221 BrowserUI.addAndShowTab(uri, Browser.selectedTab);
222 },
224 showAlert: function dh_showAlert(aName, aMessage, aTitle, aObserver) {
225 var notifier = Cc["@mozilla.org/alerts-service;1"]
226 .getService(Ci.nsIAlertsService);
228 if (!aTitle)
229 aTitle = Strings.browser.GetStringFromName("alertDownloads");
231 notifier.showAlertNotification("", aTitle, aMessage, true, "", aObserver, aName);
232 },
234 showNotification: function dh_showNotification(title, msg, buttons, priority) {
235 let notification = this._notificationBox.appendNotification(msg,
236 title,
237 URI_GENERIC_ICON_DOWNLOAD,
238 priority,
239 buttons);
240 return notification;
241 },
243 _showDownloadFailedNotification: function (aDownload) {
244 let tryAgainButtonText =
245 Strings.browser.GetStringFromName("downloadTryAgain");
246 let cancelButtonText =
247 Strings.browser.GetStringFromName("downloadCancel");
249 let message = Strings.browser.formatStringFromName("alertDownloadFailed",
250 [aDownload.displayName], 1);
252 let buttons = [
253 {
254 isDefault: true,
255 label: tryAgainButtonText,
256 accessKey: "",
257 callback: function() {
258 MetroDownloadsView.manager.retryDownload(aDownload.id);
259 }
260 },
261 {
262 label: cancelButtonText,
263 accessKey: "",
264 callback: function() {
265 MetroDownloadsView.cancelDownload(aDownload);
266 MetroDownloadsView._downloadProgressIndicator.reset();
267 }
268 }
269 ];
270 this.showNotification("download-failed", message, buttons,
271 this._notificationBox.PRIORITY_WARNING_HIGH);
272 },
274 _showDownloadCompleteNotification: function () {
275 let message = "";
276 let showInFilesButtonText = Strings.browser.GetStringFromName("downloadShowInFiles");
278 let buttons = [
279 {
280 label: showInFilesButtonText,
281 accessKey: "",
282 callback: function() {
283 let fileURI = MetroDownloadsView._lastDownload.target;
284 let file = MetroDownloadsView._getLocalFile(fileURI);
285 file.reveal();
286 MetroDownloadsView._resetCompletedDownloads();
287 }
288 }
289 ];
291 if (this._downloadCount > 1) {
292 message = PluralForm.get(this._downloadCount,
293 Strings.browser.GetStringFromName("alertMultipleDownloadsComplete"))
294 .replace("#1", this._downloadCount)
295 } else {
296 let runButtonText =
297 Strings.browser.GetStringFromName("downloadOpen");
298 message = Strings.browser.formatStringFromName("alertDownloadsDone2",
299 [this._lastDownload.displayName], 1);
301 buttons.unshift({
302 isDefault: true,
303 label: runButtonText,
304 accessKey: "",
305 callback: function() {
306 MetroDownloadsView.openDownload(MetroDownloadsView._lastDownload);
307 MetroDownloadsView._resetCompletedDownloads();
308 }
309 });
310 }
311 this._removeNotification("download-complete");
312 this.showNotification("download-complete", message, buttons,
313 this._notificationBox.PRIORITY_WARNING_MEDIUM);
314 },
316 _showDownloadCompleteToast: function () {
317 let name = "DownloadComplete";
318 let msg = "";
319 let title = "";
320 let observer = null;
321 if (this._downloadCount > 1) {
322 title = PluralForm.get(this._downloadCount,
323 Strings.browser.GetStringFromName("alertMultipleDownloadsComplete"))
324 .replace("#1", this._downloadCount)
325 msg = PluralForm.get(2, Strings.browser.GetStringFromName("downloadShowInFiles"));
327 observer = {
328 observe: function (aSubject, aTopic, aData) {
329 switch (aTopic) {
330 case "alertclickcallback":
331 let fileURI = MetroDownloadsView._lastDownload.target;
332 let file = MetroDownloadsView._getLocalFile(fileURI);
333 file.reveal();
334 MetroDownloadsView._resetCompletedDownloads();
335 break;
336 }
337 }
338 }
339 } else {
340 title = Strings.browser.formatStringFromName("alertDownloadsDone",
341 [this._lastDownload.displayName], 1);
342 msg = Strings.browser.GetStringFromName("downloadOpenNow");
343 observer = {
344 observe: function (aSubject, aTopic, aData) {
345 switch (aTopic) {
346 case "alertclickcallback":
347 MetroDownloadsView.openDownload(MetroDownloadsView._lastDownload);
348 MetroDownloadsView._resetCompletedDownloads();
349 break;
350 }
351 }
352 }
353 }
354 this.showAlert(name, msg, title, observer);
355 },
357 _resetCompletedDownloads: function () {
358 this._progressNotificationInfo.clear();
359 this._downloadCount = 0;
360 this._lastDownload = null;
361 this._downloadProgressIndicator.reset();
362 this._removeNotification("download-complete");
363 },
365 _updateCircularProgressMeter: function dv_updateCircularProgressMeter() {
366 if (!this._progressNotificationInfo) {
367 return;
368 }
370 let totPercent = 0;
371 for (let [guid, info] of this._progressNotificationInfo) {
372 // info.download => nsIDownload
373 totPercent += info.download.percentComplete;
374 }
376 let percentComplete = totPercent / this._progressNotificationInfo.size;
377 this._downloadProgressIndicator.updateProgress(percentComplete);
378 },
380 _computeDownloadProgressString: function dv_computeDownloadProgressString() {
381 let totTransferred = 0, totSize = 0, totSecondsLeft = 0;
382 let guid, info;
383 for ([guid, info] of this._progressNotificationInfo) {
384 let size = info.download.size;
385 let amountTransferred = info.download.amountTransferred;
386 let speed = info.download.speed;
388 totTransferred += amountTransferred;
389 totSize += size;
390 totSecondsLeft += ((size - amountTransferred) / speed);
391 }
393 // Compute progress in bytes.
394 let amountTransferred = Util.getDownloadSize(totTransferred);
395 let size = Util.getDownloadSize(totSize);
396 let progress = amountTransferred + "/" + size;
398 // Compute progress in time.;
399 let [timeLeft, newLast] = DownloadUtils.getTimeLeft(totSecondsLeft, this._lastSec);
400 this._lastSec = newLast;
402 if (this._downloadCount == 1) {
403 return Strings.browser.GetStringFromName("alertDownloadsStart2")
404 .replace("#1", info.download.displayName)
405 .replace("#2", progress)
406 .replace("#3", timeLeft)
407 }
409 let numDownloads = this._downloadCount;
410 return PluralForm.get(numDownloads,
411 Strings.browser.GetStringFromName("alertDownloadMultiple"))
412 .replace("#1", numDownloads)
413 .replace("#2", progress)
414 .replace("#3", timeLeft);
415 },
417 _saveDownloadData: function dv_saveDownloadData(aDownload) {
418 if (!this._progressNotificationInfo.get(aDownload.guid)) {
419 this._progressNotificationInfo.set(aDownload.guid, {});
420 }
421 let infoObj = this._progressNotificationInfo.get(aDownload.guid);
422 infoObj.download = aDownload;
423 this._progressNotificationInfo.set(aDownload.guid, infoObj);
424 },
426 onDownloadButton: function dv_onDownloadButton() {
427 let progressNotification = this._getNotificationWithValue("download-progress");
428 let wasProgressVisible = (progressNotification &&
429 progressNotification.parentNode == this._notificationBox);
430 let completeNotification = this._getNotificationWithValue("download-complete");
431 let wasCompleteVisible = (completeNotification &&
432 completeNotification.parentNode == this._notificationBox);
434 this._removeNotification("download-complete");
435 this._removeNotification("download-progress");
437 if (this._downloadsInProgress && !wasProgressVisible) {
438 this.updateInfobar();
439 } else if (this._downloadCount && !wasCompleteVisible) {
440 this._showDownloadCompleteNotification();
441 }
442 },
444 _removeNotification: function (aValue) {
445 let notification = this._getNotificationWithValue(aValue);
446 return notification &&
447 notification.parentNode.removeNotification(notification);
448 },
450 updateInfobar: function dv_updateInfobar() {
451 let message = this._computeDownloadProgressString();
452 this._updateCircularProgressMeter();
454 let notn = this._progressNotification;
455 if (!notn) {
456 let cancelButtonText =
457 Strings.browser.GetStringFromName("downloadCancel");
459 let buttons = [
460 {
461 isDefault: false,
462 label: cancelButtonText,
463 accessKey: "",
464 callback: function() {
465 MetroDownloadsView.cancelDownloads();
466 MetroDownloadsView._downloadProgressIndicator.reset();
467 }
468 }
469 ];
471 notn = this.showNotification("download-progress", message, buttons,
472 this._notificationBox.PRIORITY_WARNING_LOW);
474 ContextUI.displayNavbar();
475 } else {
476 notn.label = message;
477 }
478 },
480 updateDownload: function dv_updateDownload(aDownload) {
481 this._saveDownloadData(aDownload);
482 let notn = this._progressNotification;
483 if (notn) {
484 notn.label =
485 this._computeDownloadProgressString(aDownload);
486 }
487 this._updateCircularProgressMeter();
488 },
490 watchDownload: function dv_watchDownload(aDownload) {
491 this._saveDownloadData(aDownload);
492 this._downloadCount++;
493 this._downloadsInProgress++;
494 if (!this._progressNotificationInfo.get(aDownload.guid)) {
495 this._progressNotificationInfo.set(aDownload.guid, {});
496 }
497 if (!this._progressAlert) {
498 this._progressAlert = new AlertDownloadProgressListener();
499 this.manager.addListener(this._progressAlert);
500 }
501 },
503 observe: function (aSubject, aTopic, aData) {
504 let message = "";
505 let msgTitle = "";
507 switch (aTopic) {
508 case "dl-run":
509 let file = aSubject.QueryInterface(Ci.nsIFile);
510 this._runDownloadBooleanMap.set(file.path, (aData == 'true'));
511 break;
512 case "dl-start":
513 let download = aSubject.QueryInterface(Ci.nsIDownload);
514 this.watchDownload(download);
515 this.updateInfobar();
516 break;
517 case "dl-done":
518 this._downloadsInProgress--;
519 download = aSubject.QueryInterface(Ci.nsIDownload);
520 this._lastDownload = download;
521 let runAfterDownload = this._runDownloadBooleanMap.get(download.targetFile.path);
522 if (runAfterDownload) {
523 this.openDownload(download);
524 }
526 this._runDownloadBooleanMap.delete(download.targetFile.path);
527 if (this._downloadsInProgress == 0) {
528 if (this._downloadCount > 1 || !runAfterDownload) {
529 this._showDownloadCompleteToast();
530 this._showDownloadCompleteNotification();
531 }
532 let notn = this._progressNotification;
533 if (notn)
534 this._notificationBox.removeNotification(notn);
536 ContextUI.displayNavbar();
537 }
539 this._downloadProgressIndicator.notify();
540 break;
541 case "dl-failed":
542 download = aSubject.QueryInterface(Ci.nsIDownload);
543 this._showDownloadFailedNotification(download);
544 break;
545 }
546 },
548 handleEvent: function(aEvent) {
549 switch (aEvent.type) {
550 case 'TabClose': {
551 let browser = aEvent.originalTarget.linkedBrowser;
552 let tab = Browser.getTabForBrowser(browser);
553 let notificationBox = Browser.getNotificationBox(browser);
555 // move any download-related notification before the tab and its notificationBox goes away
556 // The 3 possible values should be mutually exclusive
557 for(let name of ["download-progress",
558 "save-download",
559 "download-complete"]) {
560 let notn = notificationBox.getNotificationWithValue(name);
561 if (!notn) {
562 continue;
563 }
565 let nextTab = Browser.getNextTab(tab);
566 let nextBox = nextTab && Browser.getNotificationBox(nextTab.browser);
567 if (nextBox) {
568 // move notification to the next tab
569 nextBox.adoptNotification(notn);
570 } else {
571 // Alas, no browser to move the notifications to.
572 }
573 }
574 break;
575 }
576 }
577 },
579 QueryInterface: function (aIID) {
580 if (!aIID.equals(Ci.nsIObserver) &&
581 !aIID.equals(Ci.nsISupportsWeakReference) &&
582 !aIID.equals(Ci.nsISupports))
583 throw Components.results.NS_ERROR_NO_INTERFACE;
584 return this;
585 }
586 };
589 /**
590 * Notifies Downloads object about updates in the state of various downloads.
591 *
592 * @param aDownloads An instance of Downloads.
593 */
594 function DownloadProgressListener(aDownloads) {
595 this._downloads = aDownloads;
596 }
598 DownloadProgressListener.prototype = {
599 _downloads: null,
601 //////////////////////////////////////////////////////////////////////////////
602 //// nsIDownloadProgressListener
603 onDownloadStateChange: function dPL_onDownloadStateChange(aState, aDownload) {
604 // TODO: Use DownloadProgressListener instead of observers in the Downloads object.
605 this._downloads.updateDownload(aDownload);
606 },
608 onProgressChange: function dPL_onProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, aDownload) {
609 // TODO <jwilde>: Add more detailed progress information.
610 this._downloads.updateDownload(aDownload);
611 },
613 onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { },
614 onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { },
616 //////////////////////////////////////////////////////////////////////////////
617 //// nsISupports
618 QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadProgressListener])
619 };
622 /**
623 * Tracks download progress so that additional information can be displayed
624 * about its download in alert popups.
625 */
626 function AlertDownloadProgressListener() { }
628 AlertDownloadProgressListener.prototype = {
629 //////////////////////////////////////////////////////////////////////////////
630 //// nsIDownloadProgressListener
631 onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, aDownload) {
632 let strings = Strings.browser;
633 let availableSpace = -1;
635 try {
636 // diskSpaceAvailable is not implemented on all systems
637 let availableSpace = aDownload.targetFile.diskSpaceAvailable;
638 } catch(ex) { }
640 let contentLength = aDownload.size;
641 if (availableSpace > 0 && contentLength > 0 && contentLength > availableSpace) {
642 MetroDownloadsView.showAlert(aDownload.target.spec.replace("file:", "download:"),
643 strings.GetStringFromName("alertDownloadsNoSpace"),
644 strings.GetStringFromName("alertDownloadsSize"));
645 MetroDownloadsView.cancelDownload(aDownload);
646 }
647 },
649 onDownloadStateChange: function(aState, aDownload) { },
650 onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { },
651 onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { },
653 //////////////////////////////////////////////////////////////////////////////
654 //// nsISupports
655 QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadProgressListener])
656 };