diff -r 000000000000 -r 6474c204b198 dom/downloads/src/DownloadsAPI.jsm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dom/downloads/src/DownloadsAPI.jsm Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,268 @@ +/* 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"; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +this.EXPORTED_SYMBOLS = []; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Downloads.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); + +XPCOMUtils.defineLazyServiceGetter(this, "ppmm", + "@mozilla.org/parentprocessmessagemanager;1", + "nsIMessageBroadcaster"); + +function debug(aStr) { +#ifdef MOZ_DEBUG + dump("-*- DownloadsAPI.jsm : " + aStr + "\n"); +#endif +} + +function sendPromiseMessage(aMm, aMessageName, aData, aError) { + debug("sendPromiseMessage " + aMessageName); + let msg = { + id: aData.id, + promiseId: aData.promiseId + }; + + if (aError) { + msg.error = aError; + } + + aMm.sendAsyncMessage(aMessageName, msg); +} + +let DownloadsAPI = { + init: function() { + debug("init"); + + this._ids = new WeakMap(); // Maps toolkit download objects to ids. + this._index = {}; // Maps ids to downloads. + + ["Downloads:GetList", + "Downloads:ClearAllDone", + "Downloads:Remove", + "Downloads:Pause", + "Downloads:Resume"].forEach((msgName) => { + ppmm.addMessageListener(msgName, this); + }); + + let self = this; + Task.spawn(function () { + let list = yield Downloads.getList(Downloads.ALL); + yield list.addView(self); + + debug("view added to download list."); + }).then(null, Components.utils.reportError); + + this._currentId = 0; + }, + + /** + * Returns a unique id for each download, hashing the url and the path. + */ + downloadId: function(aDownload) { + let id = this._ids.get(aDownload, null); + if (!id) { + id = "download-" + this._currentId++; + this._ids.set(aDownload, id); + this._index[id] = aDownload; + } + return id; + }, + + getDownloadById: function(aId) { + return this._index[aId]; + }, + + /** + * Converts a download object into a plain json object that we'll + * send to the DOM side. + */ + jsonDownload: function(aDownload) { + let res = { + totalBytes: aDownload.totalBytes, + currentBytes: aDownload.currentBytes, + url: aDownload.source.url, + path: aDownload.target.path, + contentType: aDownload.contentType, + startTime: aDownload.startTime.getTime() + }; + + if (aDownload.error) { + res.error = aDownload.error; + } + + res.id = this.downloadId(aDownload); + + // The state of the download. Can be any of "downloading", "stopped", + // "succeeded", finalized". + + // Default to "stopped" + res.state = "stopped"; + if (!aDownload.stopped && + !aDownload.canceled && + !aDownload.succeeded && + !aDownload.DownloadError) { + res.state = "downloading"; + } else if (aDownload.succeeded) { + res.state = "succeeded"; + } + return res; + }, + + /** + * download view methods. + */ + onDownloadAdded: function(aDownload) { + let download = this.jsonDownload(aDownload); + debug("onDownloadAdded " + uneval(download)); + ppmm.broadcastAsyncMessage("Downloads:Added", download); + }, + + onDownloadRemoved: function(aDownload) { + let download = this.jsonDownload(aDownload); + download.state = "finalized"; + debug("onDownloadRemoved " + uneval(download)); + ppmm.broadcastAsyncMessage("Downloads:Removed", download); + this._index[this._ids.get(aDownload)] = null; + this._ids.delete(aDownload); + }, + + onDownloadChanged: function(aDownload) { + let download = this.jsonDownload(aDownload); + debug("onDownloadChanged " + uneval(download)); + ppmm.broadcastAsyncMessage("Downloads:Changed", download); + }, + + receiveMessage: function(aMessage) { + if (!aMessage.target.assertPermission("downloads")) { + debug("No 'downloads' permission!"); + return; + } + + debug("message: " + aMessage.name); + + switch (aMessage.name) { + case "Downloads:GetList": + this.getList(aMessage.data, aMessage.target); + break; + case "Downloads:ClearAllDone": + this.clearAllDone(aMessage.data, aMessage.target); + break; + case "Downloads:Remove": + this.remove(aMessage.data, aMessage.target); + break; + case "Downloads:Pause": + this.pause(aMessage.data, aMessage.target); + break; + case "Downloads:Resume": + this.resume(aMessage.data, aMessage.target); + break; + default: + debug("Invalid message: " + aMessage.name); + } + }, + + getList: function(aData, aMm) { + debug("getList called!"); + let self = this; + Task.spawn(function () { + let list = yield Downloads.getList(Downloads.ALL); + let downloads = yield list.getAll(); + let res = []; + downloads.forEach((aDownload) => { + res.push(self.jsonDownload(aDownload)); + }); + aMm.sendAsyncMessage("Downloads:GetList:Return", res); + }).then(null, Components.utils.reportError); + }, + + clearAllDone: function(aData, aMm) { + debug("clearAllDone called!"); + let self = this; + Task.spawn(function () { + let list = yield Downloads.getList(Downloads.ALL); + yield list.removeFinished(); + list = yield Downloads.getList(Downloads.ALL); + let downloads = yield list.getAll(); + let res = []; + downloads.forEach((aDownload) => { + res.push(self.jsonDownload(aDownload)); + }); + aMm.sendAsyncMessage("Downloads:ClearAllDone:Return", res); + }).then(null, Components.utils.reportError); + }, + + remove: function(aData, aMm) { + debug("remove id " + aData.id); + let download = this.getDownloadById(aData.id); + if (!download) { + sendPromiseMessage(aMm, "Downloads:Remove:Return", + aData, "NoSuchDownload"); + return; + } + + Task.spawn(function() { + yield download.finalize(true); + let list = yield Downloads.getList(Downloads.ALL); + yield list.remove(download); + }).then( + function() { + sendPromiseMessage(aMm, "Downloads:Remove:Return", aData); + }, + function() { + sendPromiseMessage(aMm, "Downloads:Remove:Return", + aData, "RemoveError"); + } + ); + }, + + pause: function(aData, aMm) { + debug("pause id " + aData.id); + let download = this.getDownloadById(aData.id); + if (!download) { + sendPromiseMessage(aMm, "Downloads:Pause:Return", + aData, "NoSuchDownload"); + return; + } + + download.cancel().then( + function() { + sendPromiseMessage(aMm, "Downloads:Pause:Return", aData); + }, + function() { + sendPromiseMessage(aMm, "Downloads:Pause:Return", + aData, "PauseError"); + } + ); + }, + + resume: function(aData, aMm) { + debug("resume id " + aData.id); + let download = this.getDownloadById(aData.id); + if (!download) { + sendPromiseMessage(aMm, "Downloads:Resume:Return", + aData, "NoSuchDownload"); + return; + } + + download.start().then( + function() { + sendPromiseMessage(aMm, "Downloads:Resume:Return", aData); + }, + function() { + sendPromiseMessage(aMm, "Downloads:Resume:Return", + aData, "ResumeError"); + } + ); + } +}; + +DownloadsAPI.init();