Bumping manifests a=b2g-bump
[gecko.git] / dom / apps / AppsServiceChild.jsm
blob0c3bbe6a59b2d7f0c1567fe6fd5ca936d819e78c
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 const Cu = Components.utils;
8 const Cc = Components.classes;
9 const Ci = Components.interfaces;
11 // This module exposes a subset of the functionalities of the parent DOM
12 // Registry to content processes, to be used from the AppsService component.
14 this.EXPORTED_SYMBOLS = ["DOMApplicationRegistry", "WrappedManifestCache"];
16 Cu.import("resource://gre/modules/AppsUtils.jsm");
17 Cu.import("resource://gre/modules/Services.jsm");
19 function debug(s) {
20   //dump("-*- AppsServiceChild.jsm: " + s + "\n");
23 const APPS_IPC_MSG_NAMES = [
24   "Webapps:AddApp",
25   "Webapps:RemoveApp",
26   "Webapps:UpdateApp",
27   "Webapps:CheckForUpdate:Return:KO",
28   "Webapps:FireEvent",
29   "Webapps:UpdateState"
32 // A simple cache for the wrapped manifests.
33 this.WrappedManifestCache = {
34   _cache: { },
36   // Gets an entry from the cache, and populates the cache if needed.
37   get: function mcache_get(aManifestURL, aManifest, aWindow, aInnerWindowID) {
38     if (!aManifest) {
39       return;
40     }
42     if (!(aManifestURL in this._cache)) {
43       this._cache[aManifestURL] = { };
44     }
46     let winObjs = this._cache[aManifestURL];
47     if (!(aInnerWindowID in winObjs)) {
48       winObjs[aInnerWindowID] = Cu.cloneInto(aManifest, aWindow);
49     }
51     return winObjs[aInnerWindowID];
52   },
54   // Invalidates an entry in the cache.
55   evict: function mcache_evict(aManifestURL, aInnerWindowID) {
56     debug("Evicting manifest " + aManifestURL + " window ID " +
57           aInnerWindowID);
58     if (aManifestURL in this._cache) {
59       let winObjs = this._cache[aManifestURL];
60       if (aInnerWindowID in winObjs) {
61         delete winObjs[aInnerWindowID];
62       }
64       if (Object.keys(winObjs).length == 0) {
65         delete this._cache[aManifestURL];
66       }
67     }
68   },
70   observe: function(aSubject, aTopic, aData) {
71     // Clear the cache on memory pressure.
72     this._cache = { };
73     Cu.forceGC();
74   },
76   init: function() {
77     Services.obs.addObserver(this, "memory-pressure", false);
78   }
81 this.WrappedManifestCache.init();
84 // DOMApplicationRegistry keeps a cache containing a list of apps in the device.
85 // This information is updated with the data received from the main process and
86 // it is queried by the DOM objects to set their state.
87 // This module handle all the messages broadcasted from the parent process,
88 // including DOM events, which are dispatched to the corresponding DOM objects.
90 this.DOMApplicationRegistry = {
91   // DOMApps will hold a list of arrays of weak references to
92   // mozIDOMApplication objects indexed by manifest URL.
93   DOMApps: {},
95   ready: false,
96   webapps: null,
98   init: function init() {
99     this.cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
100                   .getService(Ci.nsISyncMessageSender);
102     APPS_IPC_MSG_NAMES.forEach((function(aMsgName) {
103       this.cpmm.addMessageListener(aMsgName, this);
104     }).bind(this));
106     this.cpmm.sendAsyncMessage("Webapps:RegisterForMessages", {
107       messages: APPS_IPC_MSG_NAMES
108     });
110     // We need to prime the cache with the list of apps.
111     let list = this.cpmm.sendSyncMessage("Webapps:GetList", { })[0];
112     this.webapps = list.webapps;
113     // We need a fast mapping from localId -> app, so we add an index.
114     // We also add the manifest to the app object.
115     this.localIdIndex = { };
116     for (let id in this.webapps) {
117       let app = this.webapps[id];
118       this.localIdIndex[app.localId] = app;
119       app.manifest = list.manifests[id];
120     }
122     Services.obs.addObserver(this, "xpcom-shutdown", false);
123   },
125   observe: function(aSubject, aTopic, aData) {
126     // cpmm.addMessageListener causes the DOMApplicationRegistry object to
127     // live forever if we don't clean up properly.
128     this.webapps = null;
129     this.DOMApps = null;
131     APPS_IPC_MSG_NAMES.forEach((aMsgName) => {
132       this.cpmm.removeMessageListener(aMsgName, this);
133     });
135     this.cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
136                                APPS_IPC_MSG_NAMES)
137   },
139   receiveMessage: function receiveMessage(aMessage) {
140     debug("Received " + aMessage.name + " message.");
141     let msg = aMessage.data;
142     switch (aMessage.name) {
143       case "Webapps:AddApp":
144         this.webapps[msg.id] = msg.app;
145         this.localIdIndex[msg.app.localId] = msg.app;
146         if (msg.manifest) {
147           this.webapps[msg.id].manifest = msg.manifest;
148         }
149         break;
150       case "Webapps:RemoveApp":
151         delete this.DOMApps[this.webapps[msg.id].manifestURL];
152         delete this.localIdIndex[this.webapps[msg.id].localId];
153         delete this.webapps[msg.id];
154         break;
155       case "Webapps:UpdateApp":
156         let app = this.webapps[msg.oldId];
157         if (!app) {
158           return;
159         }
161         if (msg.app) {
162           for (let prop in msg.app) {
163             app[prop] = msg.app[prop];
164           }
165         }
167         this.webapps[msg.newId] = app;
168         this.localIdIndex[app.localId] = app;
169         delete this.webapps[msg.oldId];
171         let apps = this.DOMApps[msg.app.manifestURL];
172         if (!apps) {
173           return;
174         }
175         for (let i = 0; i < apps.length; i++) {
176           let domApp = apps[i].get();
177           if (!domApp || domApp._window === null) {
178             apps.splice(i, 1);
179             continue;
180           }
181           domApp._proxy = new Proxy(domApp, {
182             get: function(target, prop) {
183               if (!DOMApplicationRegistry.webapps[msg.newId]) {
184                 return;
185               }
186               return DOMApplicationRegistry.webapps[msg.newId][prop];
187             },
188             set: function(target, prop, val) {
189               if (!DOMApplicationRegistry.webapps[msg.newId]) {
190                 return;
191               }
192               DOMApplicationRegistry.webapps[msg.newId][prop] = val;
193               return;
194             },
195           });
196         }
197         break;
198       case "Webapps:FireEvent":
199         this._fireEvent(aMessage);
200         break;
201       case "Webapps:UpdateState":
202         this._updateState(msg);
203         break;
204       case "Webapps:CheckForUpdate:Return:KO":
205         let DOMApps = this.DOMApps[msg.manifestURL];
206         if (!DOMApps || !msg.requestID) {
207           return;
208         }
209         DOMApps.forEach((DOMApp) => {
210           let domApp = DOMApp.get();
211           if (domApp && msg.requestID) {
212             domApp._fireRequestResult(aMessage, true /* aIsError */);
213           }
214         });
215         break;
216     }
217   },
219   /**
220    * mozIDOMApplication management
221    */
223   // Every time a DOM app is created, we save a weak reference to it that will
224   // be used to dispatch events and fire request results.
225   addDOMApp: function(aApp, aManifestURL, aId) {
226     let weakRef = Cu.getWeakReference(aApp);
228     if (!this.DOMApps[aManifestURL]) {
229       this.DOMApps[aManifestURL] = [];
230     }
232     let apps = this.DOMApps[aManifestURL];
234     // Get rid of dead weak references.
235     for (let i = 0; i < apps.length; i++) {
236       let app = apps[i].get();
237       if (!app || app._window === null) {
238         apps.splice(i, 1);
239       }
240     }
242     apps.push(weakRef);
244     // Each DOM app contains a proxy object used to build their state. We
245     // return the handler for this proxy object with traps to get and set
246     // app properties kept in the DOMApplicationRegistry app cache.
247     return {
248       get: function(target, prop) {
249         if (!DOMApplicationRegistry.webapps[aId]) {
250           return;
251         }
253         if (prop in DOMApplicationRegistry.webapps[aId]) {
254           return DOMApplicationRegistry.webapps[aId][prop];
255         }
256         return null;
257       },
258       set: function(target, prop, val) {
259         if (!DOMApplicationRegistry.webapps[aId]) {
260           return;
261         }
262         DOMApplicationRegistry.webapps[aId][prop] = val;
263         return;
264       },
265     };
266   },
268   _fireEvent: function(aMessage) {
269     let msg = aMessage.data;
270     debug("_fireEvent " + JSON.stringify(msg));
271     if (!this.DOMApps || !msg.manifestURL || !msg.eventType) {
272       return;
273     }
275     let DOMApps = this.DOMApps[msg.manifestURL];
276     if (!DOMApps) {
277       return;
278     }
280     // The parent might ask childs to trigger more than one event in one
281     // shot, so in order to avoid needless IPC we allow an array for the
282     // 'eventType' IPC message field.
283     if (!Array.isArray(msg.eventType)) {
284       msg.eventType = [msg.eventType];
285     }
287     DOMApps.forEach((DOMApp) => {
288       let domApp = DOMApp.get();
289       if (!domApp) {
290         return;
291       }
292       msg.eventType.forEach((aEventType) => {
293         if ('on' + aEventType in domApp) {
294           domApp._fireEvent(aEventType);
295         }
296       });
298       if (msg.requestID) {
299         aMessage.data.result = msg.manifestURL;
300         domApp._fireRequestResult(aMessage);
301       }
302     });
303   },
305   _updateState: function(aMessage) {
306     if (!this.DOMApps || !aMessage.id) {
307       return;
308     }
310     let app = this.webapps[aMessage.id];
311     if (!app) {
312       return;
313     }
315     if (aMessage.app) {
316       for (let prop in aMessage.app) {
317         app[prop] = aMessage.app[prop];
318       }
319     }
321     if ("error" in aMessage) {
322       app.downloadError = aMessage.error;
323     }
325     if (aMessage.manifest) {
326       app.manifest = aMessage.manifest;
327       // Evict the wrapped manifest cache for all the affected DOM objects.
328       let DOMApps = this.DOMApps[app.manifestURL];
329       if (!DOMApps) {
330         return;
331       }
332       DOMApps.forEach((DOMApp) => {
333         let domApp = DOMApp.get();
334         if (!domApp) {
335           return;
336         }
337         WrappedManifestCache.evict(app.manifestURL, domApp.innerWindowID);
338       });
339     }
340   },
342   getAll: function(aCallback) {
343     debug("getAll()\n");
344     if (!aCallback || typeof aCallback !== "function") {
345       return;
346     }
348     let res = [];
349     for (let id in this.webapps) {
350       res.push(this.webapps[id]);
351     }
352     aCallback(res);
353   },
355   /**
356    * nsIAppsService API
357    */
358   getAppByManifestURL: function getAppByManifestURL(aManifestURL) {
359     debug("getAppByManifestURL " + aManifestURL);
360     return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL);
361   },
363   getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aManifestURL) {
364     debug("getAppLocalIdByManifestURL " + aManifestURL);
365     return AppsUtils.getAppLocalIdByManifestURL(this.webapps, aManifestURL);
366   },
368   getManifestCSPByLocalId: function(aLocalId) {
369     debug("getManifestCSPByLocalId:" + aLocalId);
370     return AppsUtils.getManifestCSPByLocalId(this.webapps, aLocalId);
371   },
373   getDefaultCSPByLocalId: function(aLocalId) {
374     debug("getDefaultCSPByLocalId:" + aLocalId);
375     return AppsUtils.getDefaultCSPByLocalId(this.webapps, aLocalId);
376   },
378   getAppLocalIdByStoreId: function(aStoreId) {
379     debug("getAppLocalIdByStoreId:" + aStoreId);
380     return AppsUtils.getAppLocalIdByStoreId(this.webapps, aStoreId);
381   },
383   getAppByLocalId: function getAppByLocalId(aLocalId) {
384     debug("getAppByLocalId " + aLocalId + " - ready: " + this.ready);
385     let app = this.localIdIndex[aLocalId];
386     if (!app) {
387       debug("Ouch, No app!");
388       return null;
389     }
391     return new mozIApplication(app);
392   },
394   getManifestURLByLocalId: function getManifestURLByLocalId(aLocalId) {
395     debug("getManifestURLByLocalId " + aLocalId);
396     return AppsUtils.getManifestURLByLocalId(this.webapps, aLocalId);
397   },
399   getCoreAppsBasePath: function getCoreAppsBasePath() {
400     debug("getCoreAppsBasePath() not yet supported on child!");
401     return null;
402   },
404   getWebAppsBasePath: function getWebAppsBasePath() {
405     debug("getWebAppsBasePath() not yet supported on child!");
406     return null;
407   },
409   getAppInfo: function getAppInfo(aAppId) {
410     return AppsUtils.getAppInfo(this.webapps, aAppId);
411   }
414 DOMApplicationRegistry.init();