diff --git a/dom/apps/src/Webapps.jsm b/dom/apps/src/Webapps.jsm index 3e0cc82af04..a242eba6466 100755 --- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -1428,21 +1428,31 @@ this.DOMApplicationRegistry = { } let manifest = new ManifestHelper(json, app.manifestURL); - let [aId, aManifest] = yield this.downloadPackage(manifest, { - manifestURL: aManifestURL, - origin: app.origin, - installOrigin: app.installOrigin, - downloadSize: app.downloadSize - }, isUpdate); + let newApp = { + manifestURL: aManifestURL, + origin: app.origin, + installOrigin: app.installOrigin, + downloadSize: app.downloadSize + }; + + let newManifest, newId; + + try { + [newId, newManifest] = yield this.downloadPackage(id, app, manifest, newApp, isUpdate); + } catch (ex) { + this.revertDownloadPackage(id, app, newApp, isUpdate, ex); + throw ex; + } // Success! Keep the zip in of TmpD, we'll move it out when // applyDownload() will be called. // Save the manifest in TmpD also - let manFile = OS.Path.join(OS.Constants.Path.tmpDir, "webapps", aId, + let manFile = OS.Path.join(OS.Constants.Path.tmpDir, "webapps", newId, "manifest.webapp"); - yield this._writeFile(manFile, JSON.stringify(aManifest)); + yield this._writeFile(manFile, JSON.stringify(newManifest)); + + app = this.webapps[id]; - app = this.webapps[aId]; // Set state and fire events. app.downloading = false; app.downloadAvailable = false; @@ -2281,7 +2291,7 @@ this.DOMApplicationRegistry = { queuedDownload: {}, queuedPackageDownload: {}, - onInstallSuccessAck: function(aManifestURL, aDontNeedNetwork) { + onInstallSuccessAck: Task.async(function*(aManifestURL, aDontNeedNetwork) { // If we are offline, register to run when we'll be online. if ((Services.io.offline) && !aDontNeedNetwork) { let onlineWrapper = { @@ -2314,11 +2324,18 @@ this.DOMApplicationRegistry = { delete this.queuedPackageDownload[aManifestURL]; - this.downloadPackage(manifest, newApp, false).then( - this._onDownloadPackage.bind(this, newApp, installSuccessCallback) - ); + let id = this._appIdForManifestURL(newApp.manifestURL); + let oldApp = this.webapps[id]; + let newManifest, newId; + try { + [newId, newManifest] = yield this.downloadPackage(id, oldApp, manifest, newApp, false); + + yield this._onDownloadPackage(newApp, installSuccessCallback, newId, newManifest); + } catch (ex) { + this.revertDownloadPackage(id, oldApp, newApp, false, ex); + } } - }, + }), _setupApp: function(aData, aId) { let app = aData.app; @@ -2603,7 +2620,7 @@ this.DOMApplicationRegistry = { * @param aManifest {Object} The manifest of the application */ _onDownloadPackage: Task.async(function*(aNewApp, aInstallSuccessCallback, - [aId, aManifest]) { + aId, aManifest) { debug("_onDownloadPackage"); // Success! Move the zip out of TmpD. let app = this.webapps[aId]; @@ -2737,7 +2754,7 @@ this.DOMApplicationRegistry = { }.bind(this)).then(null, Cu.reportError); }, - downloadPackage: function(aManifest, aNewApp, aIsUpdate, aOnSuccess) { + downloadPackage: Task.async(function*(aId, aOldApp, aManifest, aNewApp, aIsUpdate) { // Here are the steps when installing a package: // - create a temp directory where to store the app. // - download the zip in this directory. @@ -2745,91 +2762,80 @@ this.DOMApplicationRegistry = { // - extract the manifest from the zip and check it. // - ask confirmation to the user. // - add the new app to the registry. - // If we fail at any step, we revert the previous ones and return an error. + yield this._ensureSufficientStorage(aNewApp); - // We define these outside the task to use them in its reject handler. - let id = this._appIdForManifestURL(aNewApp.manifestURL); - let oldApp = this.webapps[id]; + let fullPackagePath = aManifest.fullPackagePath(); + // Check if it's a local file install (we've downloaded/sideloaded the + // package already, it existed on the build, or it came with an APK). + // Note that this variable also controls whether files signed with expired + // certificates are accepted or not. If isLocalFileInstall is true and the + // device date is earlier than the build generation date, then the signature + // will be accepted even if the certificate is expired. + let isLocalFileInstall = + Services.io.extractScheme(fullPackagePath) === 'file'; - return Task.spawn((function*() { - yield this._ensureSufficientStorage(aNewApp); + debug("About to download " + fullPackagePath); - let fullPackagePath = aManifest.fullPackagePath(); + let requestChannel = this._getRequestChannel(fullPackagePath, + isLocalFileInstall, + aOldApp, + aNewApp); - // Check if it's a local file install (we've downloaded/sideloaded the - // package already, it existed on the build, or it came with an APK). - // Note that this variable also controls whether files signed with expired - // certificates are accepted or not. If isLocalFileInstall is true and the - // device date is earlier than the build generation date, then the signature - // will be accepted even if the certificate is expired. - let isLocalFileInstall = - Services.io.extractScheme(fullPackagePath) === 'file'; + AppDownloadManager.add( + aNewApp.manifestURL, + { + channel: requestChannel, + appId: aId, + previousState: aIsUpdate ? "installed" : "pending" + } + ); - debug("About to download " + fullPackagePath); + // We set the 'downloading' flag to true right before starting the fetch. + aOldApp.downloading = true; - let requestChannel = this._getRequestChannel(fullPackagePath, - isLocalFileInstall, - oldApp, - aNewApp); + // We determine the app's 'installState' according to its previous + // state. Cancelled download should remain as 'pending'. Successfully + // installed apps should morph to 'updating'. + aOldApp.installState = aIsUpdate ? "updating" : "pending"; - AppDownloadManager.add( - aNewApp.manifestURL, - { - channel: requestChannel, - appId: id, - previousState: aIsUpdate ? "installed" : "pending" - } - ); + // initialize the progress to 0 right now + aOldApp.progress = 0; - // We set the 'downloading' flag to true right before starting the fetch. - oldApp.downloading = true; + // Save the current state of the app to handle cases where we may be + // retrying a past download. + yield DOMApplicationRegistry._saveApps(); - // We determine the app's 'installState' according to its previous - // state. Cancelled download should remain as 'pending'. Successfully - // installed apps should morph to 'updating'. - oldApp.installState = aIsUpdate ? "updating" : "pending"; - - // initialize the progress to 0 right now - oldApp.progress = 0; - - // Save the current state of the app to handle cases where we may be - // retrying a past download. - yield DOMApplicationRegistry._saveApps(); - DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", { + DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", { // Clear any previous download errors. error: null, - app: oldApp, + app: aOldApp, manifestURL: aNewApp.manifestURL - }); + }); - let zipFile = yield this._getPackage(requestChannel, id, oldApp, aNewApp); - let hash = yield this._computeFileHash(zipFile.path); + let zipFile = yield this._getPackage(requestChannel, aId, aOldApp, aNewApp); + let hash = yield this._computeFileHash(zipFile.path); - let responseStatus = requestChannel.responseStatus; - let oldPackage = (responseStatus == 304 || hash == oldApp.packageHash); + let responseStatus = requestChannel.responseStatus; + let oldPackage = (responseStatus == 304 || hash == aOldApp.packageHash); - if (oldPackage) { - debug("package's etag or hash unchanged; sending 'applied' event"); - // The package's Etag or hash has not changed. - // 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, oldApp, id); - throw new Error("PACKAGE_UNCHANGED"); - } + if (oldPackage) { + debug("package's etag or hash unchanged; sending 'applied' event"); + // The package's Etag or hash has not changed. + // 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); + throw new Error("PACKAGE_UNCHANGED"); + } - let newManifest = yield this._openAndReadPackage(zipFile, oldApp, aNewApp, - isLocalFileInstall, aIsUpdate, aManifest, requestChannel, hash); + let newManifest = yield this._openAndReadPackage(zipFile, aOldApp, aNewApp, + isLocalFileInstall, aIsUpdate, aManifest, requestChannel, hash); - AppDownloadManager.remove(aNewApp.manifestURL); + AppDownloadManager.remove(aNewApp.manifestURL); - return [oldApp.id, newManifest]; + return [aOldApp.id, newManifest]; - }).bind(this)).then( - aOnSuccess, - this._revertDownloadPackage.bind(this, id, oldApp, aNewApp, aIsUpdate) - ); - }, + }), _ensureSufficientStorage: function(aNewApp) { let deferred = Promise.defer(); @@ -3494,8 +3500,8 @@ this.DOMApplicationRegistry = { }, // Removes the directory we created, and sends an error to the DOM side. - _revertDownloadPackage: function(aId, aOldApp, aNewApp, aIsUpdate, aError) { - debug("Cleanup: " + aError + "\n" + aError.stack); + revertDownloadPackage: function(aId, aOldApp, aNewApp, aIsUpdate, aError) { + debug("Error downloading package: " + aError); let dir = FileUtils.getDir("TmpD", ["webapps", aId], true, true); try { dir.remove(true); @@ -3536,8 +3542,6 @@ this.DOMApplicationRegistry = { }); }); AppDownloadManager.remove(aNewApp.manifestURL); - - throw aError; }, doUninstall: function(aData, aMm) {