summaryrefslogtreecommitdiffstats
path: root/dom/downloads/DownloadsIPC.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'dom/downloads/DownloadsIPC.jsm')
-rw-r--r--dom/downloads/DownloadsIPC.jsm224
1 files changed, 224 insertions, 0 deletions
diff --git a/dom/downloads/DownloadsIPC.jsm b/dom/downloads/DownloadsIPC.jsm
new file mode 100644
index 000000000..0e290abf4
--- /dev/null
+++ b/dom/downloads/DownloadsIPC.jsm
@@ -0,0 +1,224 @@
+/* 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 = ["DownloadsIPC"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+ "@mozilla.org/childprocessmessagemanager;1",
+ "nsIMessageSender");
+
+/**
+ * This module lives in the child process and receives the ipc messages
+ * from the parent. It saves the download's state and redispatch changes
+ * to DOM objects using an observer notification.
+ *
+ * This module needs to be loaded once and only once per process.
+ */
+
+function debug(aStr) {
+#ifdef MOZ_DEBUG
+ dump("-*- DownloadsIPC.jsm : " + aStr + "\n");
+#endif
+}
+
+const ipcMessages = ["Downloads:Added",
+ "Downloads:Removed",
+ "Downloads:Changed",
+ "Downloads:GetList:Return",
+ "Downloads:Remove:Return",
+ "Downloads:Pause:Return",
+ "Downloads:Resume:Return",
+ "Downloads:Adopt:Return"];
+
+this.DownloadsIPC = {
+ downloads: {},
+
+ init: function() {
+ debug("init");
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+ ipcMessages.forEach((aMessage) => {
+ cpmm.addMessageListener(aMessage, this);
+ });
+
+ // We need to get the list of current downloads.
+ this.ready = false;
+ this.getListPromises = [];
+ this.downloadPromises = {};
+ cpmm.sendAsyncMessage("Downloads:GetList", {});
+ this._promiseId = 0;
+ },
+
+ notifyChanges: function(aId) {
+ // TODO: use the subject instead of stringifying.
+ if (this.downloads[aId]) {
+ debug("notifyChanges notifying changes for " + aId);
+ Services.obs.notifyObservers(null, "downloads-state-change-" + aId,
+ JSON.stringify(this.downloads[aId]));
+ } else {
+ debug("notifyChanges failed for " + aId)
+ }
+ },
+
+ _updateDownloadsArray: function(aDownloads) {
+ this.downloads = [];
+ // We actually have an array of downloads.
+ aDownloads.forEach((aDownload) => {
+ this.downloads[aDownload.id] = aDownload;
+ });
+ },
+
+ receiveMessage: function(aMessage) {
+ let download = aMessage.data;
+ debug("message: " + aMessage.name);
+ switch(aMessage.name) {
+ case "Downloads:GetList:Return":
+ this._updateDownloadsArray(download);
+
+ if (!this.ready) {
+ this.getListPromises.forEach(aPromise =>
+ aPromise.resolve(this.downloads));
+ this.getListPromises.length = 0;
+ }
+ this.ready = true;
+ break;
+ case "Downloads:Added":
+ this.downloads[download.id] = download;
+ this.notifyChanges(download.id);
+ break;
+ case "Downloads:Removed":
+ if (this.downloads[download.id]) {
+ this.downloads[download.id] = download;
+ this.notifyChanges(download.id);
+ delete this.downloads[download.id];
+ }
+ break;
+ case "Downloads:Changed":
+ // Only update properties that actually changed.
+ let cached = this.downloads[download.id];
+ if (!cached) {
+ debug("No download found for " + download.id);
+ return;
+ }
+ let props = ["totalBytes", "currentBytes", "url", "path", "state",
+ "contentType", "startTime"];
+ let changed = false;
+
+ props.forEach((aProp) => {
+ if (download[aProp] && (download[aProp] != cached[aProp])) {
+ cached[aProp] = download[aProp];
+ changed = true;
+ }
+ });
+
+ // Updating the error property. We always get a 'state' change as
+ // well.
+ cached.error = download.error;
+
+ if (changed) {
+ this.notifyChanges(download.id);
+ }
+ break;
+ case "Downloads:Remove:Return":
+ case "Downloads:Pause:Return":
+ case "Downloads:Resume:Return":
+ case "Downloads:Adopt:Return":
+ if (this.downloadPromises[download.promiseId]) {
+ if (!download.error) {
+ this.downloadPromises[download.promiseId].resolve(download);
+ } else {
+ this.downloadPromises[download.promiseId].reject(download);
+ }
+ delete this.downloadPromises[download.promiseId];
+ }
+ break;
+ }
+ },
+
+ /**
+ * Returns a promise that is resolved with the list of current downloads.
+ */
+ getDownloads: function() {
+ debug("getDownloads()");
+ let deferred = Promise.defer();
+ if (this.ready) {
+ debug("Returning existing list.");
+ deferred.resolve(this.downloads);
+ } else {
+ this.getListPromises.push(deferred);
+ }
+ return deferred.promise;
+ },
+
+ /**
+ * Void function to trigger removal of completed downloads.
+ */
+ clearAllDone: function() {
+ debug("clearAllDone");
+ cpmm.sendAsyncMessage("Downloads:ClearAllDone", {});
+ },
+
+ promiseId: function() {
+ return this._promiseId++;
+ },
+
+ remove: function(aId) {
+ debug("remove " + aId);
+ let deferred = Promise.defer();
+ let pId = this.promiseId();
+ this.downloadPromises[pId] = deferred;
+ cpmm.sendAsyncMessage("Downloads:Remove",
+ { id: aId, promiseId: pId });
+ return deferred.promise;
+ },
+
+ pause: function(aId) {
+ debug("pause " + aId);
+ let deferred = Promise.defer();
+ let pId = this.promiseId();
+ this.downloadPromises[pId] = deferred;
+ cpmm.sendAsyncMessage("Downloads:Pause",
+ { id: aId, promiseId: pId });
+ return deferred.promise;
+ },
+
+ resume: function(aId) {
+ debug("resume " + aId);
+ let deferred = Promise.defer();
+ let pId = this.promiseId();
+ this.downloadPromises[pId] = deferred;
+ cpmm.sendAsyncMessage("Downloads:Resume",
+ { id: aId, promiseId: pId });
+ return deferred.promise;
+ },
+
+ adoptDownload: function(aJsonDownload) {
+ debug("adoptDownload");
+ let deferred = Promise.defer();
+ let pId = this.promiseId();
+ this.downloadPromises[pId] = deferred;
+ cpmm.sendAsyncMessage("Downloads:Adopt",
+ { jsonDownload: aJsonDownload, promiseId: pId });
+ return deferred.promise;
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "xpcom-shutdown") {
+ ipcMessages.forEach((aMessage) => {
+ cpmm.removeMessageListener(aMessage, this);
+ });
+ }
+ }
+};
+
+DownloadsIPC.init();