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/. */
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");
20 //dump("-*- AppsServiceChild.jsm: " + s + "\n");
23 const APPS_IPC_MSG_NAMES = [
27 "Webapps:CheckForUpdate:Return:KO",
32 // A simple cache for the wrapped manifests.
33 this.WrappedManifestCache = {
36 // Gets an entry from the cache, and populates the cache if needed.
37 get: function mcache_get(aManifestURL, aManifest, aWindow, aInnerWindowID) {
42 if (!(aManifestURL in this._cache)) {
43 this._cache[aManifestURL] = { };
46 let winObjs = this._cache[aManifestURL];
47 if (!(aInnerWindowID in winObjs)) {
48 winObjs[aInnerWindowID] = Cu.cloneInto(aManifest, aWindow);
51 return winObjs[aInnerWindowID];
54 // Invalidates an entry in the cache.
55 evict: function mcache_evict(aManifestURL, aInnerWindowID) {
56 debug("Evicting manifest " + aManifestURL + " window ID " +
58 if (aManifestURL in this._cache) {
59 let winObjs = this._cache[aManifestURL];
60 if (aInnerWindowID in winObjs) {
61 delete winObjs[aInnerWindowID];
64 if (Object.keys(winObjs).length == 0) {
65 delete this._cache[aManifestURL];
70 observe: function(aSubject, aTopic, aData) {
71 // Clear the cache on memory pressure.
77 Services.obs.addObserver(this, "memory-pressure", false);
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.
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);
106 this.cpmm.sendAsyncMessage("Webapps:RegisterForMessages", {
107 messages: APPS_IPC_MSG_NAMES
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];
122 Services.obs.addObserver(this, "xpcom-shutdown", false);
125 observe: function(aSubject, aTopic, aData) {
126 // cpmm.addMessageListener causes the DOMApplicationRegistry object to
127 // live forever if we don't clean up properly.
131 APPS_IPC_MSG_NAMES.forEach((aMsgName) => {
132 this.cpmm.removeMessageListener(aMsgName, this);
135 this.cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
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;
147 this.webapps[msg.id].manifest = msg.manifest;
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];
155 case "Webapps:UpdateApp":
156 let app = this.webapps[msg.oldId];
162 for (let prop in msg.app) {
163 app[prop] = msg.app[prop];
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];
175 for (let i = 0; i < apps.length; i++) {
176 let domApp = apps[i].get();
177 if (!domApp || domApp._window === null) {
181 domApp._proxy = new Proxy(domApp, {
182 get: function(target, prop) {
183 if (!DOMApplicationRegistry.webapps[msg.newId]) {
186 return DOMApplicationRegistry.webapps[msg.newId][prop];
188 set: function(target, prop, val) {
189 if (!DOMApplicationRegistry.webapps[msg.newId]) {
192 DOMApplicationRegistry.webapps[msg.newId][prop] = val;
198 case "Webapps:FireEvent":
199 this._fireEvent(aMessage);
201 case "Webapps:UpdateState":
202 this._updateState(msg);
204 case "Webapps:CheckForUpdate:Return:KO":
205 let DOMApps = this.DOMApps[msg.manifestURL];
206 if (!DOMApps || !msg.requestID) {
209 DOMApps.forEach((DOMApp) => {
210 let domApp = DOMApp.get();
211 if (domApp && msg.requestID) {
212 domApp._fireRequestResult(aMessage, true /* aIsError */);
220 * mozIDOMApplication management
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] = [];
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) {
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.
248 get: function(target, prop) {
249 if (!DOMApplicationRegistry.webapps[aId]) {
253 if (prop in DOMApplicationRegistry.webapps[aId]) {
254 return DOMApplicationRegistry.webapps[aId][prop];
258 set: function(target, prop, val) {
259 if (!DOMApplicationRegistry.webapps[aId]) {
262 DOMApplicationRegistry.webapps[aId][prop] = val;
268 _fireEvent: function(aMessage) {
269 let msg = aMessage.data;
270 debug("_fireEvent " + JSON.stringify(msg));
271 if (!this.DOMApps || !msg.manifestURL || !msg.eventType) {
275 let DOMApps = this.DOMApps[msg.manifestURL];
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];
287 DOMApps.forEach((DOMApp) => {
288 let domApp = DOMApp.get();
292 msg.eventType.forEach((aEventType) => {
293 if ('on' + aEventType in domApp) {
294 domApp._fireEvent(aEventType);
299 aMessage.data.result = msg.manifestURL;
300 domApp._fireRequestResult(aMessage);
305 _updateState: function(aMessage) {
306 if (!this.DOMApps || !aMessage.id) {
310 let app = this.webapps[aMessage.id];
316 for (let prop in aMessage.app) {
317 app[prop] = aMessage.app[prop];
321 if ("error" in aMessage) {
322 app.downloadError = aMessage.error;
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];
332 DOMApps.forEach((DOMApp) => {
333 let domApp = DOMApp.get();
337 WrappedManifestCache.evict(app.manifestURL, domApp.innerWindowID);
342 getAll: function(aCallback) {
344 if (!aCallback || typeof aCallback !== "function") {
349 for (let id in this.webapps) {
350 res.push(this.webapps[id]);
358 getAppByManifestURL: function getAppByManifestURL(aManifestURL) {
359 debug("getAppByManifestURL " + aManifestURL);
360 return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL);
363 getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aManifestURL) {
364 debug("getAppLocalIdByManifestURL " + aManifestURL);
365 return AppsUtils.getAppLocalIdByManifestURL(this.webapps, aManifestURL);
368 getManifestCSPByLocalId: function(aLocalId) {
369 debug("getManifestCSPByLocalId:" + aLocalId);
370 return AppsUtils.getManifestCSPByLocalId(this.webapps, aLocalId);
373 getDefaultCSPByLocalId: function(aLocalId) {
374 debug("getDefaultCSPByLocalId:" + aLocalId);
375 return AppsUtils.getDefaultCSPByLocalId(this.webapps, aLocalId);
378 getAppLocalIdByStoreId: function(aStoreId) {
379 debug("getAppLocalIdByStoreId:" + aStoreId);
380 return AppsUtils.getAppLocalIdByStoreId(this.webapps, aStoreId);
383 getAppByLocalId: function getAppByLocalId(aLocalId) {
384 debug("getAppByLocalId " + aLocalId + " - ready: " + this.ready);
385 let app = this.localIdIndex[aLocalId];
387 debug("Ouch, No app!");
391 return new mozIApplication(app);
394 getManifestURLByLocalId: function getManifestURLByLocalId(aLocalId) {
395 debug("getManifestURLByLocalId " + aLocalId);
396 return AppsUtils.getManifestURLByLocalId(this.webapps, aLocalId);
399 getCoreAppsBasePath: function getCoreAppsBasePath() {
400 debug("getCoreAppsBasePath() not yet supported on child!");
404 getWebAppsBasePath: function getWebAppsBasePath() {
405 debug("getWebAppsBasePath() not yet supported on child!");
409 getAppInfo: function getAppInfo(aAppId) {
410 return AppsUtils.getAppInfo(this.webapps, aAppId);
414 DOMApplicationRegistry.init();