diff options
Diffstat (limited to 'dom/apps/AppsServiceChild.jsm')
-rw-r--r-- | dom/apps/AppsServiceChild.jsm | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/dom/apps/AppsServiceChild.jsm b/dom/apps/AppsServiceChild.jsm new file mode 100644 index 000000000..4aca938af --- /dev/null +++ b/dom/apps/AppsServiceChild.jsm @@ -0,0 +1,408 @@ +/* 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 Cu = Components.utils; +const Cc = Components.classes; +const Ci = Components.interfaces; + +// This module exposes a subset of the functionalities of the parent DOM +// Registry to content processes, to be used from the AppsService component. + +this.EXPORTED_SYMBOLS = ["DOMApplicationRegistry", "WrappedManifestCache"]; + +Cu.import("resource://gre/modules/AppsUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +function debug(s) { + //dump("-*- AppsServiceChild.jsm: " + s + "\n"); +} + +const APPS_IPC_MSG_NAMES = [ + "Webapps:AddApp", + "Webapps:RemoveApp", + "Webapps:UpdateApp", + "Webapps:CheckForUpdate:Return:KO", + "Webapps:FireEvent", + "Webapps:UpdateState" +]; + +// A simple cache for the wrapped manifests. +this.WrappedManifestCache = { + _cache: { }, + + // Gets an entry from the cache, and populates the cache if needed. + get: function mcache_get(aManifestURL, aManifest, aWindow, aInnerWindowID) { + if (!aManifest) { + return; + } + + if (!(aManifestURL in this._cache)) { + this._cache[aManifestURL] = { }; + } + + let winObjs = this._cache[aManifestURL]; + if (!(aInnerWindowID in winObjs)) { + winObjs[aInnerWindowID] = Cu.cloneInto(aManifest, aWindow); + } + + return winObjs[aInnerWindowID]; + }, + + // Invalidates an entry in the cache. + evict: function mcache_evict(aManifestURL, aInnerWindowID) { + debug("Evicting manifest " + aManifestURL + " window ID " + + aInnerWindowID); + if (aManifestURL in this._cache) { + let winObjs = this._cache[aManifestURL]; + if (aInnerWindowID in winObjs) { + delete winObjs[aInnerWindowID]; + } + + if (Object.keys(winObjs).length == 0) { + delete this._cache[aManifestURL]; + } + } + }, + + observe: function(aSubject, aTopic, aData) { + // Clear the cache on memory pressure. + this._cache = { }; + Cu.forceGC(); + }, + + init: function() { + Services.obs.addObserver(this, "memory-pressure", false); + } +}; + +this.WrappedManifestCache.init(); + + +// DOMApplicationRegistry keeps a cache containing a list of apps in the device. +// This information is updated with the data received from the main process and +// it is queried by the DOM objects to set their state. +// This module handle all the messages broadcasted from the parent process, +// including DOM events, which are dispatched to the corresponding DOM objects. + +this.DOMApplicationRegistry = { + // DOMApps will hold a list of arrays of weak references to + // mozIDOMApplication objects indexed by manifest URL. + DOMApps: {}, + + ready: false, + webapps: null, + + init: function init() { + this.cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"] + .getService(Ci.nsISyncMessageSender); + + APPS_IPC_MSG_NAMES.forEach((function(aMsgName) { + this.cpmm.addMessageListener(aMsgName, this); + }).bind(this)); + + this.webapps = { }; + // We need a fast mapping from localId -> app, so we add an index. + // We also add the manifest to the app object. + this.localIdIndex = { }; + for (let id in this.webapps) { + let app = this.webapps[id]; + this.localIdIndex[app.localId] = app; + app.manifest = list.manifests[id]; + } + + Services.obs.addObserver(this, "xpcom-shutdown", false); + }, + + observe: function(aSubject, aTopic, aData) { + // cpmm.addMessageListener causes the DOMApplicationRegistry object to + // live forever if we don't clean up properly. + this.webapps = null; + this.DOMApps = null; + + APPS_IPC_MSG_NAMES.forEach((aMsgName) => { + this.cpmm.removeMessageListener(aMsgName, this); + }); + }, + + receiveMessage: function receiveMessage(aMessage) { + debug("Received " + aMessage.name + " message."); + let msg = aMessage.data; + switch (aMessage.name) { + case "Webapps:AddApp": + this.webapps[msg.id] = msg.app; + this.localIdIndex[msg.app.localId] = msg.app; + if (msg.manifest) { + this.webapps[msg.id].manifest = msg.manifest; + } + break; + case "Webapps:RemoveApp": + delete this.DOMApps[this.webapps[msg.id].manifestURL]; + delete this.localIdIndex[this.webapps[msg.id].localId]; + delete this.webapps[msg.id]; + break; + case "Webapps:UpdateApp": + let app = this.webapps[msg.oldId]; + if (!app) { + return; + } + + if (msg.app) { + for (let prop in msg.app) { + app[prop] = msg.app[prop]; + } + } + + this.webapps[msg.newId] = app; + this.localIdIndex[app.localId] = app; + delete this.webapps[msg.oldId]; + + let apps = this.DOMApps[msg.app.manifestURL]; + if (!apps) { + return; + } + for (let i = 0; i < apps.length; i++) { + let domApp = apps[i].get(); + if (!domApp || domApp._window === null) { + apps.splice(i, 1); + continue; + } + domApp._proxy = new Proxy(domApp, { + get: function(target, prop) { + if (!DOMApplicationRegistry.webapps[msg.newId]) { + return; + } + return DOMApplicationRegistry.webapps[msg.newId][prop]; + }, + set: function(target, prop, val) { + if (!DOMApplicationRegistry.webapps[msg.newId]) { + return; + } + DOMApplicationRegistry.webapps[msg.newId][prop] = val; + return; + }, + }); + } + break; + case "Webapps:FireEvent": + this._fireEvent(aMessage); + break; + case "Webapps:UpdateState": + this._updateState(msg); + break; + case "Webapps:CheckForUpdate:Return:KO": + let DOMApps = this.DOMApps[msg.manifestURL]; + if (!DOMApps || !msg.requestID) { + return; + } + DOMApps.forEach((DOMApp) => { + let domApp = DOMApp.get(); + if (domApp && msg.requestID) { + domApp._fireRequestResult(aMessage, true /* aIsError */); + } + }); + break; + } + }, + + /** + * mozIDOMApplication management + */ + + // Every time a DOM app is created, we save a weak reference to it that will + // be used to dispatch events and fire request results. + addDOMApp: function(aApp, aManifestURL, aId) { + let weakRef = Cu.getWeakReference(aApp); + + if (!this.DOMApps[aManifestURL]) { + this.DOMApps[aManifestURL] = []; + } + + let apps = this.DOMApps[aManifestURL]; + + // Get rid of dead weak references. + for (let i = 0; i < apps.length; i++) { + let app = apps[i].get(); + if (!app || app._window === null) { + apps.splice(i, 1); + } + } + + apps.push(weakRef); + + // Each DOM app contains a proxy object used to build their state. We + // return the handler for this proxy object with traps to get and set + // app properties kept in the DOMApplicationRegistry app cache. + return { + get: function(target, prop) { + if (!DOMApplicationRegistry.webapps[aId]) { + return; + } + + if (prop in DOMApplicationRegistry.webapps[aId]) { + return DOMApplicationRegistry.webapps[aId][prop]; + } + return null; + }, + set: function(target, prop, val) { + if (!DOMApplicationRegistry.webapps[aId]) { + return; + } + DOMApplicationRegistry.webapps[aId][prop] = val; + return; + }, + }; + }, + + _fireEvent: function(aMessage) { + let msg = aMessage.data; + debug("_fireEvent " + JSON.stringify(msg)); + if (!this.DOMApps || !msg.manifestURL || !msg.eventType) { + return; + } + + let DOMApps = this.DOMApps[msg.manifestURL]; + if (!DOMApps) { + return; + } + + // The parent might ask childs to trigger more than one event in one + // shot, so in order to avoid needless IPC we allow an array for the + // 'eventType' IPC message field. + if (!Array.isArray(msg.eventType)) { + msg.eventType = [msg.eventType]; + } + + DOMApps.forEach((DOMApp) => { + let domApp = DOMApp.get(); + if (!domApp) { + return; + } + msg.eventType.forEach((aEventType) => { + if ('on' + aEventType in domApp) { + domApp._fireEvent(aEventType); + } + }); + + if (msg.requestID) { + aMessage.data.result = msg.manifestURL; + domApp._fireRequestResult(aMessage); + } + }); + }, + + _updateState: function(aMessage) { + if (!this.DOMApps || !aMessage.id) { + return; + } + + let app = this.webapps[aMessage.id]; + if (!app) { + return; + } + + if (aMessage.app) { + for (let prop in aMessage.app) { + app[prop] = aMessage.app[prop]; + } + } + + if ("error" in aMessage) { + app.downloadError = aMessage.error; + } + + if (aMessage.manifest) { + app.manifest = aMessage.manifest; + // Evict the wrapped manifest cache for all the affected DOM objects. + let DOMApps = this.DOMApps[app.manifestURL]; + if (!DOMApps) { + return; + } + DOMApps.forEach((DOMApp) => { + let domApp = DOMApp.get(); + if (!domApp) { + return; + } + WrappedManifestCache.evict(app.manifestURL, domApp.innerWindowID); + }); + } + }, + + getAll: function(aCallback) { + debug("getAll()\n"); + if (!aCallback || typeof aCallback !== "function") { + return; + } + + let res = []; + for (let id in this.webapps) { + res.push(this.webapps[id]); + } + aCallback(res); + }, + + getAdditionalLanguages: function(aManifestURL) { + for (let id in this.webapps) { + if (this.webapps[id].manifestURL == aManifestURL) { + return this.webapps[id].additionalLanguages || {}; + } + } + return {}; + }, + + /** + * nsIAppsService API + */ + getAppByManifestURL: function getAppByManifestURL(aManifestURL) { + debug("getAppByManifestURL " + aManifestURL); + return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL); + }, + + getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aManifestURL) { + debug("getAppLocalIdByManifestURL " + aManifestURL); + return AppsUtils.getAppLocalIdByManifestURL(this.webapps, aManifestURL); + }, + + getAppLocalIdByStoreId: function(aStoreId) { + debug("getAppLocalIdByStoreId:" + aStoreId); + return AppsUtils.getAppLocalIdByStoreId(this.webapps, aStoreId); + }, + + getAppByLocalId: function getAppByLocalId(aLocalId) { + debug("getAppByLocalId " + aLocalId + " - ready: " + this.ready); + let app = this.localIdIndex[aLocalId]; + if (!app) { + debug("Ouch, No app!"); + return null; + } + + return new mozIApplication(app); + }, + + getManifestURLByLocalId: function getManifestURLByLocalId(aLocalId) { + debug("getManifestURLByLocalId " + aLocalId); + return AppsUtils.getManifestURLByLocalId(this.webapps, aLocalId); + }, + + getCoreAppsBasePath: function getCoreAppsBasePath() { + debug("getCoreAppsBasePath() not yet supported on child!"); + return null; + }, + + getWebAppsBasePath: function getWebAppsBasePath() { + debug("getWebAppsBasePath() not yet supported on child!"); + return null; + }, + + areAnyAppsInstalled: function() { + return AppsUtils.areAnyAppsInstalled(this.webapps); + }, + + getAppInfo: function getAppInfo(aAppId) { + return AppsUtils.getAppInfo(this.webapps, aAppId); + } +} + +DOMApplicationRegistry.init(); |