From bc88eee7bcf9fe464945bb9280a6462e689d219f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Jim=C3=A9nez?= Date: Sat, 24 May 2014 12:28:13 -0700 Subject: [PATCH] Bug 903291 - App download hangs indefinitely if the child process dies before confirming the install. r=fabrice * * * Bug 903291 - App download hangs indefinitely if the child process dies before confirming the install --- dom/apps/src/AppsServiceChild.jsm | 297 +++++++++++++++++- dom/apps/src/Webapps.js | 305 ++++++++----------- dom/apps/src/Webapps.jsm | 139 +++++---- dom/apps/tests/test_packaged_app_common.js | 1 + dom/apps/tests/test_packaged_app_update.html | 18 +- dom/apps/tests/test_receipt_operations.html | 2 +- toolkit/devtools/server/actors/webapps.js | 2 +- 7 files changed, 501 insertions(+), 263 deletions(-) diff --git a/dom/apps/src/AppsServiceChild.jsm b/dom/apps/src/AppsServiceChild.jsm index c7cb9890f76..5bf6a5aad74 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 functionnalities of the parent DOM -// Registry to content processes, to be be used from the AppsService component. +// 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"]; +this.EXPORTED_SYMBOLS = ["DOMApplicationRegistry", "WrappedManifestCache"]; Cu.import("resource://gre/modules/AppsUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); @@ -20,54 +20,323 @@ 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); - ["Webapps:AddApp", "Webapps:RemoveApp"].forEach((function(aMsgName) { + APPS_IPC_MSG_NAMES.forEach((function(aMsgName) { this.cpmm.addMessageListener(aMsgName, this); }).bind(this)); - // We need to prime the cache with the list of apps. - // XXX shoud we do this async and block callers if it's not yet there? - this.webapps = this.cpmm.sendSyncMessage("Webapps:GetList", { })[0]; + 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; // 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; - ["Webapps:AddApp", "Webapps:RemoveApp"].forEach((function(aMsgName) { + this.DOMApps = null; + + APPS_IPC_MSG_NAMES.forEach((aMsgName) => { this.cpmm.removeMessageListener(aMsgName, this); - }).bind(this)); + }); + + this.cpmm.sendAsyncMessage("Webapps:UnregisterForMessages", + APPS_IPC_MSG_NAMES) }, receiveMessage: function receiveMessage(aMessage) { debug("Received " + aMessage.name + " message."); - let msg = aMessage.json; + 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) { + apps.splice(i); + 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++) { + if (!apps[i].get()) { + apps.splice(i); + } + } + + 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); @@ -89,7 +358,7 @@ this.DOMApplicationRegistry = { }, getAppByLocalId: function getAppByLocalId(aLocalId) { - debug("getAppByLocalId " + aLocalId); + debug("getAppByLocalId " + aLocalId + " - ready: " + this.ready); 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 47a3156159f..e267f3a56b2 100644 --- a/dom/apps/src/Webapps.js +++ b/dom/apps/src/Webapps.js @@ -12,6 +12,7 @@ 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", @@ -278,50 +279,9 @@ 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; } @@ -334,27 +294,12 @@ 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; @@ -362,40 +307,83 @@ WebappsApplication.prototype = { this._ondownloadavailable = null; this._ondownloadapplied = null; - this._downloadError = null; + this.initDOMRequestHelper(aWindow); + }, - 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 _appStatus() { + return this._proxy.appStatus; + }, - cpmm.sendAsyncMessage("Webapps:RegisterForMessages", { - messages: ["Webapps:FireEvent", - "Webapps:UpdateState"], - app: { - id: this.id, - manifestURL: this.manifestURL, - installState: this.installState, - downloading: this.downloading - } - }); + 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; }, get manifest() { - return manifestCache.get(this.manifestURL, - this._manifest, - this._window, - this.innerWindowID); + return WrappedManifestCache.get(this.manifestURL, + this._proxy.manifest, + this._window, + this.innerWindowID); }, get updateManifest() { - return this.updateManifest = - this._updateManifest ? Cu.cloneInto(this._updateManifest, this._window) - : null; + return this._proxy.updateManifest ? + Cu.cloneInto(this._proxy.updateManifest, this._window) : null; }, set onprogress(aCallback) { @@ -440,10 +428,10 @@ WebappsApplication.prototype = { get downloadError() { // Only return DOMError when we have an error. - if (!this._downloadError) { + if (!this._proxy.downloadError) { return null; } - return new this._window.DOMError(this._downloadError); + return new this._window.DOMError(this._proxy.downloadError); }, download: function() { @@ -485,12 +473,11 @@ 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"); } @@ -498,28 +485,33 @@ 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)); }, @@ -568,12 +560,7 @@ WebappsApplication.prototype = { uninit: function() { this._onprogress = null; - cpmm.sendAsyncMessage("Webapps:UnregisterForMessages", [ - "Webapps:FireEvent", - "Webapps:UpdateState" - ]); - - manifestCache.evict(this.manifestURL, this.innerWindowID); + WrappedManifestCache.evict(this.manifestURL, this.innerWindowID); }, _fireEvent: function(aName) { @@ -590,22 +577,16 @@ WebappsApplication.prototype = { } }, - _updateState: function(aMsg) { - if (aMsg.app) { - for (let prop in aMsg.app) { - this[prop] = aMsg.app[prop]; - } + _fireRequestResult: function(aMessage, aIsError) { + let req; + let msg = aMessage.data; + req = this.takeRequest(msg.requestID); + if (!req) { + return; } - // 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); - } + aIsError ? Services.DOMRequest.fireError(req, msg.error) + : Services.DOMRequest.fireSuccess(req, msg.result); }, receiveMessage: function(aMessage) { @@ -619,10 +600,7 @@ WebappsApplication.prototype = { req = this.takeRequest(msg.requestID); } - // ondownload* callbacks should be triggered on all app instances - if ((msg.oid != this._id || !req) && - aMessage.name !== "Webapps:FireEvent" && - aMessage.name !== "Webapps:UpdateState") { + if (msg.oid !== this._id || !req) { return; } @@ -637,51 +615,13 @@ 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); @@ -690,9 +630,12 @@ 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 = @@ -874,12 +817,8 @@ 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, detail) }); + { application : createApplicationObject(this._window, msg) }); this._onuninstall.handleEvent(event); } break; @@ -908,7 +847,5 @@ 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 fe57310d272..b5fae7d151c 100755 --- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -1141,8 +1141,8 @@ this.DOMApplicationRegistry = { this.removeMessageListener(["Webapps:Internal:AllMessages"], mm); break; case "Webapps:GetList": - this.addMessageListener(["Webapps:AddApp", "Webapps:RemoveApp"], null, mm); - return this.webapps; + return this.doGetList(); + break; case "Webapps:Download": this.startDownload(msg.manifestURL); break; @@ -1245,6 +1245,38 @@ this.DOMApplicationRegistry = { return deferred.promise; }, + /** + * Returns the full list of apps and manifests. + */ + doGetList: function() { + let tmp = []; + + for (let id in this.webapps) { + tmp.push({ id: id }); + } + + let res = {}; + let done = false; + + this._readManifests(tmp).then( + function(manifests) { + manifests.forEach((item) => { + res[item.id] = item.manifest; + }); + done = true; + } + ); + + let thread = Services.tm.currentThread; + while (!done) { + //debug("before processNextEvent"); + thread.processNextEvent(/* mayWait */ true); + //after("before processNextEvent"); + } + return { webapps: this.webapps, manifests: res }; + }, + + doLaunch: function (aData, aMm) { this.launch( aData.manifestURL, @@ -1330,7 +1362,7 @@ this.DOMApplicationRegistry = { downloading: false }, error: error, - manifestURL: app.manifestURL, + id: app.id }) this.broadcastMessage("Webapps:FireEvent", { eventType: "downloaderror", @@ -1361,7 +1393,7 @@ this.DOMApplicationRegistry = { if (!app.downloadAvailable) { this.broadcastMessage("Webapps:UpdateState", { error: "NO_DOWNLOAD_AVAILABLE", - manifestURL: app.manifestURL + id: app.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloaderror", @@ -1409,7 +1441,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: app, manifest: jsonManifest, - manifestURL: aManifestURL + id: app.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadsuccess", @@ -1463,7 +1495,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: app, - manifestURL: aManifestURL + id: app.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadsuccess", @@ -1565,7 +1597,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: app, manifest: newManifest, - manifestURL: app.manifestURL + id: app.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadapplied", @@ -1604,7 +1636,7 @@ this.DOMApplicationRegistry = { installState: aApp.installState, progress: 0 }, - manifestURL: aApp.manifestURL + id: aApp.id }); let cacheUpdate = updateSvc.scheduleAppUpdate( appcacheURI, docURI, aApp.localId, false, aProfileDir); @@ -1654,6 +1686,7 @@ this.DOMApplicationRegistry = { debug("checkForUpdate for " + aData.manifestURL); function sendError(aError) { + debug("checkForUpdate error " + aError); aData.error = aError; aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); } @@ -1683,8 +1716,7 @@ this.DOMApplicationRegistry = { // then we can't have an update. if (app.origin.startsWith("app://") && app.manifestURL.startsWith("app://")) { - aData.error = "NOT_UPDATABLE"; - aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); + sendError("NOT_UPDATABLE"); return; } @@ -1701,8 +1733,7 @@ this.DOMApplicationRegistry = { if (onlyCheckAppCache) { // Bail out for packaged apps. if (app.origin.startsWith("app://")) { - aData.error = "NOT_UPDATABLE"; - aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); + sendError("NOT_UPDATABLE"); return; } @@ -1710,8 +1741,7 @@ this.DOMApplicationRegistry = { this._readManifests([{ id: id }]).then((aResult) => { let manifest = aResult[0].manifest; if (!manifest.appcache_path) { - aData.error = "NOT_UPDATABLE"; - aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); + sendError("NOT_UPDATABLE"); return; } @@ -1727,7 +1757,7 @@ this.DOMApplicationRegistry = { this._saveApps().then(() => { this.broadcastMessage("Webapps:UpdateState", { app: app, - manifestURL: app.manifestURL + id: app.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadavailable", @@ -1736,8 +1766,7 @@ this.DOMApplicationRegistry = { }); }); } else { - aData.error = "NOT_UPDATABLE"; - aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); + sendError("NOT_UPDATABLE"); } } }; @@ -1797,7 +1826,7 @@ this.DOMApplicationRegistry = { : "downloadapplied"; aMm.sendAsyncMessage("Webapps:UpdateState", { app: app, - manifestURL: app.manifestURL + id: app.id }); aMm.sendAsyncMessage("Webapps:FireEvent", { eventType: eventType, @@ -1824,7 +1853,7 @@ this.DOMApplicationRegistry = { : "downloadapplied"; aMm.sendAsyncMessage("Webapps:UpdateState", { app: app, - manifestURL: app.manifestURL + id: app.id }); aMm.sendAsyncMessage("Webapps:FireEvent", { eventType: eventType, @@ -1933,7 +1962,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: aApp, - manifestURL: aApp.manifestURL + id: aApp.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadavailable", @@ -1999,7 +2028,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: aApp, manifest: aApp.manifest, - manifestURL: aApp.manifestURL + id: aApp.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadapplied", @@ -2033,7 +2062,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: aApp, manifest: aApp.manifest, - manifestURL: aApp.manifestURL + id: aApp.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: eventType, @@ -2464,7 +2493,8 @@ this.DOMApplicationRegistry = { } this._saveApps().then(() => { - this.broadcastMessage("Webapps:AddApp", { id: app.id, app: app }); + this.broadcastMessage("Webapps:AddApp", + { id: app.id, app: app, manifest: aManifest }); }); }), @@ -2564,6 +2594,8 @@ 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) { @@ -2646,7 +2678,8 @@ this.DOMApplicationRegistry = { delete this._manifestCache[aId]; } - this.broadcastMessage("Webapps:AddApp", { id: aId, app: aNewApp }); + this.broadcastMessage("Webapps:AddApp", + { id: aId, app: aNewApp, manifest: aManifest }); Services.obs.notifyObservers(null, "webapps-installed", JSON.stringify({ manifestURL: aNewApp.manifestURL })); @@ -2806,7 +2839,7 @@ this.DOMApplicationRegistry = { // Clear any previous download errors. error: null, app: aOldApp, - manifestURL: aNewApp.manifestURL + id: aId }); let zipFile = yield this._getPackage(requestChannel, aId, aOldApp, aNewApp); @@ -2821,7 +2854,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(aNewApp, aOldApp, aId); + this._sendAppliedEvent(aOldApp); throw new Error("PACKAGE_UNCHANGED"); } @@ -2957,7 +2990,7 @@ this.DOMApplicationRegistry = { app: { progress: aProgress }, - manifestURL: aNewApp.manifestURL + id: aNewApp.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "progress", @@ -3074,27 +3107,24 @@ 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 aNewApp {Object} the new app data - * @param aOldApp {Object} the currently stored app data - * @param aId {String} the unique id of the app + * @param aApp {Object} app data */ - _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) { + _sendAppliedEvent: function(aApp) { + aApp.downloading = false; + aApp.downloadAvailable = false; + aApp.downloadSize = 0; + aApp.installState = "installed"; + aApp.readyToApplyDownload = false; + if (aApp.staged && aApp.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 - aOldApp.manifestHash = aOldApp.staged.manifestHash; - aOldApp.etag = aOldApp.staged.etag || aOldApp.etag; - aOldApp.staged = {}; - - // Move the staged update manifest to a non staged one. + aApp.manifestHash = aApp.staged.manifestHash; + aApp.etag = aApp.staged.etag || aApp.etag; + aApp.staged = {}; + // Move the staged update manifest to a non staged one. try { - let staged = this._getAppDir(aId); + let staged = this._getAppDir(aApp.id); staged.append("staged-update.webapp"); staged.moveTo(staged.parent, "update.webapp"); } catch (ex) { @@ -3105,15 +3135,15 @@ this.DOMApplicationRegistry = { // Save the updated registry, and cleanup the tmp directory. this._saveApps().then(() => { this.broadcastMessage("Webapps:UpdateState", { - app: aOldApp, - manifestURL: aNewApp.manifestURL + app: aApp, + id: aApp.id }); this.broadcastMessage("Webapps:FireEvent", { - manifestURL: aNewApp.manifestURL, + manifestURL: aApp.manifestURL, eventType: ["downloadsuccess", "downloadapplied"] }); }); - let file = FileUtils.getFile("TmpD", ["webapps", aId], false); + let file = FileUtils.getFile("TmpD", ["webapps", aApp.id], false); if (file && file.exists()) { file.remove(true); } @@ -3432,9 +3462,10 @@ this.DOMApplicationRegistry = { dir.moveTo(parent, newId); }); // Signals that we need to swap the old id with the new app. - this.broadcastMessage("Webapps:RemoveApp", { id: oldId }); - this.broadcastMessage("Webapps:AddApp", { id: newId, - app: aOldApp }); + this.broadcastMessage("Webapps:UpdateApp", { oldId: oldId, + newId: newId, + app: aOldApp }); + } } }, @@ -3537,7 +3568,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: aOldApp, error: aError, - manifestURL: aNewApp.manifestURL + id: aNewApp.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloaderror", @@ -4087,7 +4118,7 @@ AppcacheObserver.prototype = { let app = this.app; DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", { app: app, - manifestURL: app.manifestURL + id: app.id }); DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", { eventType: "progress", @@ -4119,7 +4150,7 @@ AppcacheObserver.prototype = { app.downloadAvailable = false; DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", { app: app, - manifestURL: app.manifestURL + id: app.id }); DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", { eventType: ["downloadsuccess", "downloadapplied"], @@ -4142,7 +4173,7 @@ AppcacheObserver.prototype = { DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", { app: app, error: aError, - manifestURL: app.manifestURL + id: app.id }); 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 9a0bddbff4c..7c91021f1f6 100644 --- a/dom/apps/tests/test_packaged_app_common.js +++ b/dom/apps/tests/test_packaged_app_common.js @@ -98,6 +98,7 @@ 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 1278e767ca4..3b1fc995775 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", 2, false, true, + "MANIFEST_MISMATCH", 1, 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 0907e8d964c..7ebe84ccfae 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/toolkit/devtools/server/actors/webapps.js b/toolkit/devtools/server/actors/webapps.js index 8c96990ba2e..c4221cda95a 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, - manifestURL: aApp.manifestURL + id: aApp.id }); reg.broadcastMessage("Webapps:FireEvent", { eventType: ["downloadsuccess", "downloadapplied"],