browser/metro/base/content/downloads.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

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 };

mercurial