diff -r 000000000000 -r 6474c204b198 mobile/android/chrome/content/downloads.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mobile/android/chrome/content/downloads.js Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,298 @@ +// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +let Cu = Components.utils; +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +function dump(a) { + Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a); +} + +XPCOMUtils.defineLazyModuleGetter(this, "Notifications", + "resource://gre/modules/Notifications.jsm"); + +const URI_GENERIC_ICON_DOWNLOAD = "drawable://alert_download"; +const URI_PAUSE_ICON = "drawable://pause"; +const URI_CANCEL_ICON = "drawable://close"; +const URI_RESUME_ICON = "drawable://play"; + + +XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); + +var Downloads = { + _initialized: false, + _dlmgr: null, + _progressAlert: null, + _privateDownloads: [], + _showingPrompt: false, + _downloadsIdMap: {}, + + _getLocalFile: function dl__getLocalFile(aFileURI) { + // if this is a URL, get the file from that + // XXX it's possible that using a null char-set here is bad + const fileUrl = Services.io.newURI(aFileURI, null, null).QueryInterface(Ci.nsIFileURL); + return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile); + }, + + init: function dl_init() { + if (this._initialized) + return; + this._initialized = true; + + // Monitor downloads and display alerts + this._dlmgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager); + this._progressAlert = new AlertDownloadProgressListener(); + this._dlmgr.addPrivacyAwareListener(this._progressAlert); + Services.obs.addObserver(this, "last-pb-context-exited", true); + }, + + openDownload: function dl_openDownload(aDownload) { + let fileUri = aDownload.target.spec; + let guid = aDownload.guid; + let f = this._getLocalFile(fileUri); + try { + f.launch(); + } catch (ex) { + // in case we are not able to open the file (i.e. there is no app able to handle it) + // we just open the browser tab showing it + BrowserApp.addTab("about:downloads?id=" + guid); + } + }, + + cancelDownload: function dl_cancelDownload(aDownload) { + aDownload.cancel(); + let fileURI = aDownload.target.spec; + let f = this._getLocalFile(fileURI); + + OS.File.remove(f.path); + }, + + showCancelConfirmPrompt: function dl_showCancelConfirmPrompt(aDownload) { + if (this._showingPrompt) + return; + this._showingPrompt = true; + // Open a prompt that offers a choice to cancel the download + let title = Strings.browser.GetStringFromName("downloadCancelPromptTitle"); + let message = Strings.browser.GetStringFromName("downloadCancelPromptMessage"); + let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_YES + + Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_NO; + let choice = Services.prompt.confirmEx(null, title, message, flags, + null, null, null, null, {}); + if (choice == 0) + this.cancelDownload(aDownload); + this._showingPrompt = false; + }, + + handleClickEvent: function dl_handleClickEvent(aDownload) { + // Only open the downloaded file if the download is complete + if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) + this.openDownload(aDownload); + else if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING || + aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_PAUSED) + this.showCancelConfirmPrompt(aDownload); + }, + + clickCallback: function dl_clickCallback(aDownloadId) { + this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) { + if (Components.isSuccessCode(status)) + this.handleClickEvent(download); + }).bind(this)); + }, + + pauseClickCallback: function dl_buttonPauseCallback(aDownloadId) { + this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) { + if (Components.isSuccessCode(status)) + download.pause(); + }).bind(this)); + }, + + resumeClickCallback: function dl_buttonPauseCallback(aDownloadId) { + this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) { + if (Components.isSuccessCode(status)) + download.resume(); + }).bind(this)); + }, + + cancelClickCallback: function dl_buttonPauseCallback(aDownloadId) { + this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) { + if (Components.isSuccessCode(status)) + this.cancelDownload(download); + }).bind(this)); + }, + + notificationCanceledCallback: function dl_notifCancelCallback(aId, aDownloadId) { + let notificationId = this._downloadsIdMap[aDownloadId]; + if (notificationId && notificationId == aId) + delete this._downloadsIdMap[aDownloadId]; + }, + + createNotification: function dl_createNotif(aDownload, aOptions) { + let notificationId = Notifications.create(aOptions); + this._downloadsIdMap[aDownload.guid] = notificationId; + }, + + updateNotification: function dl_updateNotif(aDownload, aOptions) { + let notificationId = this._downloadsIdMap[aDownload.guid]; + if (notificationId) + Notifications.update(notificationId, aOptions); + }, + + cancelNotification: function dl_cleanNotif(aDownload) { + Notifications.cancel(this._downloadsIdMap[aDownload.guid]); + delete this._downloadsIdMap[aDownload.guid]; + }, + + // observer for last-pb-context-exited + observe: function dl_observe(aSubject, aTopic, aData) { + let download; + while ((download = this._privateDownloads.pop())) { + try { + let notificationId = aDownload.guid; + Notifications.clear(notificationId); + Downloads.removeNotification(download); + } catch (e) { + dump("Error removing private download: " + e); + } + } + }, + + QueryInterface: function (aIID) { + if (!aIID.equals(Ci.nsISupports) && + !aIID.equals(Ci.nsIObserver) && + !aIID.equals(Ci.nsISupportsWeakReference)) + throw Components.results.NS_ERROR_NO_INTERFACE; + return this; + } +}; + +const PAUSE_BUTTON = { + buttonId: "pause", + title : Strings.browser.GetStringFromName("alertDownloadsPause"), + icon : URI_PAUSE_ICON, + onClicked: function (aId, aCookie) { + Downloads.pauseClickCallback(aCookie); + } +}; + +const CANCEL_BUTTON = { + buttonId: "cancel", + title : Strings.browser.GetStringFromName("alertDownloadsCancel"), + icon : URI_CANCEL_ICON, + onClicked: function (aId, aCookie) { + Downloads.cancelClickCallback(aCookie); + } +}; + +const RESUME_BUTTON = { + buttonId: "resume", + title : Strings.browser.GetStringFromName("alertDownloadsResume"), + icon: URI_RESUME_ICON, + onClicked: function (aId, aCookie) { + Downloads.resumeClickCallback(aCookie); + } +}; + +function DownloadNotifOptions (aDownload, aTitle, aMessage) { + this.icon = URI_GENERIC_ICON_DOWNLOAD; + this.onCancel = function (aId, aCookie) { + Downloads.notificationCanceledCallback(aId, aCookie); + } + this.onClick = function (aId, aCookie) { + Downloads.clickCallback(aCookie); + } + this.title = aTitle; + this.message = aMessage; + this.buttons = null; + this.cookie = aDownload.guid; + this.persistent = true; +} + +function DownloadProgressNotifOptions (aDownload, aButtons) { + DownloadNotifOptions.apply(this, [aDownload, aDownload.displayName, aDownload.percentComplete + "%"]); + this.ongoing = true; + this.progress = aDownload.percentComplete; + this.buttons = aButtons; +} + +// AlertDownloadProgressListener is used to display progress in the alert notifications. +function AlertDownloadProgressListener() { } + +AlertDownloadProgressListener.prototype = { + ////////////////////////////////////////////////////////////////////////////// + //// nsIDownloadProgressListener + onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, aDownload) { + let strings = Strings.browser; + let availableSpace = -1; + try { + // diskSpaceAvailable is not implemented on all systems + let availableSpace = aDownload.targetFile.diskSpaceAvailable; + } catch(ex) { } + let contentLength = aDownload.size; + if (availableSpace > 0 && contentLength > 0 && contentLength > availableSpace) { + Downloads.updateNotification(aDownload, new DownloadNotifOptions(aDownload, + strings.GetStringFromName("alertDownloadsNoSpace"), + strings.GetStringFromName("alertDownloadsSize"))); + aDownload.cancel(); + } + + if (aDownload.percentComplete == -1) { + // Undetermined progress is not supported yet + return; + } + + Downloads.updateNotification(aDownload, new DownloadProgressNotifOptions(aDownload, [PAUSE_BUTTON, CANCEL_BUTTON])); + }, + + onDownloadStateChange: function(aState, aDownload) { + let state = aDownload.state; + switch (state) { + case Ci.nsIDownloadManager.DOWNLOAD_QUEUED: { + NativeWindow.toast.show(Strings.browser.GetStringFromName("alertDownloadsToast"), "long"); + Downloads.createNotification(aDownload, new DownloadNotifOptions(aDownload, + Strings.browser.GetStringFromName("alertDownloadsStart2"), + aDownload.displayName)); + break; + } + case Ci.nsIDownloadManager.DOWNLOAD_PAUSED: { + Downloads.updateNotification(aDownload, new DownloadProgressNotifOptions(aDownload, [RESUME_BUTTON, CANCEL_BUTTON])); + break; + } + case Ci.nsIDownloadManager.DOWNLOAD_FAILED: + case Ci.nsIDownloadManager.DOWNLOAD_CANCELED: + case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL: + case Ci.nsIDownloadManager.DOWNLOAD_DIRTY: + case Ci.nsIDownloadManager.DOWNLOAD_FINISHED: { + Downloads.cancelNotification(aDownload); + if (aDownload.isPrivate) { + let index = Downloads._privateDownloads.indexOf(aDownload); + if (index != -1) { + Downloads._privateDownloads.splice(index, 1); + } + } + + if (state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) { + Downloads.createNotification(aDownload, new DownloadNotifOptions(aDownload, + Strings.browser.GetStringFromName("alertDownloadsDone2"), + aDownload.displayName)); + } + break; + } + } + }, + + onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { }, + onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { }, + + ////////////////////////////////////////////////////////////////////////////// + //// nsISupports + QueryInterface: function (aIID) { + if (!aIID.equals(Ci.nsIDownloadProgressListener) && + !aIID.equals(Ci.nsISupports)) + throw Components.results.NS_ERROR_NO_INTERFACE; + return this; + } +};