Bug 820456 - Installing a big packaged app could kill the homescreen and make the phone unresponsive [r=ferjm]
authorFabrice Desré <fabrice@mozilla.com>
Fri, 14 Dec 2012 11:29:25 -0800
changeset 116102 f75a9f57c14e796b6eae9637e3a40a602dc8bb18
parent 116101 e0b6350b8b24fc514556637eee4ecfc2fa5b5adb
child 116103 11e5207523120ac832493e25647a049073950257
push id19730
push userfdesre@mozilla.com
push dateFri, 14 Dec 2012 19:29:34 +0000
treeherdermozilla-inbound@f75a9f57c14e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersferjm
bugs820456
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 820456 - Installing a big packaged app could kill the homescreen and make the phone unresponsive [r=ferjm]
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.