Merge fx-team to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Sat, 07 Jun 2014 13:32:47 -0400
changeset 206483 ee2320d65148fb218321c7f706597493a3555aac
parent 206478 94055a293286f472d16b43429adfcb6aceff5343 (current diff)
parent 206482 7331dd991a5c084a1ae6b85f44a3ed60dcdff724 (diff)
child 206529 ff99f6b4ae83f151fc51c4b84d2a47d3aa773217
push id3741
push userasasaki@mozilla.com
push dateMon, 21 Jul 2014 20:25:18 +0000
treeherdermozilla-beta@4d6f46f5af68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone32.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
Merge fx-team to m-c. a=merge
--- a/browser/themes/shared/devtools/toolbars.inc.css
+++ b/browser/themes/shared/devtools/toolbars.inc.css
@@ -505,16 +505,18 @@
   width: 2px;
   background-image: linear-gradient(hsla(204,45%,98%,0), hsla(204,45%,98%,.1), hsla(204,45%,98%,0)),
                     linear-gradient(hsla(206,37%,4%,0), hsla(206,37%,4%,.6), hsla(206,37%,4%,0)),
                     linear-gradient(hsla(204,45%,98%,0), hsla(204,45%,98%,.1), hsla(204,45%,98%,0));
   background-size: 1px 100%;
   background-repeat: no-repeat;
   background-position: 0, 1px, 2px;
 }
+
+#toolbox-buttons:empty + #toolbox-controls-separator,
 #toolbox-controls-separator[invisible] {
   visibility: hidden;
 }
 
 /* Command buttons */
 
 .command-button {
   -moz-appearance: none;
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -1328,36 +1328,36 @@ this.DOMApplicationRegistry = {
 
   startDownload: Task.async(function*(aManifestURL) {
     debug("startDownload for " + aManifestURL);
 
     let id = this._appIdForManifestURL(aManifestURL);
     let app = this.webapps[id];
     if (!app) {
       debug("startDownload: No app found for " + aManifestURL);
-      return;
+      throw new Error("NO_SUCH_APP");
     }
 
     if (app.downloading) {
       debug("app is already downloading. Ignoring.");
-      return;
+      throw new Error("APP_IS_DOWNLOADING");
     }
 
     // If the caller is trying to start a download but we have nothing to
     // download, send an error.
     if (!app.downloadAvailable) {
       this.broadcastMessage("Webapps:UpdateState", {
         error: "NO_DOWNLOAD_AVAILABLE",
         manifestURL: app.manifestURL
       });
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloaderror",
         manifestURL: app.manifestURL
       });
-      return;
+      throw new Error("NO_DOWNLOAD_AVAILABLE");
     }
 
     // First of all, we check if the download is supposed to update an
     // already installed application.
     let isUpdate = (app.installState == "installed");
 
     // An app download would only be triggered for two reasons: an app
     // update or while retrying to download a previously failed or canceled
@@ -1404,17 +1404,17 @@ this.DOMApplicationRegistry = {
 
       return;
     }
 
     let json = yield AppsUtils.loadJSONAsync(file.path);
     if (!json) {
       debug("startDownload: No update manifest found at " + file.path + " " +
             aManifestURL);
-      return;
+      throw new Error("MISSING_UPDATE_MANIFEST");
     }
 
     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
@@ -1445,114 +1445,113 @@ this.DOMApplicationRegistry = {
       manifestURL: aManifestURL
     });
     if (app.installState == "pending") {
       // We restarted a failed download, apply it automatically.
       this.applyDownload(aManifestURL);
     }
   }),
 
-  applyDownload: function applyDownload(aManifestURL) {
+  applyDownload: Task.async(function*(aManifestURL) {
     debug("applyDownload for " + aManifestURL);
     let id = this._appIdForManifestURL(aManifestURL);
     let app = this.webapps[id];
-    if (!app || (app && !app.readyToApplyDownload)) {
-      return;
+    if (!app) {
+      throw new Error("NO_SUCH_APP");
+    }
+    if (!app.readyToApplyDownload) {
+      throw new Error("NOT_READY_TO_APPLY_DOWNLOAD");
     }
 
     // We need to get the old manifest to unregister web activities.
-    this.getManifestFor(aManifestURL).then((aOldManifest) => {
-      // Move the application.zip and manifest.webapp files out of TmpD
-      let tmpDir = FileUtils.getDir("TmpD", ["webapps", id], true, true);
-      let manFile = tmpDir.clone();
-      manFile.append("manifest.webapp");
-      let appFile = tmpDir.clone();
-      appFile.append("application.zip");
-
-      let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
-      appFile.moveTo(dir, "application.zip");
-      manFile.moveTo(dir, "manifest.webapp");
-
-      // Move the staged update manifest to a non staged one.
-      let staged = dir.clone();
-      staged.append("staged-update.webapp");
-
-      // If we are applying after a restarted download, we have no
-      // staged update manifest.
-      if (staged.exists()) {
-        staged.moveTo(dir, "update.webapp");
-      }
-
-      try {
-        tmpDir.remove(true);
-      } catch(e) { }
-
-      // Clean up the deprecated manifest cache if needed.
-      if (id in this._manifestCache) {
-        delete this._manifestCache[id];
+    let oldManifest = yield this.getManifestFor(aManifestURL);
+    // Move the application.zip and manifest.webapp files out of TmpD
+    let tmpDir = FileUtils.getDir("TmpD", ["webapps", id], true, true);
+    let manFile = tmpDir.clone();
+    manFile.append("manifest.webapp");
+    let appFile = tmpDir.clone();
+    appFile.append("application.zip");
+
+    let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
+    appFile.moveTo(dir, "application.zip");
+    manFile.moveTo(dir, "manifest.webapp");
+
+    // Move the staged update manifest to a non staged one.
+    let staged = dir.clone();
+    staged.append("staged-update.webapp");
+
+    // If we are applying after a restarted download, we have no
+    // staged update manifest.
+    if (staged.exists()) {
+      staged.moveTo(dir, "update.webapp");
+    }
+
+    try {
+      tmpDir.remove(true);
+    } catch(e) { }
+
+    // Clean up the deprecated manifest cache if needed.
+    if (id in this._manifestCache) {
+      delete this._manifestCache[id];
+    }
+
+    // Flush the zip reader cache to make sure we use the new application.zip
+    // when re-launching the application.
+    let zipFile = dir.clone();
+    zipFile.append("application.zip");
+    Services.obs.notifyObservers(zipFile, "flush-cache-entry", null);
+
+    // Get the manifest, and set properties.
+    let newManifest = yield this.getManifestFor(aManifestURL);
+    app.downloading = false;
+    app.downloadAvailable = false;
+    app.downloadSize = 0;
+    app.installState = "installed";
+    app.readyToApplyDownload = false;
+
+    // Update the staged properties.
+    if (app.staged) {
+      for (let prop in app.staged) {
+        app[prop] = app.staged[prop];
       }
-
-      // Flush the zip reader cache to make sure we use the new application.zip
-      // when re-launching the application.
-      let zipFile = dir.clone();
-      zipFile.append("application.zip");
-      Services.obs.notifyObservers(zipFile, "flush-cache-entry", null);
-
-      // Get the manifest, and set properties.
-      this.getManifestFor(aManifestURL).then((aData) => {
-        app.downloading = false;
-        app.downloadAvailable = false;
-        app.downloadSize = 0;
-        app.installState = "installed";
-        app.readyToApplyDownload = false;
-
-        // Update the staged properties.
-        if (app.staged) {
-          for (let prop in app.staged) {
-            app[prop] = app.staged[prop];
-          }
-          delete app.staged;
-        }
-
-        delete app.retryingDownload;
-
-        // Update the asm.js scripts we need to compile.
-        ScriptPreloader.preload(app, aData)
-          .then(() => this._saveApps()).then(() => {
-          // Update the handlers and permissions for this app.
-          this.updateAppHandlers(aOldManifest, aData, app);
-
-          AppsUtils.loadJSONAsync(staged.path).then((aUpdateManifest) => {
-            let appObject = AppsUtils.cloneAppObject(app);
-            appObject.updateManifest = aUpdateManifest;
-            this.notifyUpdateHandlers(appObject, aData, appFile.path);
-          });
-
-          if (supportUseCurrentProfile()) {
-            PermissionsInstaller.installPermissions(
-              { manifest: aData,
-                origin: app.origin,
-                manifestURL: app.manifestURL },
-              true);
-          }
-          this.updateDataStore(this.webapps[id].localId, app.origin,
-                               app.manifestURL, aData, app.appStatus);
-          this.broadcastMessage("Webapps:UpdateState", {
-            app: app,
-            manifest: aData,
-            manifestURL: app.manifestURL
-          });
-          this.broadcastMessage("Webapps:FireEvent", {
-            eventType: "downloadapplied",
-            manifestURL: app.manifestURL
-          });
-        });
-      });
+      delete app.staged;
+    }
+
+    delete app.retryingDownload;
+
+    // Update the asm.js scripts we need to compile.
+    yield ScriptPreloader.preload(app, newManifest);
+    yield this._saveApps();
+    // Update the handlers and permissions for this app.
+    this.updateAppHandlers(oldManifest, newManifest, app);
+
+    let updateManifest = yield AppsUtils.loadJSONAsync(staged.path);
+    let appObject = AppsUtils.cloneAppObject(app);
+    appObject.updateManifest = updateManifest;
+    this.notifyUpdateHandlers(appObject, newManifest, appFile.path);
+
+    if (supportUseCurrentProfile()) {
+      PermissionsInstaller.installPermissions(
+        { manifest: newManifest,
+          origin: app.origin,
+          manifestURL: app.manifestURL },
+        true);
+    }
+    this.updateDataStore(this.webapps[id].localId, app.origin,
+                         app.manifestURL, newManifest, app.appStatus);
+    this.broadcastMessage("Webapps:UpdateState", {
+      app: app,
+      manifest: newManifest,
+      manifestURL: app.manifestURL
     });
-  },
+    this.broadcastMessage("Webapps:FireEvent", {
+      eventType: "downloadapplied",
+      manifestURL: app.manifestURL
+    });
+  }),
 
   startOfflineCacheDownload: function(aManifest, aApp, aProfileDir, aIsUpdate) {
     if (!aManifest.appcache_path) {
       return;
     }
 
     // If the manifest has an appcache_path property, use it to populate the
     // appcache.
@@ -2768,19 +2767,21 @@ this.DOMApplicationRegistry = {
       let hash = yield this._computeFileHash(zipFile.path);
 
       let responseStatus = requestChannel.responseStatus;
       let oldPackage = (responseStatus == 304 || hash == oldApp.packageHash);
 
       if (oldPackage) {
         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.
+        // 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);
-        return;
+        throw new Error("PACKAGE_UNCHANGED");
       }
 
       let newManifest = yield this._openAndReadPackage(zipFile, oldApp, aNewApp,
               isLocalFileInstall, aIsUpdate, aManifest, requestChannel, hash);
 
       AppDownloadManager.remove(aNewApp.manifestURL);
 
       return [oldApp.id, newManifest];
@@ -3483,16 +3484,18 @@ this.DOMApplicationRegistry = {
         manifestURL: aNewApp.manifestURL
       });
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloaderror",
         manifestURL:  aNewApp.manifestURL
       });
     });
     AppDownloadManager.remove(aNewApp.manifestURL);
+
+    throw aError;
   },
 
   doUninstall: function(aData, aMm) {
     this.uninstall(aData.manifestURL,
       function onsuccess() {
         aMm.sendAsyncMessage("Webapps:Uninstall:Return:OK", aData);
       },
       function onfailure() {
--- a/mobile/android/base/webapp/InstallHelper.java
+++ b/mobile/android/base/webapp/InstallHelper.java
@@ -14,34 +14,36 @@ import java.io.OutputStream;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.gfx.BitmapUtils;
-import org.mozilla.gecko.util.GeckoEventListener;
+import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.util.NativeEventListener;
+import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.util.Log;
 
-public class InstallHelper implements GeckoEventListener {
+public class InstallHelper implements NativeEventListener {
     private static final String LOGTAG = "GeckoWebappInstallHelper";
     private static final String[] INSTALL_EVENT_NAMES = new String[] {"Webapps:Postinstall"};
     private final Context mContext;
     private final InstallCallback mCallback;
     private final ApkResources mApkResources;
 
     public static interface InstallCallback {
         // on the GeckoThread
-        void installCompleted(InstallHelper installHelper, String event, JSONObject message);
+        void installCompleted(InstallHelper installHelper, String event, NativeJSObject message);
 
         // on the GeckoBackgroundThread
         void installErrored(InstallHelper installHelper, Exception exception);
     }
 
     public InstallHelper(Context context, ApkResources apkResources, InstallCallback cb) {
         mContext = context;
         mCallback = cb;
@@ -157,16 +159,16 @@ public class InstallHelper implements Ge
         ThreadUtils.assertOnBackgroundThread();
         Allocator slots = Allocator.getInstance(mContext);
         int index = slots.getIndexForApp(mApkResources.getPackageName());
         Bitmap bitmap = BitmapUtils.getBitmapFromDrawable(mApkResources.getAppIcon());
         slots.updateColor(index, BitmapUtils.getDominantColor(bitmap));
     }
 
     @Override
-    public void handleMessage(String event, JSONObject message) {
+    public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this, INSTALL_EVENT_NAMES);
 
         if (mCallback != null) {
             mCallback.installCompleted(this, event, message);
         }
     }
 }
--- a/mobile/android/base/webapp/WebappImpl.java
+++ b/mobile/android/base/webapp/WebappImpl.java
@@ -13,16 +13,17 @@ import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
+import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.webapp.InstallHelper.InstallCallback;
 
 import android.content.Intent;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.net.Uri;
@@ -320,23 +321,23 @@ public class WebappImpl extends GeckoApp
                 @Override
                 public void onAnimationStart(Animation animation) { }
             });
             mSplashscreen.startAnimation(fadeout);
         }
     }
 
     @Override
-    public void installCompleted(InstallHelper installHelper, String event, JSONObject message) {
+    public void installCompleted(InstallHelper installHelper, String event, NativeJSObject message) {
         if (event == null) {
             return;
         }
 
         if (event.equals("Webapps:Postinstall")) {
-            String origin = message.optString("origin");
+            String origin = message.optString("origin", null);
             launchWebapp(origin);
         }
     }
 
     @Override
     public void installErrored(InstallHelper installHelper, Exception exception) {
         Log.e(LOGTAG, "Install errored", exception);
     }
--- a/mobile/android/modules/WebappManager.jsm
+++ b/mobile/android/modules/WebappManager.jsm
@@ -153,47 +153,56 @@ this.WebappManager = {
     }
 
     // Trigger the download.
     worker.postMessage({ url: generatorUrl.spec, path: file.path });
 
     return deferred.promise;
   },
 
-  askInstall: function(aData) {
-    let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
-    file.initWithPath(aData.profilePath);
-
+  _deleteAppcachePath: function(aManifest) {
     // We don't yet support pre-installing an appcache because it isn't clear
     // how to do it without degrading the user experience (since users expect
     // apps to be available after the system tells them they've been installed,
     // which has already happened) and because nsCacheService shuts down
     // when we trigger the native install dialog and doesn't re-init itself
     // afterward (TODO: file bug about this behavior).
-    if ("appcache_path" in aData.app.manifest) {
-      debug("deleting appcache_path from manifest: " + aData.app.manifest.appcache_path);
-      delete aData.app.manifest.appcache_path;
+    if ("appcache_path" in aManifest) {
+      debug("deleting appcache_path from manifest: " + aManifest.appcache_path);
+      delete aManifest.appcache_path;
     }
+  },
+
+  askInstall: function(aData) {
+    let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+    file.initWithPath(aData.profilePath);
+
+    this._deleteAppcachePath(aData.app.manifest);
 
     DOMApplicationRegistry.registryReady.then(() => {
       DOMApplicationRegistry.confirmInstall(aData, file, (function(aManifest) {
-        let localeManifest = new ManifestHelper(aManifest, aData.app.origin);
-
-        // aData.app.origin may now point to the app: url that hosts this app.
-        sendMessageToJava({
-          type: "Webapps:Postinstall",
-          apkPackageName: aData.app.apkPackageName,
-          origin: aData.app.origin,
-        });
-
-        this.writeDefaultPrefs(file, localeManifest);
+        this._postInstall(aData.profilePath, aManifest, aData.app.origin, aData.app.apkPackageName);
       }).bind(this));
     });
   },
 
+  _postInstall: function(aProfilePath, aNewManifest, aOrigin, aApkPackageName) {
+    // aOrigin may now point to the app: url that hosts this app.
+    sendMessageToJava({
+      type: "Webapps:Postinstall",
+      apkPackageName: aApkPackageName,
+      origin: aOrigin,
+    });
+
+    let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+    file.initWithPath(aProfilePath);
+    let localeManifest = new ManifestHelper(aNewManifest, aOrigin);
+    this.writeDefaultPrefs(file, localeManifest);
+  },
+
   launch: function({ manifestURL, origin }) {
     debug("launchWebapp: " + manifestURL);
 
     sendMessageToJava({
       type: "Webapps:Open",
       manifestURL: manifestURL,
       origin: origin
     });
@@ -206,21 +215,26 @@ this.WebappManager = {
       // We don't have to do anything, as the registry does all the work.
       return;
     }
 
     // TODO: uninstall the APK.
   },
 
   autoInstall: function(aData) {
-    let oldApp = DOMApplicationRegistry.getAppByManifestURL(aData.manifestURL);
-    if (oldApp) {
-      // If the app is already installed, update the existing installation.
-      this._autoUpdate(aData, oldApp);
-      return;
+    debug("autoInstall " + aData.manifestURL);
+
+    // If the app is already installed, update the existing installation.
+    // We should be able to use DOMApplicationRegistry.getAppByManifestURL,
+    // but it returns a mozIApplication, while _autoUpdate needs the original
+    // object from DOMApplicationRegistry.webapps in order to modify it.
+    for (let [ , app] in Iterator(DOMApplicationRegistry.webapps)) {
+      if (app.manifestURL == aData.manifestURL) {
+        return this._autoUpdate(aData, app);
+      }
     }
 
     let mm = {
       sendAsyncMessage: function (aMessageName, aData) {
         // TODO hook this back to Java to report errors.
         debug("sendAsyncMessage " + aMessageName + ": " + JSON.stringify(aData));
       }
     };
@@ -271,22 +285,48 @@ this.WebappManager = {
     if (aOldApp.apkPackageName != aData.apkPackageName) {
       // This happens when the app was installed as a shortcut via the old
       // runtime and is now being updated to an APK.
       debug("update apkPackageName from " + aOldApp.apkPackageName + " to " + aData.apkPackageName);
       aOldApp.apkPackageName = aData.apkPackageName;
     }
 
     if (aData.type == "hosted") {
+      this._deleteAppcachePath(aData.manifest);
       let oldManifest = yield DOMApplicationRegistry.getManifestFor(aData.manifestURL);
-      DOMApplicationRegistry.updateHostedApp(aData, aOldApp.id, aOldApp, oldManifest, aData.manifest);
+      yield DOMApplicationRegistry.updateHostedApp(aData, aOldApp.id, aOldApp, oldManifest, aData.manifest);
     } else {
-      DOMApplicationRegistry.updatePackagedApp(aData, aOldApp.id, aOldApp, aData.manifest);
+      yield this._autoUpdatePackagedApp(aData, aOldApp);
+    }
+
+    this._postInstall(aData.profilePath, aData.manifest, aOldApp.origin, aOldApp.apkPackageName);
+  }).bind(this)); },
+
+  _autoUpdatePackagedApp: Task.async(function*(aData, aOldApp) {
+    debug("_autoUpdatePackagedApp: " + aData.manifestURL);
+
+    if (aData.updateManifest && aData.zipFilePath) {
+      aData.updateManifest.package_path = aData.zipFilePath;
     }
-  }).bind(this)); },
+
+    // updatePackagedApp just prepares the update, after which we must
+    // download the package via the misnamed startDownload and then apply it
+    // via applyDownload.
+    yield DOMApplicationRegistry.updatePackagedApp(aData, aOldApp.id, aOldApp, aData.updateManifest);
+
+    try {
+      yield DOMApplicationRegistry.startDownload(aData.manifestURL);
+    } catch (ex if ex.message == "PACKAGE_UNCHANGED") {
+      debug("package unchanged");
+      // If the package is unchanged, then there's nothing more to do.
+      return;
+    }
+
+    yield DOMApplicationRegistry.applyDownload(aData.manifestURL);
+  }),
 
   _checkingForUpdates: false,
 
   checkForUpdates: function(userInitiated) { return Task.spawn((function*() {
     debug("checkForUpdates");
 
     // Don't start checking for updates if we're already doing so.
     // TODO: Consider cancelling the old one and starting a new one anyway