diff --git a/dom/apps/src/AppsServiceChild.jsm b/dom/apps/src/AppsServiceChild.jsm index 58bb4fee21ae..c7cb9890f768 100644 --- a/dom/apps/src/AppsServiceChild.jsm +++ b/dom/apps/src/AppsServiceChild.jsm @@ -8,10 +8,10 @@ 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 module exposes a subset of the functionnalities of the parent DOM +// Registry to content processes, to be be used from the AppsService component. -this.EXPORTED_SYMBOLS = ["DOMApplicationRegistry", "WrappedManifestCache"]; +this.EXPORTED_SYMBOLS = ["DOMApplicationRegistry"]; Cu.import("resource://gre/modules/AppsUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); @@ -20,324 +20,54 @@ 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() { + debug("init"); this.cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"] .getService(Ci.nsISyncMessageSender); - APPS_IPC_MSG_NAMES.forEach((function(aMsgName) { + ["Webapps:AddApp", "Webapps:RemoveApp"].forEach((function(aMsgName) { this.cpmm.addMessageListener(aMsgName, this); }).bind(this)); - this.cpmm.sendAsyncMessage("Webapps:RegisterForMessages", { - messages: APPS_IPC_MSG_NAMES - }); - // We need to prime the cache with the list of apps. - let list = this.cpmm.sendSyncMessage("Webapps:GetList", { })[0]; - this.webapps = list.webapps; + // XXX shoud we do this async and block callers if it's not yet there? + this.webapps = this.cpmm.sendSyncMessage("Webapps:GetList", { })[0]; + // 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. + // 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) => { + ["Webapps:AddApp", "Webapps:RemoveApp"].forEach((function(aMsgName) { this.cpmm.removeMessageListener(aMsgName, this); - }); - - this.cpmm.sendAsyncMessage("Webapps:UnregisterForMessages", - APPS_IPC_MSG_NAMES) + }).bind(this)); }, receiveMessage: function receiveMessage(aMessage) { debug("Received " + aMessage.name + " message."); - let msg = aMessage.data; + let msg = aMessage.json; 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; - } - return DOMApplicationRegistry.webapps[aId][prop]; - }, - 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); - }); - } - }, - - /** - * nsIAppsService API - */ getAppByManifestURL: function getAppByManifestURL(aManifestURL) { debug("getAppByManifestURL " + aManifestURL); return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL); @@ -359,7 +89,7 @@ this.DOMApplicationRegistry = { }, getAppByLocalId: function getAppByLocalId(aLocalId) { - debug("getAppByLocalId " + aLocalId + " - ready: " + this.ready); + debug("getAppByLocalId " + aLocalId); let app = this.localIdIndex[aLocalId]; if (!app) { debug("Ouch, No app!"); diff --git a/dom/apps/src/Webapps.js b/dom/apps/src/Webapps.js index ed2c3226ebac..47a3156159f0 100644 --- a/dom/apps/src/Webapps.js +++ b/dom/apps/src/Webapps.js @@ -12,7 +12,6 @@ Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/DOMRequestHelper.jsm"); Cu.import("resource://gre/modules/AppsUtils.jsm"); Cu.import("resource://gre/modules/BrowserElementPromptService.jsm"); -Cu.import("resource://gre/modules/AppsServiceChild.jsm"); XPCOMUtils.defineLazyServiceGetter(this, "cpmm", "@mozilla.org/childprocessmessagemanager;1", @@ -279,9 +278,50 @@ WebappsRegistry.prototype = { * mozIDOMApplication object */ +// A simple cache for the wrapped manifests. +let manifestCache = { + _cache: { }, + + // Gets an entry from the cache, and populates the cache if needed. + get: function mcache_get(aManifestURL, aManifest, aWindow, aInnerWindowID) { + 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) { + 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 = { }; + }, + + init: function() { + Services.obs.addObserver(this, "memory-pressure", false); + } +}; + function createApplicationObject(aWindow, aApp) { - let app = Cc["@mozilla.org/webapps/application;1"] - .createInstance(Ci.mozIDOMApplication); + let app = Cc["@mozilla.org/webapps/application;1"].createInstance(Ci.mozIDOMApplication); app.wrappedJSObject.init(aWindow, aApp); return app; } @@ -294,12 +334,27 @@ WebappsApplication.prototype = { __proto__: DOMRequestIpcHelper.prototype, init: function(aWindow, aApp) { - let proxyHandler = DOMApplicationRegistry.addDOMApp(this, - aApp.manifestURL, - aApp.id); - this._proxy = new Proxy(this, proxyHandler); - this._window = aWindow; + let principal = this._window.document.nodePrincipal; + this._appStatus = principal.appStatus; + this.origin = aApp.origin; + this._manifest = aApp.manifest; + this._updateManifest = aApp.updateManifest; + this.manifestURL = aApp.manifestURL; + this.receipts = aApp.receipts; + this.installOrigin = aApp.installOrigin; + this.installTime = aApp.installTime; + this.installState = aApp.installState || "installed"; + this.removable = aApp.removable; + this.lastUpdateCheck = aApp.lastUpdateCheck ? aApp.lastUpdateCheck + : Date.now(); + this.updateTime = aApp.updateTime ? aApp.updateTime + : aApp.installTime; + this.progress = NaN; + this.downloadAvailable = aApp.downloadAvailable; + this.downloading = aApp.downloading; + this.readyToApplyDownload = aApp.readyToApplyDownload; + this.downloadSize = aApp.downloadSize || 0; this._onprogress = null; this._ondownloadsuccess = null; @@ -307,83 +362,40 @@ WebappsApplication.prototype = { this._ondownloadavailable = null; this._ondownloadapplied = null; - this.initDOMRequestHelper(aWindow); - }, + this._downloadError = null; - get _appStatus() { - return this._proxy.appStatus; - }, + this.initDOMRequestHelper(aWindow, [ + { name: "Webapps:CheckForUpdate:Return:KO", weakRef: true }, + { name: "Webapps:Connect:Return:OK", weakRef: true }, + { name: "Webapps:Connect:Return:KO", weakRef: true }, + { name: "Webapps:FireEvent", weakRef: true }, + { name: "Webapps:GetConnections:Return:OK", weakRef: true }, + { name: "Webapps:UpdateState", weakRef: true } + ]); - get downloadAvailable() { - return this._proxy.downloadAvailable; - }, - - get downloading() { - return this._proxy.downloading; - }, - - get downloadSize() { - return this._proxy.downloadSize; - }, - - get installOrigin() { - return this._proxy.installOrigin; - }, - - get installState() { - return this._proxy.installState; - }, - - get installTime() { - return this._proxy.installTime; - }, - - get lastUpdateCheck() { - return this._proxy.lastUpdateCheck; - }, - - get manifestURL() { - return this._proxy.manifestURL; - }, - - get origin() { - return this._proxy.origin; - }, - - get progress() { - return this._proxy.progress; - }, - - get readyToApplyDownload() { - return this._proxy.readyToApplyDownload; - }, - - get receipts() { - return this._proxy.receipts; - }, - - set receipts(aReceipts) { - this._proxy.receipts = aReceipts; - }, - - get removable() { - return this._proxy.removable; - }, - - get updateTime() { - return this._proxy.updateTime; + cpmm.sendAsyncMessage("Webapps:RegisterForMessages", { + messages: ["Webapps:FireEvent", + "Webapps:UpdateState"], + app: { + id: this.id, + manifestURL: this.manifestURL, + installState: this.installState, + downloading: this.downloading + } + }); }, get manifest() { - return WrappedManifestCache.get(this.manifestURL, - this._proxy.manifest, - this._window, - this.innerWindowID); + return manifestCache.get(this.manifestURL, + this._manifest, + this._window, + this.innerWindowID); }, get updateManifest() { - return this._proxy.updateManifest ? - Cu.cloneInto(this._proxy.updateManifest, this._window) : null; + return this.updateManifest = + this._updateManifest ? Cu.cloneInto(this._updateManifest, this._window) + : null; }, set onprogress(aCallback) { @@ -428,10 +440,10 @@ WebappsApplication.prototype = { get downloadError() { // Only return DOMError when we have an error. - if (!this._proxy.downloadError) { + if (!this._downloadError) { return null; } - return new this._window.DOMError(this._proxy.downloadError); + return new this._window.DOMError(this._downloadError); }, download: function() { @@ -473,11 +485,12 @@ WebappsApplication.prototype = { BrowserElementPromptService.getBrowserElementChildForWindow(this._window); if (browserChild) { this.addMessageListeners("Webapps:ClearBrowserData:Return"); - browserChild.messageManager.sendAsyncMessage("Webapps:ClearBrowserData", { - manifestURL: this.manifestURL, - oid: this._id, - requestID: this.getRequestId(request) - }); + browserChild.messageManager.sendAsyncMessage( + "Webapps:ClearBrowserData", + { manifestURL: this.manifestURL, + oid: this._id, + requestID: this.getRequestId(request) } + ); } else { Services.DOMRequest.fireErrorAsync(request, "NO_CLEARABLE_BROWSER"); } @@ -485,33 +498,28 @@ WebappsApplication.prototype = { }, connect: function(aKeyword, aRules) { - this.addMessageListeners(["Webapps:Connect:Return:OK", - "Webapps:Connect:Return:KO"]); return this.createPromise(function (aResolve, aReject) { - cpmm.sendAsyncMessage("Webapps:Connect", { - keyword: aKeyword, - rules: aRules, - manifestURL: this.manifestURL, - outerWindowID: this._id, - requestID: this.getPromiseResolverId({ - resolve: aResolve, - reject: aReject - }) - }); + cpmm.sendAsyncMessage("Webapps:Connect", + { keyword: aKeyword, + rules: aRules, + manifestURL: this.manifestURL, + outerWindowID: this._id, + requestID: this.getPromiseResolverId({ + resolve: aResolve, + reject: aReject + })}); }.bind(this)); }, getConnections: function() { - this.addMessageListeners("Webapps:GetConnections:Return:OK"); return this.createPromise(function (aResolve, aReject) { - cpmm.sendAsyncMessage("Webapps:GetConnections", { - manifestURL: this.manifestURL, - outerWindowID: this._id, - requestID: this.getPromiseResolverId({ - resolve: aResolve, - reject: aReject - }) - }); + cpmm.sendAsyncMessage("Webapps:GetConnections", + { manifestURL: this.manifestURL, + outerWindowID: this._id, + requestID: this.getPromiseResolverId({ + resolve: aResolve, + reject: aReject + })}); }.bind(this)); }, @@ -560,7 +568,12 @@ WebappsApplication.prototype = { uninit: function() { this._onprogress = null; - WrappedManifestCache.evict(this.manifestURL, this.innerWindowID); + cpmm.sendAsyncMessage("Webapps:UnregisterForMessages", [ + "Webapps:FireEvent", + "Webapps:UpdateState" + ]); + + manifestCache.evict(this.manifestURL, this.innerWindowID); }, _fireEvent: function(aName) { @@ -577,16 +590,22 @@ WebappsApplication.prototype = { } }, - _fireRequestResult: function(aMessage, aIsError) { - let req; - let msg = aMessage.data; - req = this.takeRequest(msg.requestID); - if (!req) { - return; + _updateState: function(aMsg) { + if (aMsg.app) { + for (let prop in aMsg.app) { + this[prop] = aMsg.app[prop]; + } } - aIsError ? Services.DOMRequest.fireError(req, msg.error) - : Services.DOMRequest.fireSuccess(req, msg.result); + // Intentional use of 'in' so we unset the error if this is explicitly null. + if ('error' in aMsg) { + this._downloadError = aMsg.error; + } + + if (aMsg.manifest) { + this._manifest = aMsg.manifest; + manifestCache.evict(this.manifestURL, this.innerWindowID); + } }, receiveMessage: function(aMessage) { @@ -600,7 +619,10 @@ WebappsApplication.prototype = { req = this.takeRequest(msg.requestID); } - if (msg.oid !== this._id || !req) { + // ondownload* callbacks should be triggered on all app instances + if ((msg.oid != this._id || !req) && + aMessage.name !== "Webapps:FireEvent" && + aMessage.name !== "Webapps:UpdateState") { return; } @@ -615,13 +637,51 @@ WebappsApplication.prototype = { "Webapps:Launch:Return:KO"]); Services.DOMRequest.fireSuccess(req, null); break; + case "Webapps:CheckForUpdate:Return:KO": + Services.DOMRequest.fireError(req, msg.error); + break; + case "Webapps:FireEvent": + if (msg.manifestURL != this.manifestURL) { + 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]; + } + + msg.eventType.forEach((aEventType) => { + // If we are in a successful state clear any past errors. + if (aEventType === 'downloadapplied' || + aEventType === 'downloadsuccess') { + this._downloadError = null; + } + + if ("_on" + aEventType in this) { + this._fireEvent(aEventType); + } else { + dump("Unsupported event type " + aEventType + "\n"); + } + }); + + if (req) { + Services.DOMRequest.fireSuccess(req, this.manifestURL); + } + break; + case "Webapps:UpdateState": + if (msg.manifestURL != this.manifestURL) { + return; + } + + this._updateState(msg); + break; case "Webapps:ClearBrowserData:Return": this.removeMessageListeners(aMessage.name); Services.DOMRequest.fireSuccess(req, null); break; case "Webapps:Connect:Return:OK": - this.removeMessageListeners(["Webapps:Connect:Return:OK", - "Webapps:Connect:Return:KO"]); let messagePorts = []; msg.messagePortIDs.forEach((aPortID) => { let port = new this._window.MozInterAppMessagePort(aPortID); @@ -630,12 +690,9 @@ WebappsApplication.prototype = { req.resolve(messagePorts); break; case "Webapps:Connect:Return:KO": - this.removeMessageListeners(["Webapps:Connect:Return:OK", - "Webapps:Connect:Return:KO"]); req.reject("No connections registered"); break; case "Webapps:GetConnections:Return:OK": - this.removeMessageListeners(aMessage.name); let connections = []; msg.connections.forEach((aConnection) => { let connection = @@ -748,6 +805,7 @@ WebappsApplicationMgmt.prototype = { }, uninstall: function(aApp) { + dump("-- webapps.js uninstall " + aApp.manifestURL + "\n"); let request = this.createRequest(); cpmm.sendAsyncMessage("Webapps:Uninstall", { origin: aApp.origin, manifestURL: aApp.manifestURL, @@ -816,8 +874,12 @@ WebappsApplicationMgmt.prototype = { break; case "Webapps:Uninstall:Broadcast:Return:OK": if (this._onuninstall) { + let detail = { + manifestURL: msg.manifestURL, + origin: msg.origin + }; let event = new this._window.MozApplicationEvent("applicationuninstall", - { application : createApplicationObject(this._window, msg) }); + { application : createApplicationObject(this._window, detail) }); this._onuninstall.handleEvent(event); } break; @@ -846,5 +908,7 @@ WebappsApplicationMgmt.prototype = { classDescription: "Webapps Application Mgmt"}) } +manifestCache.init(); + this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebappsRegistry, WebappsApplication]); diff --git a/dom/apps/src/Webapps.jsm b/dom/apps/src/Webapps.jsm index 2fba41293edd..fe57310d272a 100755 --- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -179,7 +179,6 @@ this.DOMApplicationRegistry = { }).bind(this)); cpmm.addMessageListener("Activities:Register:OK", this); - cpmm.addMessageListener("Activities:Register:KO", this); Services.obs.addObserver(this, "xpcom-shutdown", false); Services.obs.addObserver(this, "memory-pressure", false); @@ -276,15 +275,9 @@ this.DOMApplicationRegistry = { return this._registryStarted.promise; }, - // The registry will be safe to clone when this promise is resolved. - _safeToClone: Promise.defer(), - // Notify we are done with registering apps and save a copy of the registry. _registryReady: Promise.defer(), notifyAppsRegistryReady: function notifyAppsRegistryReady() { - // Usually this promise will be resolved earlier, but just in case, - // resolve it here also. - this._safeToClone.resolve(); this._registryReady.resolve(); Services.obs.notifyObservers(this, "webapps-registry-ready", null); this._saveApps(); @@ -294,10 +287,6 @@ this.DOMApplicationRegistry = { return this._registryReady.promise; }, - get safeToClone() { - return this._safeToClone.promise; - }, - // Ensure that the .to property in redirects is a relative URL. sanitizeRedirects: function sanitizeRedirects(aSource) { if (!aSource) { @@ -973,7 +962,6 @@ this.DOMApplicationRegistry = { this._registerInterAppConnections(manifest, app); appsToRegister.push({ manifest: manifest, app: app }); }); - this._safeToClone.resolve(); this._registerActivitiesForApps(appsToRegister, aRunUpdate); }); }, @@ -1101,114 +1089,88 @@ this.DOMApplicationRegistry = { let mm = aMessage.target; msg.mm = mm; - let processedImmediately = true; - - // There are two kind of messages: the messages that only make sense once the - // registry is ready, and those that can (or have to) be treated as soon as - // they're received. switch (aMessage.name) { - case "Activities:Register:KO": - dump("Activities didn't register correctly!"); - case "Activities:Register:OK": - // Activities:Register:OK is special because it's one way the registryReady - // promise can be resolved. - // XXX: What to do when the activities registration failed? At this point - // just act as if nothing happened. - this.notifyAppsRegistryReady(); + case "Webapps:Install": { +#ifdef MOZ_WIDGET_ANDROID + Services.obs.notifyObservers(mm, "webapps-runtime-install", JSON.stringify(msg)); +#else + this.doInstall(msg, mm); +#endif break; - case "Webapps:GetList": - // GetList is special because it's synchronous. So far so well, it's the - // only synchronous message, if we get more at some point they should get - // this treatment also. - return this.doGetList(); - case "child-process-shutdown": - this.removeMessageListener(["Webapps:Internal:AllMessages"], mm); + } + case "Webapps:GetSelf": + this.getSelf(msg, mm); break; + case "Webapps:Uninstall": +#ifdef MOZ_WIDGET_ANDROID + Services.obs.notifyObservers(mm, "webapps-runtime-uninstall", JSON.stringify(msg)); +#else + this.doUninstall(msg, mm); +#endif + break; + case "Webapps:Launch": + this.doLaunch(msg, mm); + break; + case "Webapps:CheckInstalled": + this.checkInstalled(msg, mm); + break; + case "Webapps:GetInstalled": + this.getInstalled(msg, mm); + break; + case "Webapps:GetNotInstalled": + this.getNotInstalled(msg, mm); + break; + case "Webapps:GetAll": + this.doGetAll(msg, mm); + break; + case "Webapps:InstallPackage": { +#ifdef MOZ_WIDGET_ANDROID + Services.obs.notifyObservers(mm, "webapps-runtime-install-package", JSON.stringify(msg)); +#else + this.doInstallPackage(msg, mm); +#endif + break; + } case "Webapps:RegisterForMessages": this.addMessageListener(msg.messages, msg.app, mm); break; case "Webapps:UnregisterForMessages": this.removeMessageListener(msg, mm); break; - default: - processedImmediately = false; + case "child-process-shutdown": + this.removeMessageListener(["Webapps:Internal:AllMessages"], mm); + break; + case "Webapps:GetList": + this.addMessageListener(["Webapps:AddApp", "Webapps:RemoveApp"], null, mm); + return this.webapps; + case "Webapps:Download": + this.startDownload(msg.manifestURL); + break; + case "Webapps:CancelDownload": + this.cancelDownload(msg.manifestURL); + break; + case "Webapps:CheckForUpdate": + this.checkForUpdate(msg, mm); + break; + case "Webapps:ApplyDownload": + this.applyDownload(msg.manifestURL); + break; + case "Activities:Register:OK": + this.notifyAppsRegistryReady(); + break; + case "Webapps:Install:Return:Ack": + this.onInstallSuccessAck(msg.manifestURL); + break; + case "Webapps:AddReceipt": + this.addReceipt(msg, mm); + break; + case "Webapps:RemoveReceipt": + this.removeReceipt(msg, mm); + break; + case "Webapps:ReplaceReceipt": + this.replaceReceipt(msg, mm); + break; } - - if (processedImmediately) { - return; - } - - // For all the rest (asynchronous), we wait till the registry is ready - // before processing the message. - this.registryReady.then( () => { - switch (aMessage.name) { - case "Webapps:Install": { -#ifdef MOZ_WIDGET_ANDROID - Services.obs.notifyObservers(mm, "webapps-runtime-install", JSON.stringify(msg)); -#else - this.doInstall(msg, mm); -#endif - break; - } - case "Webapps:GetSelf": - this.getSelf(msg, mm); - break; - case "Webapps:Uninstall": -#ifdef MOZ_WIDGET_ANDROID - Services.obs.notifyObservers(mm, "webapps-runtime-uninstall", JSON.stringify(msg)); -#else - this.doUninstall(msg, mm); -#endif - break; - case "Webapps:Launch": - this.doLaunch(msg, mm); - break; - case "Webapps:CheckInstalled": - this.checkInstalled(msg, mm); - break; - case "Webapps:GetInstalled": - this.getInstalled(msg, mm); - break; - case "Webapps:GetNotInstalled": - this.getNotInstalled(msg, mm); - break; - case "Webapps:GetAll": - this.doGetAll(msg, mm); - break; - case "Webapps:InstallPackage": { -#ifdef MOZ_WIDGET_ANDROID - Services.obs.notifyObservers(mm, "webapps-runtime-install-package", JSON.stringify(msg)); -#else - this.doInstallPackage(msg, mm); -#endif - break; - } - case "Webapps:Download": - this.startDownload(msg.manifestURL); - break; - case "Webapps:CancelDownload": - this.cancelDownload(msg.manifestURL); - break; - case "Webapps:CheckForUpdate": - this.checkForUpdate(msg, mm); - break; - case "Webapps:ApplyDownload": - this.applyDownload(msg.manifestURL); - break; - case "Webapps:Install:Return:Ack": - this.onInstallSuccessAck(msg.manifestURL); - break; - case "Webapps:AddReceipt": - this.addReceipt(msg, mm); - break; - case "Webapps:RemoveReceipt": - this.removeReceipt(msg, mm); - break; - case "Webapps:ReplaceReceipt": - this.replaceReceipt(msg, mm); - break; - } - }); }, getAppInfo: function getAppInfo(aAppId) { @@ -1283,38 +1245,6 @@ this.DOMApplicationRegistry = { return deferred.promise; }, - /** - * Returns the full list of apps and manifests. - */ - doGetList: function() { - let tmp = []; - - let res = {}; - let done = false; - - // We allow cloning the registry when the local processing has been done. - this.safeToClone.then( () => { - for (let id in this.webapps) { - tmp.push({ id: id }); - } - this._readManifests(tmp).then( - function(manifests) { - manifests.forEach((item) => { - res[item.id] = item.manifest; - }); - done = true; - } - ); - }); - - let thread = Services.tm.currentThread; - while (!done) { - thread.processNextEvent(/* mayWait */ true); - } - return { webapps: this.webapps, manifests: res }; - }, - - doLaunch: function (aData, aMm) { this.launch( aData.manifestURL, @@ -1400,7 +1330,7 @@ this.DOMApplicationRegistry = { downloading: false }, error: error, - id: app.id + manifestURL: app.manifestURL, }) this.broadcastMessage("Webapps:FireEvent", { eventType: "downloaderror", @@ -1431,7 +1361,7 @@ this.DOMApplicationRegistry = { if (!app.downloadAvailable) { this.broadcastMessage("Webapps:UpdateState", { error: "NO_DOWNLOAD_AVAILABLE", - id: app.id + manifestURL: app.manifestURL }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloaderror", @@ -1479,7 +1409,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: app, manifest: jsonManifest, - id: app.id + manifestURL: aManifestURL }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadsuccess", @@ -1533,7 +1463,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: app, - id: app.id + manifestURL: aManifestURL }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadsuccess", @@ -1635,7 +1565,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: app, manifest: newManifest, - id: app.id + manifestURL: app.manifestURL }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadapplied", @@ -1674,7 +1604,7 @@ this.DOMApplicationRegistry = { installState: aApp.installState, progress: 0 }, - id: aApp.id + manifestURL: aApp.manifestURL }); let cacheUpdate = updateSvc.scheduleAppUpdate( appcacheURI, docURI, aApp.localId, false, aProfileDir); @@ -1724,7 +1654,6 @@ this.DOMApplicationRegistry = { debug("checkForUpdate for " + aData.manifestURL); function sendError(aError) { - debug("checkForUpdate error " + aError); aData.error = aError; aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); } @@ -1754,7 +1683,8 @@ this.DOMApplicationRegistry = { // then we can't have an update. if (app.origin.startsWith("app://") && app.manifestURL.startsWith("app://")) { - sendError("NOT_UPDATABLE"); + aData.error = "NOT_UPDATABLE"; + aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); return; } @@ -1771,7 +1701,8 @@ this.DOMApplicationRegistry = { if (onlyCheckAppCache) { // Bail out for packaged apps. if (app.origin.startsWith("app://")) { - sendError("NOT_UPDATABLE"); + aData.error = "NOT_UPDATABLE"; + aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); return; } @@ -1779,7 +1710,8 @@ this.DOMApplicationRegistry = { this._readManifests([{ id: id }]).then((aResult) => { let manifest = aResult[0].manifest; if (!manifest.appcache_path) { - sendError("NOT_UPDATABLE"); + aData.error = "NOT_UPDATABLE"; + aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); return; } @@ -1795,7 +1727,7 @@ this.DOMApplicationRegistry = { this._saveApps().then(() => { this.broadcastMessage("Webapps:UpdateState", { app: app, - id: app.id + manifestURL: app.manifestURL }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadavailable", @@ -1804,7 +1736,8 @@ this.DOMApplicationRegistry = { }); }); } else { - sendError("NOT_UPDATABLE"); + aData.error = "NOT_UPDATABLE"; + aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); } } }; @@ -1864,7 +1797,7 @@ this.DOMApplicationRegistry = { : "downloadapplied"; aMm.sendAsyncMessage("Webapps:UpdateState", { app: app, - id: app.id + manifestURL: app.manifestURL }); aMm.sendAsyncMessage("Webapps:FireEvent", { eventType: eventType, @@ -1891,7 +1824,7 @@ this.DOMApplicationRegistry = { : "downloadapplied"; aMm.sendAsyncMessage("Webapps:UpdateState", { app: app, - id: app.id + manifestURL: app.manifestURL }); aMm.sendAsyncMessage("Webapps:FireEvent", { eventType: eventType, @@ -2000,7 +1933,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: aApp, - id: aApp.id + manifestURL: aApp.manifestURL }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadavailable", @@ -2066,7 +1999,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: aApp, manifest: aApp.manifest, - id: aApp.id + manifestURL: aApp.manifestURL }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadapplied", @@ -2100,7 +2033,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: aApp, manifest: aApp.manifest, - id: aApp.id + manifestURL: aApp.manifestURL }); this.broadcastMessage("Webapps:FireEvent", { eventType: eventType, @@ -2531,8 +2464,7 @@ this.DOMApplicationRegistry = { } this._saveApps().then(() => { - this.broadcastMessage("Webapps:AddApp", - { id: app.id, app: app, manifest: aManifest }); + this.broadcastMessage("Webapps:AddApp", { id: app.id, app: app }); }); }), @@ -2632,8 +2564,6 @@ this.DOMApplicationRegistry = { // saved in the registry. yield this._saveApps(); - aData.isPackage ? appObject.updateManifest = jsonManifest : - appObject.manifest = jsonManifest; this.broadcastMessage("Webapps:AddApp", { id: id, app: appObject }); if (!aData.isPackage) { @@ -2716,8 +2646,7 @@ this.DOMApplicationRegistry = { delete this._manifestCache[aId]; } - this.broadcastMessage("Webapps:AddApp", - { id: aId, app: aNewApp, manifest: aManifest }); + this.broadcastMessage("Webapps:AddApp", { id: aId, app: aNewApp }); Services.obs.notifyObservers(null, "webapps-installed", JSON.stringify({ manifestURL: aNewApp.manifestURL })); @@ -2877,7 +2806,7 @@ this.DOMApplicationRegistry = { // Clear any previous download errors. error: null, app: aOldApp, - id: aId + manifestURL: aNewApp.manifestURL }); let zipFile = yield this._getPackage(requestChannel, aId, aOldApp, aNewApp); @@ -2892,7 +2821,7 @@ this.DOMApplicationRegistry = { // We send an "applied" event right away so code awaiting that event // can proceed to access the app. We also throw an error to alert // the caller that the package wasn't downloaded. - this._sendAppliedEvent(aOldApp); + this._sendAppliedEvent(aNewApp, aOldApp, aId); throw new Error("PACKAGE_UNCHANGED"); } @@ -3028,7 +2957,7 @@ this.DOMApplicationRegistry = { app: { progress: aProgress }, - id: aNewApp.id + manifestURL: aNewApp.manifestURL }); this.broadcastMessage("Webapps:FireEvent", { eventType: "progress", @@ -3145,24 +3074,27 @@ this.DOMApplicationRegistry = { * something similar after updating the app, and we could refactor both cases * to use the same code to send the "applied" event. * - * @param aApp {Object} app data + * @param aNewApp {Object} the new app data + * @param aOldApp {Object} the currently stored app data + * @param aId {String} the unique id of the app */ - _sendAppliedEvent: function(aApp) { - aApp.downloading = false; - aApp.downloadAvailable = false; - aApp.downloadSize = 0; - aApp.installState = "installed"; - aApp.readyToApplyDownload = false; - if (aApp.staged && aApp.staged.manifestHash) { + _sendAppliedEvent: function(aNewApp, aOldApp, aId) { + aOldApp.downloading = false; + aOldApp.downloadAvailable = false; + aOldApp.downloadSize = 0; + aOldApp.installState = "installed"; + aOldApp.readyToApplyDownload = false; + if (aOldApp.staged && aOldApp.staged.manifestHash) { // If we're here then the manifest has changed but the package // hasn't. Let's clear this, so we don't keep offering // a bogus update to the user - aApp.manifestHash = aApp.staged.manifestHash; - aApp.etag = aApp.staged.etag || aApp.etag; - aApp.staged = {}; - // Move the staged update manifest to a non staged one. + aOldApp.manifestHash = aOldApp.staged.manifestHash; + aOldApp.etag = aOldApp.staged.etag || aOldApp.etag; + aOldApp.staged = {}; + + // Move the staged update manifest to a non staged one. try { - let staged = this._getAppDir(aApp.id); + let staged = this._getAppDir(aId); staged.append("staged-update.webapp"); staged.moveTo(staged.parent, "update.webapp"); } catch (ex) { @@ -3173,15 +3105,15 @@ this.DOMApplicationRegistry = { // Save the updated registry, and cleanup the tmp directory. this._saveApps().then(() => { this.broadcastMessage("Webapps:UpdateState", { - app: aApp, - id: aApp.id + app: aOldApp, + manifestURL: aNewApp.manifestURL }); this.broadcastMessage("Webapps:FireEvent", { - manifestURL: aApp.manifestURL, + manifestURL: aNewApp.manifestURL, eventType: ["downloadsuccess", "downloadapplied"] }); }); - let file = FileUtils.getFile("TmpD", ["webapps", aApp.id], false); + let file = FileUtils.getFile("TmpD", ["webapps", aId], false); if (file && file.exists()) { file.remove(true); } @@ -3500,10 +3432,9 @@ this.DOMApplicationRegistry = { dir.moveTo(parent, newId); }); // Signals that we need to swap the old id with the new app. - this.broadcastMessage("Webapps:UpdateApp", { oldId: oldId, - newId: newId, - app: aOldApp }); - + this.broadcastMessage("Webapps:RemoveApp", { id: oldId }); + this.broadcastMessage("Webapps:AddApp", { id: newId, + app: aOldApp }); } } }, @@ -3606,7 +3537,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: aOldApp, error: aError, - id: aNewApp.id + manifestURL: aNewApp.manifestURL }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloaderror", @@ -3786,13 +3717,9 @@ this.DOMApplicationRegistry = { }, doGetAll: function(aData, aMm) { - // We can't do this until the registry is ready. - debug("doGetAll"); - this.registryReady.then(() => { - this.getAll(function (apps) { - aData.apps = apps; - aMm.sendAsyncMessage("Webapps:GetAll:Return:OK", aData); - }); + this.getAll(function (apps) { + aData.apps = apps; + aMm.sendAsyncMessage("Webapps:GetAll:Return:OK", aData); }); }, @@ -4160,7 +4087,7 @@ AppcacheObserver.prototype = { let app = this.app; DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", { app: app, - id: app.id + manifestURL: app.manifestURL }); DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", { eventType: "progress", @@ -4192,7 +4119,7 @@ AppcacheObserver.prototype = { app.downloadAvailable = false; DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", { app: app, - id: app.id + manifestURL: app.manifestURL }); DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", { eventType: ["downloadsuccess", "downloadapplied"], @@ -4215,7 +4142,7 @@ AppcacheObserver.prototype = { DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", { app: app, error: aError, - id: app.id + manifestURL: app.manifestURL }); DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", { eventType: "downloaderror", diff --git a/dom/apps/tests/test_packaged_app_common.js b/dom/apps/tests/test_packaged_app_common.js index 449aa513d78c..abcdeacf6008 100644 --- a/dom/apps/tests/test_packaged_app_common.js +++ b/dom/apps/tests/test_packaged_app_common.js @@ -102,7 +102,6 @@ var PackagedTestHelper = (function PackagedTestHelper() { var aApp = evt.application; aApp.ondownloaderror = function(evt) { var error = aApp.downloadError.name; - ok(true, "Got downloaderror " + error); if (error == aExpectedError) { ok(true, "Got expected " + aExpectedError); var expected = { diff --git a/dom/apps/tests/test_packaged_app_update.html b/dom/apps/tests/test_packaged_app_update.html index 3b1fc9957750..1278e767ca48 100644 --- a/dom/apps/tests/test_packaged_app_update.html +++ b/dom/apps/tests/test_packaged_app_update.html @@ -79,15 +79,15 @@ function updateApp(aExpectedReady, aPreviousVersion, aNextVersion) { checkLastAppState.bind(PackagedTestHelper, miniManifestURL, false, false, aNextVersion, PackagedTestHelper.next); - var ondownloadsuccesshandler = - checkLastAppState.bind(undefined, miniManifestURL, - aExpectedReady, false, aPreviousVersion, - function() { - navigator.mozApps.mgmt.applyDownload(lApp); - }); + var ondownloadsuccesshandler = + checkLastAppState.bind(undefined, miniManifestURL, + aExpectedReady, false, aPreviousVersion, + function() { + navigator.mozApps.mgmt.applyDownload(lApp); + }); - checkForUpdate(true, ondownloadsuccesshandler, ondownloadappliedhandler, - null, true); + checkForUpdate(true, ondownloadsuccesshandler, ondownloadappliedhandler, null, + true); } @@ -254,7 +254,7 @@ var steps = [ "&appName=arandomname" + "&appToFail1"; PackagedTestHelper.checkAppDownloadError(miniManifestURL, - "MANIFEST_MISMATCH", 1, false, true, + "MANIFEST_MISMATCH", 2, false, true, "arandomname", function () { checkForUpdate(false, null, null, null, false, diff --git a/dom/apps/tests/test_receipt_operations.html b/dom/apps/tests/test_receipt_operations.html index 7ebe84ccfae0..0907e8d964c4 100644 --- a/dom/apps/tests/test_receipt_operations.html +++ b/dom/apps/tests/test_receipt_operations.html @@ -243,4 +243,4 @@ addLoadEvent(go); - + \ No newline at end of file diff --git a/dom/canvas/test/mochitest.ini b/dom/canvas/test/mochitest.ini index 6227d5a31736..f3019f95ff63 100644 --- a/dom/canvas/test/mochitest.ini +++ b/dom/canvas/test/mochitest.ini @@ -186,6 +186,7 @@ disabled = bug 407107 [test_bug764125.html] [test_bug856472.html] [test_bug866575.html] +skip-if = (toolkit == 'gonk' && debug) #bug 1045153 [test_bug902651.html] [test_canvas.html] skip-if = (toolkit == 'gonk' && debug) #debug-only crash; bug 933541 diff --git a/toolkit/devtools/server/actors/webapps.js b/toolkit/devtools/server/actors/webapps.js index c4221cda95ac..8c96990ba2e2 100644 --- a/toolkit/devtools/server/actors/webapps.js +++ b/toolkit/devtools/server/actors/webapps.js @@ -262,7 +262,7 @@ WebappsActor.prototype = { reg.broadcastMessage("Webapps:UpdateState", { app: aApp, manifest: manifest, - id: aApp.id + manifestURL: aApp.manifestURL }); reg.broadcastMessage("Webapps:FireEvent", { eventType: ["downloadsuccess", "downloadapplied"],