From c72ded32539aa5c0f3295523d59265ca82848fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Jim=C3=A9nez?= Date: Fri, 23 May 2014 14:21:09 -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 | 285 +++++++++++++++++- dom/apps/src/Webapps.js | 297 ++++++++----------- dom/apps/src/Webapps.jsm | 113 ++++--- dom/apps/tests/test_packaged_app_common.js | 1 + dom/apps/tests/test_packaged_app_update.html | 19 +- dom/apps/tests/test_receipt_operations.html | 2 +- toolkit/devtools/server/actors/webapps.js | 2 +- 7 files changed, 458 insertions(+), 261 deletions(-) diff --git a/dom/apps/src/AppsServiceChild.jsm b/dom/apps/src/AppsServiceChild.jsm index c7cb9890f768..ed454bc40d14 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,315 @@ 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 (!(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: {}, + 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)); + this.cpmm.sendAsyncMessage("Webapps:RegisterForMessages", { + messages: APPS_IPC_MSG_NAMES + }); + // 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]; + // XXX should we do this async and block callers if it's not yet there? + 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. + // And 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; 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 (aMessage.error) { + 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); diff --git a/dom/apps/src/Webapps.js b/dom/apps/src/Webapps.js index 49138a527b5f..8691875fd722 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", @@ -264,50 +265,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; } @@ -320,27 +280,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; @@ -348,40 +293,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) { @@ -425,7 +413,7 @@ WebappsApplication.prototype = { }, get downloadError() { - return new this._window.DOMError(this._downloadError || ''); + return new this._window.DOMError(this._proxy.downloadError || ''); }, download: function() { @@ -467,12 +455,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"); } @@ -480,28 +467,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)); }, @@ -550,12 +542,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) { @@ -572,21 +559,15 @@ WebappsApplication.prototype = { } }, - _updateState: function(aMsg) { - if (aMsg.app) { - for (let prop in aMsg.app) { - this[prop] = aMsg.app[prop]; - } - } - - if (aMsg.error) { - this._downloadError = aMsg.error; - } - - if (aMsg.manifest) { - this._manifest = aMsg.manifest; - manifestCache.evict(this.manifestURL, this.innerWindowID); + _fireRequestResult: function(aMessage, aIsError) { + let req; + let msg = aMessage.data; + req = this.takeRequest(msg.requestID); + if (!req) { + return; } + aIsError ? Services.DOMRequest.fireError(req, msg.error) + : Services.DOMRequest.fireSuccess(req, msg.result); }, receiveMessage: function(aMessage) { @@ -600,10 +581,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; } @@ -618,45 +596,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 ("_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); @@ -665,9 +611,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 = @@ -849,12 +798,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; @@ -883,7 +828,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 ec88ecf5b052..f44ea64e2b01 100755 --- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -1121,8 +1121,7 @@ this.DOMApplicationRegistry = { this.removeMessageListener(["Webapps:Internal:AllMessages"], mm); break; case "Webapps:GetList": - this.addMessageListener(["Webapps:AddApp", "Webapps:RemoveApp"], null, mm); - return this.webapps; + return { webapps: this.webapps, manifests: this._manifestCache }; case "Webapps:Download": this.startDownload(msg.manifestURL); break; @@ -1304,7 +1303,7 @@ this.DOMApplicationRegistry = { downloading: false }, error: error, - manifestURL: app.manifestURL, + id: app.id }) this.broadcastMessage("Webapps:FireEvent", { eventType: "downloaderror", @@ -1334,7 +1333,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", @@ -1382,7 +1381,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: app, manifest: jsonManifest, - manifestURL: aManifestURL + id: app.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadsuccess", @@ -1426,7 +1425,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: app, - manifestURL: aManifestURL + id: app.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadsuccess", @@ -1526,7 +1525,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: app, manifest: aData, - manifestURL: app.manifestURL + id: app.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadapplied", @@ -1566,7 +1565,7 @@ this.DOMApplicationRegistry = { installState: aApp.installState, progress: 0 }, - manifestURL: aApp.manifestURL + id: aApp.id }); let cacheUpdate = updateSvc.scheduleAppUpdate( appcacheURI, docURI, aApp.localId, false, aProfileDir); @@ -1616,6 +1615,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); } @@ -1645,8 +1645,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; } @@ -1663,8 +1662,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; } @@ -1672,8 +1670,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; } @@ -1689,7 +1686,7 @@ this.DOMApplicationRegistry = { this._saveApps().then(() => { this.broadcastMessage("Webapps:UpdateState", { app: app, - manifestURL: app.manifestURL + id: app.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadavailable", @@ -1698,8 +1695,7 @@ this.DOMApplicationRegistry = { }); }); } else { - aData.error = "NOT_UPDATABLE"; - aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); + sendError("NOT_UPDATABLE"); } } }; @@ -1759,7 +1755,7 @@ this.DOMApplicationRegistry = { : "downloadapplied"; aMm.sendAsyncMessage("Webapps:UpdateState", { app: app, - manifestURL: app.manifestURL + id: app.id }); aMm.sendAsyncMessage("Webapps:FireEvent", { eventType: eventType, @@ -1786,7 +1782,7 @@ this.DOMApplicationRegistry = { : "downloadapplied"; aMm.sendAsyncMessage("Webapps:UpdateState", { app: app, - manifestURL: app.manifestURL + id: app.id }); aMm.sendAsyncMessage("Webapps:FireEvent", { eventType: eventType, @@ -1895,7 +1891,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: aApp, - manifestURL: aApp.manifestURL + id: aApp.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadavailable", @@ -1961,7 +1957,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: aApp, manifest: aApp.manifest, - manifestURL: aApp.manifestURL + id: aApp.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadapplied", @@ -1995,7 +1991,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: aApp, manifest: aApp.manifest, - manifestURL: aApp.manifestURL + id: aApp.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: eventType, @@ -2523,6 +2519,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 && aData.autoInstall) { // Skip directly to onInstallSuccessAck, since there isn't @@ -2616,7 +2614,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: app, manifest: aManifest, - manifestURL: aNewApp.manifestURL + id: app.id }); // Check if we have asm.js code to preload for this application. @@ -2730,14 +2728,11 @@ this.DOMApplicationRegistry = { oldApp, aNewApp); - AppDownloadManager.add( - aNewApp.manifestURL, - { - channel: requestChannel, - appId: id, - previousState: aIsUpdate ? "installed" : "pending" - } - ); + AppDownloadManager.add(aNewApp.manifestURL, { + channel: requestChannel, + appId: id, + previousState: aIsUpdate ? "installed" : "pending" + }); // We set the 'downloading' flag to true right before starting the fetch. oldApp.downloading = true; @@ -2760,7 +2755,7 @@ this.DOMApplicationRegistry = { debug("package's etag or hash unchanged; sending 'applied' event"); // The package's Etag or hash has not changed. // We send a "applied" event right away. - this._sendAppliedEvent(aNewApp, oldApp, id); + this._sendAppliedEvent(oldApp); return; } @@ -2900,7 +2895,7 @@ this.DOMApplicationRegistry = { app: { progress: aProgress }, - manifestURL: aNewApp.manifestURL + id: aNewApp.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "progress", @@ -3017,27 +3012,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) { @@ -3048,15 +3040,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); } @@ -3361,9 +3353,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 }); + } } }, @@ -3466,7 +3459,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: aOldApp, error: aError, - manifestURL: aNewApp.manifestURL + id: aNewApp.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloaderror", @@ -4016,7 +4009,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", @@ -4048,7 +4041,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"], @@ -4062,7 +4055,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 9a0bddbff4c3..7c91021f1f64 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 7add5ffe93f4..900b4e43e761 100644 --- a/dom/apps/tests/test_packaged_app_update.html +++ b/dom/apps/tests/test_packaged_app_update.html @@ -79,16 +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); - }); - - checkForUpdate(true, ondownloadsuccesshandler, ondownloadappliedhandler, null, - true); + var ondownloadsuccesshandler = + checkLastAppState.bind(undefined, miniManifestURL, + aExpectedReady, false, aPreviousVersion, + function() { + navigator.mozApps.mgmt.applyDownload(lApp); + }); + checkForUpdate(true, ondownloadsuccesshandler, ondownloadappliedhandler, null, + true); } @@ -174,7 +173,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 0907e8d964c4..7ebe84ccfae0 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 329b15b15af6..597182560e2e 100644 --- a/toolkit/devtools/server/actors/webapps.js +++ b/toolkit/devtools/server/actors/webapps.js @@ -254,7 +254,7 @@ WebappsActor.prototype = { reg.broadcastMessage("Webapps:UpdateState", { app: aApp, manifest: manifest, - manifestURL: aApp.manifestURL + id: aApp.id }); reg.broadcastMessage("Webapps:FireEvent", { eventType: ["downloadsuccess", "downloadapplied"],