Bug 815547 - [Apps] Inconsistent app attribute status when download error; r=fabrice
authorFernando Jiménez <ferjmoreno@gmail.com>
Thu, 29 Nov 2012 15:19:37 +0100
changeset 123593 5d2fb47b62788c819bcecd9221f3b16c8c1e4a1f
parent 123592 985e380010e329be7b3ef0f19d7fbe19020d9d63
child 123594 ccabd35aa7d43c61bb87b570587e4ff8d317daef
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs815547
milestone20.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 815547 - [Apps] Inconsistent app attribute status when download error; r=fabrice
dom/apps/src/Webapps.js
dom/apps/src/Webapps.jsm
--- a/dom/apps/src/Webapps.js
+++ b/dom/apps/src/Webapps.js
@@ -568,19 +568,23 @@ WebappsApplication.prototype = {
           Services.DOMRequest.fireError(req, msg.error);
           break;
         case "Webapps:PackageEvent":
           if (msg.manifestURL != this.manifestURL)
             return;
           switch(msg.type) {
             case "error":
               this._downloadError = msg.error;
+              this.downloading = msg.app.downloading;
+              this.installState = msg.app.installState;
               this._fireEvent("downloaderror", this._ondownloaderror);
               break;
             case "progress":
+              this.downloading = msg.app.downloading;
+              this.installState = msg.app.installState;
               this.progress = msg.progress;
               this._fireEvent("downloadprogress", this._onprogress);
               break;
             case "installed":
               let app = msg.app;
               this.progress = app.progress || 0;
               this.downloadAvailable = app.downloadAvailable;
               this.downloading = app.downloading;
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -752,17 +752,17 @@ this.DOMApplicationRegistry = {
       return;
     }
     // This is a HTTP channel.
     let download = this.downloads[aManifestURL]
     download.channel.cancel(Cr.NS_BINDING_ABORTED);
     let app = this.webapps[download.appId];
 
     app.progress = 0;
-    app.installState = app.previousState;
+    app.installState = download.previousState;
     app.downloading = false;
     app.downloadAvailable = false;
     app.downloadSize = 0;
     this._saveApps((function() {
       this.broadcastMessage("Webapps:PackageEvent",
                              { type: "canceled",
                                manifestURL:  app.manifestURL,
                                app: app,
@@ -774,31 +774,34 @@ this.DOMApplicationRegistry = {
     debug("startDownload for " + aManifestURL);
     let id = this._appIdForManifestURL(aManifestURL);
     let app = this.webapps[id];
     if (!app) {
       debug("startDownload: No app found for " + aManifestURL);
       return;
     }
 
+    // First of all, we check if the download is supposed to update an
+    // already installed application.
+    let isUpdate = (app.installState == "installed");
+
     // We need to get the update manifest here, not the webapp manifest.
     let file = FileUtils.getFile(DIRECTORY_NAME,
                                  ["webapps", id, "update.webapp"], true);
 
     if (!file.exists()) {
       // This is a hosted app, let's check if it has an appcache
       // and download it.
       this._readManifests([{ id: id }], (function readManifest(aResults) {
         let jsonManifest = aResults[0].manifest;
         let manifest = new ManifestHelper(jsonManifest, app.origin);
 
         if (manifest.appcache_path) {
           debug("appcache found");
-          app.installState = "updating";
-          this.startOfflineCacheDownload(manifest, app);
+          this.startOfflineCacheDownload(manifest, app, null, null, isUpdate);
         } else {
           // hosted app with no appcache, nothing to do, but we fire a
           // downloaded event
           DOMApplicationRegistry.broadcastMessage("Webapps:PackageEvent",
                                                   { type: "downloaded",
                                                     manifestURL: aManifestURL,
                                                     app: app,
                                                     manifest: jsonManifest });
@@ -811,17 +814,17 @@ this.DOMApplicationRegistry = {
     this._loadJSONAsync(file, (function(aJSON) {
       if (!aJSON) {
         debug("startDownload: No update manifest found at " + file.path + " " + aManifestURL);
         return;
       }
 
       let manifest = new ManifestHelper(aJSON, app.installOrigin);
       this.downloadPackage(manifest, { manifestURL: aManifestURL,
-                                       origin: app.origin }, true,
+                                       origin: app.origin }, isUpdate,
         function(aId, aManifest) {
           // Success! Keep the zip in of TmpD, we'll move it out when
           // applyDownload() will be called.
           let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], true, true);
 
           // Save the manifest in TmpD also
           let manFile = tmpDir.clone();
           manFile.append("manifest.webapp");
@@ -891,23 +894,33 @@ this.DOMApplicationRegistry = {
                                                 origin: app.origin,
                                                 manifestURL: app.manifestURL },
                                               true);
     }).bind(this));
   },
 
   startOfflineCacheDownload: function startOfflineCacheDownload(aManifest, aApp,
                                                                 aProfileDir,
-                                                                aOfflineCacheObserver) {
+                                                                aOfflineCacheObserver,
+                                                                aIsUpdate) {
     // if the manifest has an appcache_path property, use it to populate the appcache
     if (aManifest.appcache_path) {
       let appcacheURI = Services.io.newURI(aManifest.fullAppcachePath(), null, null);
       let updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"]
                             .getService(Ci.nsIOfflineCacheUpdateService);
       let docURI = Services.io.newURI(aManifest.fullLaunchPath(), null, null);
+      // We determine the app's 'installState' according to its previous
+      // state. Cancelled download should remain as 'pending'. Successfully
+      // installed apps should morph to 'updating'.
+      if (aIsUpdate) {
+        aApp.installState = "updating";
+      }
+      // We set the 'downloading' flag right before starting the app
+      // download/update.
+      aApp.downloading = true;
       let cacheUpdate = aProfileDir ? updateService.scheduleCustomProfileUpdate(appcacheURI, docURI, aProfileDir)
                                     : updateService.scheduleAppUpdate(appcacheURI, docURI, aApp.localId, false);
       cacheUpdate.addObserver(new AppcacheObserver(aApp), false);
       if (aOfflineCacheObserver) {
         cacheUpdate.addObserver(aOfflineCacheObserver, false);
       }
     }
   },
@@ -1325,17 +1338,17 @@ this.DOMApplicationRegistry = {
     // Here are the steps when installing a package:
     // - create a temp directory where to store the app.
     // - download the zip in this directory.
     // - 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 backout the previous ones and return an error.
 
-    debug(JSON.stringify(aApp));
+    debug("downloadPackage " + JSON.stringify(aApp));
 
     let id = this._appIdForManifestURL(aApp.manifestURL);
     let app = this.webapps[id];
 
     let self = this;
     // Removes the directory we created, and sends an error to the DOM side.
     function cleanup(aError) {
       debug("Cleanup: " + aError);
@@ -1346,20 +1359,24 @@ this.DOMApplicationRegistry = {
 
       // We avoid notifying the error to the DOM side if the app download
       // was cancelled via cancelDownload, which already sends its own
       // notification.
       if (!app.downloading && !app.downloadAvailable && !app.downloadSize) {
         return;
       }
 
+      let download = self.downloads[aApp.manifestURL];
+      app.downloading = false;
+      app.installState = download.previousState;
       self.broadcastMessage("Webapps:PackageEvent",
                             { type: "error",
                               manifestURL:  aApp.manifestURL,
-                              error: aError });
+                              error: aError,
+                              app: app });
     }
 
     function getInferedStatus() {
       // XXX Update once we have digital signatures (bug 772365)
       return Ci.nsIPrincipal.APP_STATUS_INSTALLED;
     }
 
     function getAppStatus(aManifest) {
@@ -1402,22 +1419,30 @@ this.DOMApplicationRegistry = {
         },
         onProgress: function notifProgress(aRequest, aContext,
                                            aProgress, aProgressMax) {
           debug("onProgress: " + aProgress + "/" + aProgressMax);
           app.progress = aProgress;
           self.broadcastMessage("Webapps:PackageEvent",
                                 { type: "progress",
                                   manifestURL: aApp.manifestURL,
-                                  progress: aProgress });
+                                  progress: aProgress,
+                                  app: app });
         },
         onStatus: function notifStatus(aRequest, aContext, aStatus, aStatusArg) { }
       }
-      NetUtil.asyncFetch(requestChannel,
-      function(aInput, aResult, aRequest) {
+
+      // We set the 'downloading' flag to true right before starting the fetch.
+      app.downloading = true;
+      // We determine the app's 'installState' according to its previous
+      // state. Cancelled download should remain as 'pending'. Successfully
+      // installed apps should morph to 'updating'.
+      app.installState = aIsUpdate ? "updating" : "pending";
+
+      NetUtil.asyncFetch(requestChannel, function(aInput, aResult, aRequest) {
         if (!Components.isSuccessCode(aResult)) {
           // We failed to fetch the zip.
           cleanup("NETWORK_ERROR");
           return;
         }
         // Copy the zip on disk.
         let zipFile = FileUtils.getFile("TmpD",
                                         ["webapps", id, "application.zip"], true);
@@ -1941,28 +1966,32 @@ AppcacheObserver.prototype = {
     let app = this.app;
 
     debug("Offline cache state change for " + app.origin + " : " + aState);
 
     let setStatus = function appObs_setStatus(aStatus) {
       debug("Offlinecache setStatus to " + aStatus + " for " + app.origin);
       mustSave = (app.installState != aStatus);
       app.installState = aStatus;
-      app.downloading = false;
+      if (aStatus == "installed") {
+        app.downloading = false;
+        app.downloadAvailable = false;
+      }
       DOMApplicationRegistry.broadcastMessage("Webapps:OfflineCache",
                                               { manifest: app.manifestURL,
                                                 installState: app.installState });
     }
 
     let setError = function appObs_setError(aError) {
       debug("Offlinecache setError to " + aError);
       DOMApplicationRegistry.broadcastMessage("Webapps:OfflineCache",
                                               { manifest: app.manifestURL,
                                                 error: aError });
       app.downloading = false;
+      app.downloadAvailable = false;
       mustSave = true;
     }
 
     switch (aState) {
       case Ci.nsIOfflineCacheUpdateObserver.STATE_ERROR:
         aUpdate.removeObserver(this);
         setError("APP_CACHE_DOWNLOAD_ERROR");
         break;