Bug 820456 - Installing a big packaged app could kill the homescreen and make the phone unresponsive. r=ferjm, a=blocking-basecamp
authorFabrice Desré <fabrice@mozilla.com>
Fri, 14 Dec 2012 11:29:25 -0800
changeset 118884 c28d377a35af4c042ce4fb904a4942bb5f7f45c9
parent 118883 9eecd64f9b84278f5e4ab0b2d46cb7ed42a5406f
child 118885 0c3461f4494e0f832ec338631664c7f2e0707122
push id2975
push userryanvm@gmail.com
push dateMon, 17 Dec 2012 02:40:46 +0000
treeherdermozilla-aurora@1736e9ff1576 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersferjm, blocking-basecamp
bugs820456
milestone19.0a2
Bug 820456 - Installing a big packaged app could kill the homescreen and make the phone unresponsive. r=ferjm, a=blocking-basecamp
dom/apps/src/Webapps.jsm
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -773,16 +773,20 @@ this.DOMApplicationRegistry = {
           this.getAll(msg, mm);
         else
           mm.sendAsyncMessage("Webapps:GetAll:Return:KO", msg);
         break;
       case "Webapps:InstallPackage":
         this.doInstallPackage(msg, mm);
         break;
       case "Webapps:GetBasePath":
+        if (!this.webapps[msg.id]) {
+          debug("No webapp for " + msg.id);
+          return null;
+        }
         return this.webapps[msg.id].basePath;
         break;
       case "Webapps:RegisterForMessages":
         this.addMessageListener(msg, mm);
         break;
       case "Webapps:UnregisterForMessages":
         this.removeMessageListener(msg, mm);
         break;
@@ -1145,17 +1149,17 @@ this.DOMApplicationRegistry = {
         aData.app = app;
         if (!manifest.appcache_path) {
           aData.event = "downloadapplied";
           aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:OK", aData);
         } else {
           // Check if the appcache is updatable, and send "downloadavailable" or
           // "downloadapplied".
           let updateObserver = {
-            observe: function(aSubject, aTopic, aData) {
+            observe: function(aSubject, aTopic, aObsData) {
               aData.event =
                 aTopic == "offline-cache-update-available" ? "downloadavailable"
                                                            : "downloadapplied";
               aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:OK", aData);
             }
           }
           updateSvc.checkForUpdate(Services.io.newURI(aData.manifestURL, null, null),
                                    app.localId, false, updateObserver);
@@ -1686,20 +1690,20 @@ this.DOMApplicationRegistry = {
 
           throw Cr.NS_ERROR_NO_INTERFACE;
         },
         getInterface: function notifGI(aIID) {
           return this.QueryInterface(aIID);
         },
         onProgress: function notifProgress(aRequest, aContext,
                                            aProgress, aProgressMax) {
-          debug("onProgress: " + aProgress + "/" + aProgressMax);
           app.progress = aProgress;
           let now = Date.now();
           if (now - lastProgressTime > MIN_PROGRESS_EVENT_DELAY) {
+            debug("onProgress: " + aProgress + "/" + aProgressMax);
             self.broadcastMessage("Webapps:PackageEvent",
                                   { type: "progress",
                                     manifestURL: aApp.manifestURL,
                                     app: app });
             lastProgressTime = now;
           }
         },
         onStatus: function notifStatus(aRequest, aContext, aStatus, aStatusArg) { },
@@ -1718,44 +1722,51 @@ this.DOMApplicationRegistry = {
 
       // 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. XXX: this can consume all disk space.
-        let zipFile = FileUtils.getFile("TmpD",
-                                        ["webapps", id, "application.zip"], true);
-        let ostream = FileUtils.openSafeFileOutputStream(zipFile);
-        NetUtil.asyncCopy(aInput, ostream, function (aResult) {
-          if (!Components.isSuccessCode(aResult)) {
-            // We failed to save the zip.
-            cleanup("DOWNLOAD_ERROR");
+      // Staging the zip in TmpD until all the checks are done.
+      let zipFile = FileUtils.getFile("TmpD",
+                                      ["webapps", id, "application.zip"], true);
+
+      // We need an output stream to write the channel content to the zip file.
+      let outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
+                           .createInstance(Ci.nsIFileOutputStream);
+      // write, create, truncate
+      outputStream.init(zipFile, 0x02 | 0x08 | 0x20, parseInt("0664", 8), 0);
+      let bufferedOutputStream = Cc['@mozilla.org/network/buffered-output-stream;1']
+                                   .createInstance(Ci.nsIBufferedOutputStream);
+      bufferedOutputStream.init(outputStream, 1024);
+
+      // Create a listener that will give data to the file output stream.
+      let listener = Cc["@mozilla.org/network/simple-stream-listener;1"]
+                       .createInstance(Ci.nsISimpleStreamListener);
+      listener.init(bufferedOutputStream, {
+        onStartRequest: function(aRequest, aContext) { },
+        onStopRequest: function(aRequest, aContext, aStatusCode) {
+          debug("onStopRequest " + aStatusCode);
+          bufferedOutputStream.close();
+          outputStream.close();
+
+          let certdb;
+          try {
+            certdb = Cc["@mozilla.org/security/x509certdb;1"]
+                       .getService(Ci.nsIX509CertDB);
+          } catch (e) {
+            cleanup("CERTDB_ERROR");
             return;
           }
 
-		  let certdb;
-		  try {
-			certdb = Cc["@mozilla.org/security/x509certdb;1"]
-					   .getService(Ci.nsIX509CertDB);
-		  } catch (e) {
-		    cleanup("CERTDB_ERROR");
-			return;
-		  }
           certdb.openSignedJARFileAsync(zipFile, function(aRv, aZipReader) {
+            let zipReader;
             try {
-              let zipReader;
               let isSigned;
               if (Components.isSuccessCode(aRv)) {
                 isSigned = true;
                 zipReader = aZipReader;
               } else if (aRv != Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED) {
                 throw "INVALID_SIGNATURE";
               } else {
                 isSigned = false;
@@ -1797,26 +1808,29 @@ this.DOMApplicationRegistry = {
 
               if (aOnSuccess) {
                 aOnSuccess(id, manifest);
               }
               delete self.downloads[aApp.manifestURL];
             } catch (e) {
               // Something bad happened when reading the package.
               if (typeof e == 'object') {
+                debug(e);
                 cleanup("INVALID_PACKAGE");
               } else {
                 cleanup(e);
               }
             } finally {
               zipReader.close();
             }
           });
-        });
+        }
       });
+
+      requestChannel.asyncOpen(listener, null);
     };
 
     let deviceStorage = Services.wm.getMostRecentWindow("navigator:browser")
                                 .navigator.getDeviceStorage("apps");
     let req = deviceStorage.stat();
     req.onsuccess = req.onerror = function statResult(e) {
       // Even if we could not retrieve the device storage free space, we try
       // to download the package.