1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/downloads/src/DownloadsIPC.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,223 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const Cc = Components.classes; 1.11 +const Ci = Components.interfaces; 1.12 +const Cu = Components.utils; 1.13 + 1.14 +this.EXPORTED_SYMBOLS = ["DownloadsIPC"]; 1.15 + 1.16 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.17 +Cu.import("resource://gre/modules/Services.jsm"); 1.18 +Cu.import("resource://gre/modules/Promise.jsm"); 1.19 + 1.20 +XPCOMUtils.defineLazyServiceGetter(this, "cpmm", 1.21 + "@mozilla.org/childprocessmessagemanager;1", 1.22 + "nsIMessageSender"); 1.23 + 1.24 +/** 1.25 + * This module lives in the child process and receives the ipc messages 1.26 + * from the parent. It saves the download's state and redispatch changes 1.27 + * to DOM objects using an observer notification. 1.28 + * 1.29 + * This module needs to be loaded once and only once per process. 1.30 + */ 1.31 + 1.32 +function debug(aStr) { 1.33 +#ifdef MOZ_DEBUG 1.34 + dump("-*- DownloadsIPC.jsm : " + aStr + "\n"); 1.35 +#endif 1.36 +} 1.37 + 1.38 +const ipcMessages = ["Downloads:Added", 1.39 + "Downloads:Removed", 1.40 + "Downloads:Changed", 1.41 + "Downloads:GetList:Return", 1.42 + "Downloads:ClearAllDone:Return", 1.43 + "Downloads:Remove:Return", 1.44 + "Downloads:Pause:Return", 1.45 + "Downloads:Resume:Return"]; 1.46 + 1.47 +this.DownloadsIPC = { 1.48 + downloads: {}, 1.49 + 1.50 + init: function() { 1.51 + debug("init"); 1.52 + Services.obs.addObserver(this, "xpcom-shutdown", false); 1.53 + ipcMessages.forEach((aMessage) => { 1.54 + cpmm.addMessageListener(aMessage, this); 1.55 + }); 1.56 + 1.57 + // We need to get the list of current downloads. 1.58 + this.ready = false; 1.59 + this.getListPromises = []; 1.60 + this.clearAllPromises = []; 1.61 + this.downloadPromises = {}; 1.62 + cpmm.sendAsyncMessage("Downloads:GetList", {}); 1.63 + this._promiseId = 0; 1.64 + }, 1.65 + 1.66 + notifyChanges: function(aId) { 1.67 + // TODO: use the subject instead of stringifying. 1.68 + if (this.downloads[aId]) { 1.69 + debug("notifyChanges notifying changes for " + aId); 1.70 + Services.obs.notifyObservers(null, "downloads-state-change-" + aId, 1.71 + JSON.stringify(this.downloads[aId])); 1.72 + } else { 1.73 + debug("notifyChanges failed for " + aId) 1.74 + } 1.75 + }, 1.76 + 1.77 + _updateDownloadsArray: function(aDownloads) { 1.78 + this.downloads = []; 1.79 + // We actually have an array of downloads. 1.80 + aDownloads.forEach((aDownload) => { 1.81 + this.downloads[aDownload.id] = aDownload; 1.82 + }); 1.83 + }, 1.84 + 1.85 + receiveMessage: function(aMessage) { 1.86 + let download = aMessage.data; 1.87 + debug("message: " + aMessage.name); 1.88 + switch(aMessage.name) { 1.89 + case "Downloads:GetList:Return": 1.90 + this._updateDownloadsArray(download); 1.91 + 1.92 + if (!this.ready) { 1.93 + this.getListPromises.forEach(aPromise => 1.94 + aPromise.resolve(this.downloads)); 1.95 + this.getListPromises.length = 0; 1.96 + } 1.97 + this.ready = true; 1.98 + break; 1.99 + case "Downloads:ClearAllDone:Return": 1.100 + this._updateDownloadsArray(download); 1.101 + this.clearAllPromises.forEach(aPromise => 1.102 + aPromise.resolve(this.downloads)); 1.103 + this.clearAllPromises.length = 0; 1.104 + break; 1.105 + case "Downloads:Added": 1.106 + this.downloads[download.id] = download; 1.107 + this.notifyChanges(download.id); 1.108 + break; 1.109 + case "Downloads:Removed": 1.110 + if (this.downloads[download.id]) { 1.111 + this.downloads[download.id] = download; 1.112 + this.notifyChanges(download.id); 1.113 + delete this.downloads[download.id]; 1.114 + } 1.115 + break; 1.116 + case "Downloads:Changed": 1.117 + // Only update properties that actually changed. 1.118 + let cached = this.downloads[download.id]; 1.119 + if (!cached) { 1.120 + debug("No download found for " + download.id); 1.121 + return; 1.122 + } 1.123 + let props = ["totalBytes", "currentBytes", "url", "path", "state", 1.124 + "contentType", "startTime"]; 1.125 + let changed = false; 1.126 + 1.127 + props.forEach((aProp) => { 1.128 + if (download[aProp] && (download[aProp] != cached[aProp])) { 1.129 + cached[aProp] = download[aProp]; 1.130 + changed = true; 1.131 + } 1.132 + }); 1.133 + 1.134 + // Updating the error property. We always get a 'state' change as 1.135 + // well. 1.136 + cached.error = download.error; 1.137 + 1.138 + if (changed) { 1.139 + this.notifyChanges(download.id); 1.140 + } 1.141 + break; 1.142 + case "Downloads:Remove:Return": 1.143 + case "Downloads:Pause:Return": 1.144 + case "Downloads:Resume:Return": 1.145 + if (this.downloadPromises[download.promiseId]) { 1.146 + if (!download.error) { 1.147 + this.downloadPromises[download.promiseId].resolve(download); 1.148 + } else { 1.149 + this.downloadPromises[download.promiseId].reject(download); 1.150 + } 1.151 + delete this.downloadPromises[download.promiseId]; 1.152 + } 1.153 + break; 1.154 + } 1.155 + }, 1.156 + 1.157 + /** 1.158 + * Returns a promise that is resolved with the list of current downloads. 1.159 + */ 1.160 + getDownloads: function() { 1.161 + debug("getDownloads()"); 1.162 + let deferred = Promise.defer(); 1.163 + if (this.ready) { 1.164 + debug("Returning existing list."); 1.165 + deferred.resolve(this.downloads); 1.166 + } else { 1.167 + this.getListPromises.push(deferred); 1.168 + } 1.169 + return deferred.promise; 1.170 + }, 1.171 + 1.172 + /** 1.173 + * Returns a promise that is resolved with the list of current downloads. 1.174 + */ 1.175 + clearAllDone: function() { 1.176 + debug("clearAllDone"); 1.177 + let deferred = Promise.defer(); 1.178 + this.clearAllPromises.push(deferred); 1.179 + cpmm.sendAsyncMessage("Downloads:ClearAllDone", {}); 1.180 + return deferred.promise; 1.181 + }, 1.182 + 1.183 + promiseId: function() { 1.184 + return this._promiseId++; 1.185 + }, 1.186 + 1.187 + remove: function(aId) { 1.188 + debug("remove " + aId); 1.189 + let deferred = Promise.defer(); 1.190 + let pId = this.promiseId(); 1.191 + this.downloadPromises[pId] = deferred; 1.192 + cpmm.sendAsyncMessage("Downloads:Remove", 1.193 + { id: aId, promiseId: pId }); 1.194 + return deferred.promise; 1.195 + }, 1.196 + 1.197 + pause: function(aId) { 1.198 + debug("pause " + aId); 1.199 + let deferred = Promise.defer(); 1.200 + let pId = this.promiseId(); 1.201 + this.downloadPromises[pId] = deferred; 1.202 + cpmm.sendAsyncMessage("Downloads:Pause", 1.203 + { id: aId, promiseId: pId }); 1.204 + return deferred.promise; 1.205 + }, 1.206 + 1.207 + resume: function(aId) { 1.208 + debug("resume " + aId); 1.209 + let deferred = Promise.defer(); 1.210 + let pId = this.promiseId(); 1.211 + this.downloadPromises[pId] = deferred; 1.212 + cpmm.sendAsyncMessage("Downloads:Resume", 1.213 + { id: aId, promiseId: pId }); 1.214 + return deferred.promise; 1.215 + }, 1.216 + 1.217 + observe: function(aSubject, aTopic, aData) { 1.218 + if (aTopic == "xpcom-shutdown") { 1.219 + ipcMessages.forEach((aMessage) => { 1.220 + cpmm.removeMessageListener(aMessage, this); 1.221 + }); 1.222 + } 1.223 + } 1.224 +}; 1.225 + 1.226 +DownloadsIPC.init();