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