mobile/android/chrome/content/downloads.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/chrome/content/downloads.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,298 @@
     1.4 +// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +"use strict";
    1.10 +
    1.11 +let Cu = Components.utils;
    1.12 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.13 +
    1.14 +function dump(a) {
    1.15 +  Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a);
    1.16 +}
    1.17 +
    1.18 +XPCOMUtils.defineLazyModuleGetter(this, "Notifications",
    1.19 +                                  "resource://gre/modules/Notifications.jsm");
    1.20 +
    1.21 +const URI_GENERIC_ICON_DOWNLOAD = "drawable://alert_download";
    1.22 +const URI_PAUSE_ICON = "drawable://pause";
    1.23 +const URI_CANCEL_ICON = "drawable://close";
    1.24 +const URI_RESUME_ICON = "drawable://play";
    1.25 +
    1.26 +
    1.27 +XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
    1.28 +
    1.29 +var Downloads = {
    1.30 +  _initialized: false,
    1.31 +  _dlmgr: null,
    1.32 +  _progressAlert: null,
    1.33 +  _privateDownloads: [],
    1.34 +  _showingPrompt: false,
    1.35 +  _downloadsIdMap: {},
    1.36 +
    1.37 +  _getLocalFile: function dl__getLocalFile(aFileURI) {
    1.38 +    // if this is a URL, get the file from that
    1.39 +    // XXX it's possible that using a null char-set here is bad
    1.40 +    const fileUrl = Services.io.newURI(aFileURI, null, null).QueryInterface(Ci.nsIFileURL);
    1.41 +    return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile);
    1.42 +  },
    1.43 +
    1.44 +  init: function dl_init() {
    1.45 +    if (this._initialized)
    1.46 +      return;
    1.47 +    this._initialized = true;
    1.48 +
    1.49 +    // Monitor downloads and display alerts
    1.50 +    this._dlmgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
    1.51 +    this._progressAlert = new AlertDownloadProgressListener();
    1.52 +    this._dlmgr.addPrivacyAwareListener(this._progressAlert);
    1.53 +    Services.obs.addObserver(this, "last-pb-context-exited", true);
    1.54 +  },
    1.55 +
    1.56 +  openDownload: function dl_openDownload(aDownload) {
    1.57 +    let fileUri = aDownload.target.spec;
    1.58 +    let guid = aDownload.guid;
    1.59 +    let f = this._getLocalFile(fileUri);
    1.60 +    try {
    1.61 +      f.launch();
    1.62 +    } catch (ex) {
    1.63 +      // in case we are not able to open the file (i.e. there is no app able to handle it)
    1.64 +      // we just open the browser tab showing it 
    1.65 +      BrowserApp.addTab("about:downloads?id=" + guid);
    1.66 +    }
    1.67 +  },
    1.68 +
    1.69 +  cancelDownload: function dl_cancelDownload(aDownload) {
    1.70 +    aDownload.cancel();
    1.71 +    let fileURI = aDownload.target.spec;
    1.72 +    let f = this._getLocalFile(fileURI);
    1.73 +
    1.74 +    OS.File.remove(f.path);
    1.75 +  },
    1.76 +
    1.77 +  showCancelConfirmPrompt: function dl_showCancelConfirmPrompt(aDownload) {
    1.78 +    if (this._showingPrompt)
    1.79 +      return;
    1.80 +    this._showingPrompt = true;
    1.81 +    // Open a prompt that offers a choice to cancel the download
    1.82 +    let title = Strings.browser.GetStringFromName("downloadCancelPromptTitle");
    1.83 +    let message = Strings.browser.GetStringFromName("downloadCancelPromptMessage");
    1.84 +    let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_YES +
    1.85 +                Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_NO;
    1.86 +    let choice = Services.prompt.confirmEx(null, title, message, flags,
    1.87 +                                           null, null, null, null, {});
    1.88 +    if (choice == 0)
    1.89 +      this.cancelDownload(aDownload);
    1.90 +    this._showingPrompt = false;
    1.91 +  },
    1.92 +
    1.93 +  handleClickEvent: function dl_handleClickEvent(aDownload) {
    1.94 +    // Only open the downloaded file if the download is complete
    1.95 +    if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED)
    1.96 +      this.openDownload(aDownload);
    1.97 +    else if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING ||
    1.98 +                aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_PAUSED)
    1.99 +      this.showCancelConfirmPrompt(aDownload);
   1.100 +  },
   1.101 +
   1.102 +  clickCallback: function dl_clickCallback(aDownloadId) {
   1.103 +    this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) {
   1.104 +          if (Components.isSuccessCode(status))
   1.105 +            this.handleClickEvent(download);
   1.106 +        }).bind(this));
   1.107 +  },
   1.108 +
   1.109 +  pauseClickCallback: function dl_buttonPauseCallback(aDownloadId) {
   1.110 +    this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) {
   1.111 +          if (Components.isSuccessCode(status))
   1.112 +            download.pause();
   1.113 +        }).bind(this));
   1.114 +  },
   1.115 +
   1.116 +  resumeClickCallback: function dl_buttonPauseCallback(aDownloadId) {
   1.117 +    this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) {
   1.118 +          if (Components.isSuccessCode(status))
   1.119 +            download.resume();
   1.120 +        }).bind(this));
   1.121 +  },
   1.122 +
   1.123 +  cancelClickCallback: function dl_buttonPauseCallback(aDownloadId) {
   1.124 +    this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) {
   1.125 +          if (Components.isSuccessCode(status))
   1.126 +            this.cancelDownload(download);
   1.127 +        }).bind(this));
   1.128 +  },
   1.129 +
   1.130 +  notificationCanceledCallback: function dl_notifCancelCallback(aId, aDownloadId) {
   1.131 +    let notificationId = this._downloadsIdMap[aDownloadId];
   1.132 +    if (notificationId && notificationId == aId)
   1.133 +      delete this._downloadsIdMap[aDownloadId];
   1.134 +  },
   1.135 +
   1.136 +  createNotification: function dl_createNotif(aDownload, aOptions) {
   1.137 +    let notificationId = Notifications.create(aOptions);
   1.138 +    this._downloadsIdMap[aDownload.guid] = notificationId;
   1.139 +  },
   1.140 +
   1.141 +  updateNotification: function dl_updateNotif(aDownload, aOptions) {
   1.142 +    let notificationId = this._downloadsIdMap[aDownload.guid];
   1.143 +    if (notificationId)
   1.144 +      Notifications.update(notificationId, aOptions);
   1.145 +  },
   1.146 +
   1.147 +  cancelNotification: function dl_cleanNotif(aDownload) {
   1.148 +    Notifications.cancel(this._downloadsIdMap[aDownload.guid]);
   1.149 +    delete this._downloadsIdMap[aDownload.guid];
   1.150 +  },
   1.151 +
   1.152 +  // observer for last-pb-context-exited
   1.153 +  observe: function dl_observe(aSubject, aTopic, aData) {
   1.154 +    let download;
   1.155 +    while ((download = this._privateDownloads.pop())) {
   1.156 +      try {
   1.157 +        let notificationId = aDownload.guid;
   1.158 +        Notifications.clear(notificationId);
   1.159 +        Downloads.removeNotification(download);
   1.160 +      } catch (e) {
   1.161 +        dump("Error removing private download: " + e);
   1.162 +      }
   1.163 +    }
   1.164 +  },
   1.165 +
   1.166 +  QueryInterface: function (aIID) {
   1.167 +    if (!aIID.equals(Ci.nsISupports) &&
   1.168 +        !aIID.equals(Ci.nsIObserver) &&
   1.169 +        !aIID.equals(Ci.nsISupportsWeakReference))
   1.170 +      throw Components.results.NS_ERROR_NO_INTERFACE;
   1.171 +    return this;
   1.172 +  }
   1.173 +};
   1.174 +
   1.175 +const PAUSE_BUTTON = {
   1.176 +  buttonId: "pause",
   1.177 +  title : Strings.browser.GetStringFromName("alertDownloadsPause"),
   1.178 +  icon : URI_PAUSE_ICON,
   1.179 +  onClicked: function (aId, aCookie) {
   1.180 +    Downloads.pauseClickCallback(aCookie);
   1.181 +  }
   1.182 +};
   1.183 +
   1.184 +const CANCEL_BUTTON = {
   1.185 +  buttonId: "cancel",
   1.186 +  title : Strings.browser.GetStringFromName("alertDownloadsCancel"),
   1.187 +  icon : URI_CANCEL_ICON,
   1.188 +  onClicked: function (aId, aCookie) {
   1.189 +    Downloads.cancelClickCallback(aCookie);
   1.190 +  }
   1.191 +};
   1.192 +
   1.193 +const RESUME_BUTTON = {
   1.194 +  buttonId: "resume",
   1.195 +  title : Strings.browser.GetStringFromName("alertDownloadsResume"),
   1.196 +  icon: URI_RESUME_ICON,
   1.197 +  onClicked: function (aId, aCookie) {
   1.198 +    Downloads.resumeClickCallback(aCookie);
   1.199 +  }
   1.200 +};
   1.201 +
   1.202 +function DownloadNotifOptions (aDownload, aTitle, aMessage) {
   1.203 +  this.icon = URI_GENERIC_ICON_DOWNLOAD;
   1.204 +  this.onCancel = function (aId, aCookie) {
   1.205 +    Downloads.notificationCanceledCallback(aId, aCookie);
   1.206 +  }
   1.207 +  this.onClick = function (aId, aCookie) {
   1.208 +    Downloads.clickCallback(aCookie);
   1.209 +  }
   1.210 +  this.title = aTitle;
   1.211 +  this.message = aMessage;
   1.212 +  this.buttons = null;
   1.213 +  this.cookie = aDownload.guid;
   1.214 +  this.persistent = true;
   1.215 +}
   1.216 +
   1.217 +function DownloadProgressNotifOptions (aDownload, aButtons) {
   1.218 +  DownloadNotifOptions.apply(this, [aDownload, aDownload.displayName, aDownload.percentComplete + "%"]);
   1.219 +  this.ongoing = true;
   1.220 +  this.progress = aDownload.percentComplete;
   1.221 +  this.buttons = aButtons;
   1.222 +}
   1.223 +
   1.224 +// AlertDownloadProgressListener is used to display progress in the alert notifications.
   1.225 +function AlertDownloadProgressListener() { }
   1.226 +
   1.227 +AlertDownloadProgressListener.prototype = {
   1.228 +  //////////////////////////////////////////////////////////////////////////////
   1.229 +  //// nsIDownloadProgressListener
   1.230 +  onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, aDownload) {
   1.231 +    let strings = Strings.browser;
   1.232 +    let availableSpace = -1;
   1.233 +    try {
   1.234 +      // diskSpaceAvailable is not implemented on all systems
   1.235 +      let availableSpace = aDownload.targetFile.diskSpaceAvailable;
   1.236 +    } catch(ex) { }
   1.237 +    let contentLength = aDownload.size;
   1.238 +    if (availableSpace > 0 && contentLength > 0 && contentLength > availableSpace) {
   1.239 +      Downloads.updateNotification(aDownload, new DownloadNotifOptions(aDownload,
   1.240 +                                                                        strings.GetStringFromName("alertDownloadsNoSpace"),
   1.241 +                                                                        strings.GetStringFromName("alertDownloadsSize")));
   1.242 +      aDownload.cancel();
   1.243 +    }
   1.244 +
   1.245 +    if (aDownload.percentComplete == -1) {
   1.246 +      // Undetermined progress is not supported yet
   1.247 +      return;
   1.248 +    }
   1.249 +
   1.250 +    Downloads.updateNotification(aDownload, new DownloadProgressNotifOptions(aDownload, [PAUSE_BUTTON, CANCEL_BUTTON]));
   1.251 +  },
   1.252 +
   1.253 +  onDownloadStateChange: function(aState, aDownload) {
   1.254 +    let state = aDownload.state;
   1.255 +    switch (state) {
   1.256 +      case Ci.nsIDownloadManager.DOWNLOAD_QUEUED: {
   1.257 +        NativeWindow.toast.show(Strings.browser.GetStringFromName("alertDownloadsToast"), "long");
   1.258 +        Downloads.createNotification(aDownload, new DownloadNotifOptions(aDownload,
   1.259 +                                                                         Strings.browser.GetStringFromName("alertDownloadsStart2"),
   1.260 +                                                                         aDownload.displayName));
   1.261 +        break;
   1.262 +      }
   1.263 +      case Ci.nsIDownloadManager.DOWNLOAD_PAUSED: {
   1.264 +        Downloads.updateNotification(aDownload, new DownloadProgressNotifOptions(aDownload, [RESUME_BUTTON, CANCEL_BUTTON]));
   1.265 +        break;
   1.266 +      }
   1.267 +      case Ci.nsIDownloadManager.DOWNLOAD_FAILED:
   1.268 +      case Ci.nsIDownloadManager.DOWNLOAD_CANCELED:
   1.269 +      case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL:
   1.270 +      case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
   1.271 +      case Ci.nsIDownloadManager.DOWNLOAD_FINISHED: {
   1.272 +        Downloads.cancelNotification(aDownload);
   1.273 +        if (aDownload.isPrivate) {
   1.274 +          let index = Downloads._privateDownloads.indexOf(aDownload);
   1.275 +          if (index != -1) {
   1.276 +            Downloads._privateDownloads.splice(index, 1);
   1.277 +          }
   1.278 +        }
   1.279 +
   1.280 +        if (state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) {
   1.281 +          Downloads.createNotification(aDownload, new DownloadNotifOptions(aDownload,
   1.282 +                                                                Strings.browser.GetStringFromName("alertDownloadsDone2"),
   1.283 +                                                                aDownload.displayName));
   1.284 +        }
   1.285 +        break;
   1.286 +      }
   1.287 +    }
   1.288 +  },
   1.289 +
   1.290 +  onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { },
   1.291 +  onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { },
   1.292 +
   1.293 +  //////////////////////////////////////////////////////////////////////////////
   1.294 +  //// nsISupports
   1.295 +  QueryInterface: function (aIID) {
   1.296 +    if (!aIID.equals(Ci.nsIDownloadProgressListener) &&
   1.297 +        !aIID.equals(Ci.nsISupports))
   1.298 +      throw Components.results.NS_ERROR_NO_INTERFACE;
   1.299 +    return this;
   1.300 +  }
   1.301 +};

mercurial