Bug 777402 - Use app name + manifest url hash as unique name for apps but forbid multiple apps per origin and webapposutils refactoring. r=myk,fabrice
authorMarco Castelluccio <mar.castelluccio@studenti.unina.it>
Thu, 01 Aug 2013 17:00:22 -0700
changeset 153351 fe6643be288de2cdf84dfb627299078ae03c8f28
parent 153350 30222c2903ef4f994d11f56a9bf0ab93d2b1a54f
child 153352 668bb7634d04e1ef8b7d3f82fcf751078180ee47
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmyk, fabrice
bugs777402
milestone25.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 777402 - Use app name + manifest url hash as unique name for apps but forbid multiple apps per origin and webapposutils refactoring. r=myk,fabrice
dom/apps/src/AppsUtils.jsm
dom/apps/src/Webapps.jsm
toolkit/webapps/WebappOSUtils.jsm
toolkit/webapps/WebappsInstaller.jsm
--- a/dom/apps/src/AppsUtils.jsm
+++ b/dom/apps/src/AppsUtils.jsm
@@ -477,16 +477,40 @@ this.AppsUtils = {
       if (!checkNameAndDev(aManifest1.locales[locale],
                            aManifest2.locales[locale])) {
         return false;
       }
     }
 
     // Nothing failed.
     return true;
+  },
+
+  // Returns the MD5 hash of a string.
+  computeHash: function(aString) {
+    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                      .createInstance(Ci.nsIScriptableUnicodeConverter);
+    converter.charset = "UTF-8";
+    let result = {};
+    // Data is an array of bytes.
+    let data = converter.convertToByteArray(aString, result);
+
+    let hasher = Cc["@mozilla.org/security/hash;1"]
+                   .createInstance(Ci.nsICryptoHash);
+    hasher.init(hasher.MD5);
+    hasher.update(data, data.length);
+    // We're passing false to get the binary hash and not base64.
+    let hash = hasher.finish(false);
+
+    function toHexString(charCode) {
+      return ("0" + charCode.toString(16)).slice(-2);
+    }
+
+    // Convert the binary hash data to a hex string.
+    return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
   }
 }
 
 /**
  * Helper object to access manifest information with locale support
  */
 this.ManifestHelper = function(aManifest, aOrigin) {
   this._origin = Services.io.newURI(aOrigin, null, null);
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -15,16 +15,17 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import('resource://gre/modules/ActivitiesService.jsm');
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import("resource://gre/modules/PermissionsInstaller.jsm");
 Cu.import("resource://gre/modules/OfflineCacheInstaller.jsm");
 Cu.import("resource://gre/modules/SystemMessagePermissionsChecker.jsm");
 Cu.import("resource://gre/modules/AppDownloadManager.jsm");
+Cu.import("resource://gre/modules/WebappOSUtils.jsm");
 
 #ifdef MOZ_WIDGET_GONK
 XPCOMUtils.defineLazyGetter(this, "libcutils", function() {
   Cu.import("resource://gre/modules/systemlibs.js");
   return libcutils;
 });
 #endif
 
@@ -1367,36 +1368,17 @@ this.DOMApplicationRegistry = {
         debug("Error opening " + aFile.path);
         aCallback(null);
       }
     );
   },
 
   // Returns the MD5 hash of the manifest.
   computeManifestHash: function(aManifest) {
-    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
-                      .createInstance(Ci.nsIScriptableUnicodeConverter);
-    converter.charset = "UTF-8";
-    let result = {};
-    // Data is an array of bytes.
-    let data = converter.convertToByteArray(JSON.stringify(aManifest), result);
-
-    let hasher = Cc["@mozilla.org/security/hash;1"]
-                   .createInstance(Ci.nsICryptoHash);
-    hasher.init(hasher.MD5);
-    hasher.update(data, data.length);
-    // We're passing false to get the binary hash and not base64.
-    let hash = hasher.finish(false);
-
-    function toHexString(charCode) {
-      return ("0" + charCode.toString(16)).slice(-2);
-    }
-
-    // Convert the binary hash data to a hex string.
-    return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
+    return AppsUtils.computeHash(JSON.stringify(aManifest));
   },
 
   // Updates the redirect mapping, activities and system message handlers.
   // aOldManifest can be null if we don't have any handler to unregister.
   updateAppHandlers: function(aOldManifest, aNewManifest, aApp) {
     debug("updateAppHandlers: old=" + aOldManifest + " new=" + aNewManifest);
     this.notifyAppsRegistryStart();
     if (aApp.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
@@ -1771,23 +1753,16 @@ this.DOMApplicationRegistry = {
 
     let sendError = function sendError(aError) {
       aData.error = aError;
       aMm.sendAsyncMessage("Webapps:Install:Return:KO", aData);
       Cu.reportError("Error installing app from: " + app.installOrigin +
                      ": " + aError);
     }.bind(this);
 
-    // Disallow reinstalls from the same origin for now.
-    // See https://bugzilla.mozilla.org/show_bug.cgi?id=821288
-    if (this._appId(app.origin) !== null && this._isLaunchable(app.origin)) {
-      sendError("REINSTALL_FORBIDDEN");
-      return;
-    }
-
     // Hosted apps can't be trusted or certified, so just check that the
     // manifest doesn't ask for those.
     function checkAppStatus(aManifest) {
       let manifestStatus = aManifest.type || "web";
       return manifestStatus === "web";
     }
 
     let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
@@ -1807,16 +1782,28 @@ this.DOMApplicationRegistry = {
         }
 
         app.manifest = xhr.response;
         if (!app.manifest) {
           sendError("MANIFEST_PARSE_ERROR");
           return;
         }
 
+        // Disallow multiple hosted apps installations from the same origin for now.
+        // We will remove this code after multiple apps per origin are supported (bug 778277).
+        // This will also disallow reinstalls from the same origin for now.
+        for (let id in this.webapps) {
+          if (this.webapps[id].origin == app.origin &&
+              !this.webapps[id].packageHash &&
+              this._isLaunchable(this.webapps[id])) {
+            sendError("MULTIPLE_APPS_PER_ORIGIN_FORBIDDEN");
+            return;
+          }
+        }
+
         if (!AppsUtils.checkManifest(app.manifest, app)) {
           sendError("INVALID_MANIFEST");
         } else if (!AppsUtils.checkInstallAllowed(app.manifest, app.installOrigin)) {
           sendError("INSTALL_FROM_DENIED");
         } else if (!checkAppStatus(app.manifest)) {
           sendError("INVALID_SECURITY_LEVEL");
         } else {
           app.etag = xhr.getResponseHeader("Etag");
@@ -2822,17 +2809,17 @@ this.DOMApplicationRegistry = {
       return;
     }
 
     let tmp = [];
 
     for (let id in this.webapps) {
       if (this.webapps[id].origin == aData.origin &&
           this.webapps[id].localId == aData.appId &&
-          this._isLaunchable(this.webapps[id].origin)) {
+          this._isLaunchable(this.webapps[id])) {
         let app = AppsUtils.cloneAppObject(this.webapps[id]);
         aData.apps.push(app);
         tmp.push({ id: id });
         break;
       }
     }
 
     if (!aData.apps.length) {
@@ -2869,17 +2856,17 @@ this.DOMApplicationRegistry = {
   },
 
   getInstalled: function(aData, aMm) {
     aData.apps = [];
     let tmp = [];
 
     for (let id in this.webapps) {
       if (this.webapps[id].installOrigin == aData.origin &&
-          this._isLaunchable(this.webapps[id].origin)) {
+          this._isLaunchable(this.webapps[id])) {
         aData.apps.push(AppsUtils.cloneAppObject(this.webapps[id]));
         tmp.push({ id: id });
       }
     }
 
     this._readManifests(tmp, (function(aResult) {
       for (let i = 0; i < aResult.length; i++)
         aData.apps[i].manifest = aResult[i].manifest;
@@ -2887,17 +2874,17 @@ this.DOMApplicationRegistry = {
     }).bind(this));
   },
 
   getNotInstalled: function(aData, aMm) {
     aData.apps = [];
     let tmp = [];
 
     for (let id in this.webapps) {
-      if (!this._isLaunchable(this.webapps[id].origin)) {
+      if (!this._isLaunchable(this.webapps[id])) {
         aData.apps.push(AppsUtils.cloneAppObject(this.webapps[id]));
         tmp.push({ id: id });
       }
     }
 
     this._readManifests(tmp, (function(aResult) {
       for (let i = 0; i < aResult.length; i++)
         aData.apps[i].manifest = aResult[i].manifest;
@@ -2914,17 +2901,17 @@ this.DOMApplicationRegistry = {
 
   getAll: function(aCallback) {
     debug("getAll");
     let apps = [];
     let tmp = [];
 
     for (let id in this.webapps) {
       let app = AppsUtils.cloneAppObject(this.webapps[id]);
-      if (!this._isLaunchable(app.origin))
+      if (!this._isLaunchable(app))
         continue;
 
       apps.push(app);
       tmp.push({ id: id });
     }
 
     this._readManifests(tmp, (function(aResult) {
       for (let i = 0; i < aResult.length; i++)
@@ -3067,68 +3054,21 @@ this.DOMApplicationRegistry = {
     }
 
     // Clear the manifest cache.
     this._manifestCache = { };
 
     this._saveApps(aCallback);
   },
 
-  _isLaunchable: function(aOrigin) {
+  _isLaunchable: function(aApp) {
     if (this.allAppsLaunchable)
       return true;
 
-#ifdef XP_WIN
-    let uninstallKey = Cc["@mozilla.org/windows-registry-key;1"]
-                         .createInstance(Ci.nsIWindowsRegKey);
-    try {
-      uninstallKey.open(uninstallKey.ROOT_KEY_CURRENT_USER,
-                        "SOFTWARE\\Microsoft\\Windows\\" +
-                        "CurrentVersion\\Uninstall\\" +
-                        aOrigin,
-                        uninstallKey.ACCESS_READ);
-      uninstallKey.close();
-      return true;
-    } catch (ex) {
-      return false;
-    }
-#elifdef XP_MACOSX
-    let mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"]
-                     .createInstance(Ci.nsIMacWebAppUtils);
-
-    return !!mwaUtils.pathForAppWithIdentifier(aOrigin);
-#elifdef XP_UNIX
-    let env = Cc["@mozilla.org/process/environment;1"]
-                .getService(Ci.nsIEnvironment);
-    let xdg_data_home_env;
-    try {
-      xdg_data_home_env = env.get("XDG_DATA_HOME");
-    } catch(ex) {
-    }
-
-    let desktopINI;
-    if (xdg_data_home_env) {
-      desktopINI = new FileUtils.File(xdg_data_home_env);
-    } else {
-      desktopINI = FileUtils.getFile("Home", [".local", "share"]);
-    }
-    desktopINI.append("applications");
-
-    let origin = Services.io.newURI(aOrigin, null, null);
-    let uniqueName = origin.scheme + ";" +
-                     origin.host +
-                     (origin.port != -1 ? ";" + origin.port : "");
-
-    desktopINI.append("owa-" + uniqueName + ".desktop");
-
-    return desktopINI.exists();
-#else
-    return true;
-#endif
-
+    return WebappOSUtils.isLaunchable(aApp);
   },
 
   _notifyCategoryAndObservers: function(subject, topic, data,  msg) {
     const serviceMarker = "service,";
 
     // First create observers from the category manager.
     let cm =
       Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
--- a/toolkit/webapps/WebappOSUtils.jsm
+++ b/toolkit/webapps/WebappOSUtils.jsm
@@ -3,105 +3,260 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const CC = Components.Constructor;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppsUtils.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
 
 this.EXPORTED_SYMBOLS = ["WebappOSUtils"];
 
 this.WebappOSUtils = {
-  launch: function(aData) {
+  getUniqueName: function(aApp) {
+    let name;
+
+    // During the installation of a new app, the aApp object
+    // doesn't have a name property. We then need to use the manifest.
+    // For some mozApps calls, the aApp object doesn't have a manifest
+    // associated, and so we need to use the name property.
+    if (aApp.name) {
+      name = aApp.name;
+    } else {
+      name = aApp.manifest.name;
+    }
+
+    return this.sanitizeStringForFilename(name).toLowerCase() + "-" + AppsUtils.computeHash(aApp.manifestURL);
+  },
+
+  /**
+   * Returns the executable of the given app, identifying it by its unique name,
+   * which is in either the new format or the old format.
+   * On Mac OS X, it returns the identifier of the app.
+   *
+   * The new format ensures a readable and unique name for an app by combining
+   * its name with a hash of its manifest URL.  The old format uses its origin,
+   * which is only unique until we support multiple apps per origin.
+   */
+  getLaunchTarget: function(aApp) {
+    let uniqueName = this.getUniqueName(aApp);
+
 #ifdef XP_WIN
     let appRegKey;
     try {
       let open = CC("@mozilla.org/windows-registry-key;1",
                     "nsIWindowsRegKey", "open");
-      let initWithPath = CC("@mozilla.org/file/local;1",
-                            "nsILocalFile", "initWithPath");
-      let initProcess = CC("@mozilla.org/process/util;1",
-                           "nsIProcess", "init");
-
       appRegKey = open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
                        "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" +
-                       aData.origin, Ci.nsIWindowsRegKey.ACCESS_READ);
+                       uniqueName, Ci.nsIWindowsRegKey.ACCESS_READ);
+    } catch (ex) {
+      try {
+        appRegKey = open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+                         "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" +
+                         aApp.origin, Ci.nsIWindowsRegKey.ACCESS_READ);
+      } catch (ex) {
+        return null;
+      }
+    }
+
+    let appFilename, installLocation;
+    try {
+      appFilename = appRegKey.readStringValue("AppFilename");
+      installLocation = appRegKey.readStringValue("InstallLocation");
+    } catch (ex) {
+      return null;
+    } finally {
+      appRegKey.close();
+    }
+
+    let initWithPath = CC("@mozilla.org/file/local;1",
+                          "nsILocalFile", "initWithPath");
+    let launchTarget = initWithPath(installLocation);
+    launchTarget.append(appFilename + ".exe");
+
+    return launchTarget;
+#elifdef XP_MACOSX
+    let mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"]
+                     .createInstance(Ci.nsIMacWebAppUtils);
+
+    try {
+      if (mwaUtils.pathForAppWithIdentifier(uniqueName)) {
+        return uniqueName;
+      }
+      if (mwaUtils.pathForAppWithIdentifier(aApp.origin)) {
+        return aApp.origin;
+      }
+    } catch(ex) {}
 
-      let launchTarget = initWithPath(appRegKey.readStringValue("InstallLocation"));
-      launchTarget.append(appRegKey.readStringValue("AppFilename") + ".exe");
+    return null;
+#elifdef XP_UNIX
+    let exeFile = Services.dirsvc.get("Home", Ci.nsIFile);
+    exeFile.append("." + uniqueName);
+    exeFile.append("webapprt-stub");
+
+    if (!exeFile.exists()) {
+      exeFile = Services.dirsvc.get("Home", Ci.nsIFile);
+
+      let origin = Services.io.newURI(aApp.origin, null, null);
+      let installDir = "." + origin.scheme + ";" +
+                       origin.host +
+                       (origin.port != -1 ? ";" + origin.port : "");
+
+      exeFile.append(installDir);
+      exeFile.append("webapprt-stub");
 
+      if (!exeFile.exists()) {
+        return null;
+      }
+    }
+
+    return exeFile;
+#endif
+  },
+
+  launch: function(aApp) {
+    let uniqueName = this.getUniqueName(aApp);
+
+#ifdef XP_WIN
+    let initProcess = CC("@mozilla.org/process/util;1",
+                         "nsIProcess", "init");
+
+    let launchTarget = this.getLaunchTarget(aApp);
+    if (!launchTarget) {
+      return false;
+    }
+
+    try {
       let process = initProcess(launchTarget);
       process.runwAsync([], 0);
     } catch (e) {
       return false;
-    } finally {
-      if (appRegKey) {
-        appRegKey.close();
-      }
     }
 
     return true;
 #elifdef XP_MACOSX
+    let launchIdentifier = this.getLaunchTarget(aApp);
+    if (!launchIdentifier) {
+      return false;
+    }
+
     let mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"]
-                    .createInstance(Ci.nsIMacWebAppUtils);
-    let appPath;
+                     .createInstance(Ci.nsIMacWebAppUtils);
+
     try {
-      appPath = mwaUtils.pathForAppWithIdentifier(aData.origin);
-    } catch (e) {}
+      mwaUtils.launchAppWithIdentifier(launchIdentifier);
+    } catch(e) {
+      return false;
+    }
 
-    if (appPath) {
-      mwaUtils.launchAppWithIdentifier(aData.origin);
-      return true;
+    return true;
+#elifdef XP_UNIX
+    let exeFile = this.getLaunchTarget(aApp);
+    if (!exeFile) {
+      return false;
     }
 
-    return false;
-#elifdef XP_UNIX
-    let origin = Services.io.newURI(aData.origin, null, null);
-    let installDir = "." + origin.scheme + ";" +
-                     origin.host +
-                     (origin.port != -1 ? ";" + origin.port : "");
+    try {
+      let process = Cc["@mozilla.org/process/util;1"]
+                      .createInstance(Ci.nsIProcess);
+
+      process.init(exeFile);
+      process.runAsync([], 0);
+    } catch (e) {
+      return false;
+    }
 
-    let exeFile = Services.dirsvc.get("Home", Ci.nsIFile);
-    exeFile.append(installDir);
-    exeFile.append("webapprt-stub");
+    return true;
+#endif
+  },
+
+  uninstall: function(aApp) {
+    let uniqueName = this.getUniqueName(aApp);
+
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+    let exeFile = this.getLaunchTarget(aApp);
+    if (!exeFile) {
+      return false;
+    }
 
     try {
-      if (exeFile.exists()) {
-        let process = Cc["@mozilla.org/process/util;1"]
-                        .createInstance(Ci.nsIProcess);
-        process.init(exeFile);
-        process.runAsync([], 0);
-        return true;
-      }
-    } catch (e) {}
+      let process = Cc["@mozilla.org/process/util;1"]
+                      .createInstance(Ci.nsIProcess);
 
-    return false;
+      process.init(exeFile);
+      process.runAsync(["-remove"], 1);
+    } catch (e) {
+      return false;
+    }
+
+    return true;
+#endif
 #endif
   },
 
-  uninstall: function(aData) {
-#ifdef XP_UNIX
-#ifndef XP_MACOSX
-    let origin = Services.io.newURI(aData.origin, null, null);
-    let installDir = "." + origin.scheme + ";" +
-                     origin.host +
-                     (origin.port != -1 ? ";" + origin.port : "");
+  /**
+   * Checks if the given app is locally installed.
+   */
+  isLaunchable: function(aApp) {
+    let uniqueName = this.getUniqueName(aApp);
+
+#ifdef XP_WIN
+    if (!this.getLaunchTarget(aApp)) {
+      return false;
+    }
 
-    let exeFile = Services.dirsvc.get("Home", Ci.nsIFile);
-    exeFile.append(installDir);
-    exeFile.append("webapprt-stub");
+    return true;
+#elifdef XP_MACOSX
+    if (!this.getLaunchTarget(aApp)) {
+      return false;
+    }
+
+    return true;
+#elifdef XP_UNIX
+    let env = Cc["@mozilla.org/process/environment;1"]
+                .getService(Ci.nsIEnvironment);
+
+    let xdg_data_home_env;
+    try {
+      xdg_data_home_env = env.get("XDG_DATA_HOME");
+    } catch(ex) {}
 
-    try {
-      if (exeFile.exists()) {
-        var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
-        process.init(exeFile);
-        process.runAsync(["-remove"], 1);
-        return true;
+    let desktopINI;
+    if (xdg_data_home_env) {
+      desktopINI = new FileUtils.File(xdg_data_home_env);
+    } else {
+      desktopINI = FileUtils.getFile("Home", [".local", "share"]);
+    }
+    desktopINI.append("applications");
+    desktopINI.append("owa-" + uniqueName + ".desktop");
+
+    if (!desktopINI.exists()) {
+      if (xdg_data_home_env) {
+        desktopINI = new FileUtils.File(xdg_data_home_env);
+      } else {
+        desktopINI = FileUtils.getFile("Home", [".local", "share"]);
       }
-    } catch(e) {}
+
+      let origin = Services.io.newURI(aApp.origin, null, null);
+      let oldUniqueName = origin.scheme + ";" +
+                          origin.host +
+                          (origin.port != -1 ? ";" + origin.port : "");
+
+      desktopINI.append("owa-" + oldUniqueName + ".desktop");
 
-    return false;
+      return desktopINI.exists();
+    }
+
+    return true;
 #endif
-#endif
+  },
+
+  /**
+   * Sanitize the filename (accepts only a-z, 0-9, - and _)
+   */
+  sanitizeStringForFilename: function(aPossiblyBadFilenameString) {
+    return aPossiblyBadFilenameString.replace(/[^a-z0-9_\-]/gi, "");
   }
 }
--- a/toolkit/webapps/WebappsInstaller.jsm
+++ b/toolkit/webapps/WebappsInstaller.jsm
@@ -8,16 +8,18 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/WebappOSUtils.jsm");
+Cu.import("resource://gre/modules/AppsUtils.jsm");
 
 this.WebappsInstaller = {
   /**
    * Creates a native installation of the web app in the OS
    *
    * @param aData the manifest data provided by the web app
    *
    * @returns bool true on success, false if an error was thrown
@@ -66,24 +68,19 @@ this.WebappsInstaller = {
  *
  * @param aData the data object provided by the web app with
  *              all the app settings and specifications.
  *
  */
 function NativeApp(aData) {
   let app = this.app = aData.app;
 
-  let origin = Services.io.newURI(app.origin, null, null);
+  this.uniqueName = WebappOSUtils.getUniqueName(app);
 
-  if (app.manifest.launch_path) {
-    this.launchURI = Services.io.newURI(origin.resolve(app.manifest.launch_path),
-                                        null, null);
-  } else {
-    this.launchURI = origin.clone();
-  }
+  let origin = Services.io.newURI(app.origin, null, null);
 
   let biggestIcon = getBiggestIconURL(app.manifest.icons);
   try {
     let iconURI = Services.io.newURI(biggestIcon, null, null);
     if (iconURI.scheme == "data") {
       this.iconURI = iconURI;
     }
   } catch (ex) {}
@@ -128,18 +125,17 @@ function NativeApp(aData) {
 }
 
 #ifdef XP_WIN
 /*************************************
  * Windows app installer
  *
  * The Windows installation process will generate the following files:
  *
- * ${FolderName} = protocol;app-origin[;port]
- *                 e.g.: subdomain.example.com;http;85
+ * ${FolderName} = sanitized app name + "-" + manifest url hash
  *
  * %APPDATA%/${FolderName}
  *   - webapp.ini
  *   - webapp.json
  *   - ${AppName}.exe
  *   - ${AppName}.lnk
  *   / uninstall
  *     - webapp-uninstaller.exe
@@ -194,28 +190,19 @@ WinNativeApp.prototype = {
   _init: function() {
     let filenameRE = new RegExp("[<>:\"/\\\\|\\?\\*]", "gi");
 
     this.appNameAsFilename = this.appNameAsFilename.replace(filenameRE, "");
     if (this.appNameAsFilename == "") {
       this.appNameAsFilename = "webapp";
     }
 
-    // The ${InstallDir} format is as follows:
-    //  protocol
-    //  + ";" + host of the app origin
-    //  + ";" + port (only if port is not default)
+    // The ${InstallDir} is: sanitized app name + "-" + manifest url hash
     this.installDir = Services.dirsvc.get("AppData", Ci.nsIFile);
-    let installDirLeaf = this.launchURI.scheme
-                       + ";"
-                       + this.launchURI.host;
-    if (this.launchURI.port != -1) {
-      installDirLeaf += ";" + this.launchURI.port;
-    }
-    this.installDir.append(installDirLeaf);
+    this.installDir.append(this.uniqueName);
 
     this.webapprt = this.installDir.clone();
     this.webapprt.append(this.appNameAsFilename + ".exe");
 
     this.configJson = this.installDir.clone();
     this.configJson.append("webapp.json");
 
     this.webappINI = this.installDir.clone();
@@ -228,18 +215,17 @@ WinNativeApp.prototype = {
     this.uninstallerFile.append("webapp-uninstaller.exe");
 
     this.iconFile = this.installDir.clone();
     this.iconFile.append("chrome");
     this.iconFile.append("icons");
     this.iconFile.append("default");
     this.iconFile.append("default.ico");
 
-    this.uninstallSubkeyStr = this.launchURI.scheme + "://" +
-                              this.launchURI.hostPort;
+    this.uninstallSubkeyStr = this.uniqueName;
   },
 
   /**
    * Remove the current installation
    */
   _removeInstallation : function(keepProfile) {
     let uninstallKey;
     try {
@@ -472,24 +458,19 @@ MacNativeApp.prototype = {
     this.appSupportDir.append("Application Support");
 
     let filenameRE = new RegExp("[<>:\"/\\\\|\\?\\*]", "gi");
     this.appNameAsFilename = this.appNameAsFilename.replace(filenameRE, "");
     if (this.appNameAsFilename == "") {
       this.appNameAsFilename = "Webapp";
     }
 
-    // The ${ProfileDir} format is as follows:
-    //  host of the app origin + ";" +
-    //  protocol + ";" +
-    //  port (-1 for default port)
+    // The ${ProfileDir} is: sanitized app name + "-" + manifest url hash
     this.appProfileDir = this.appSupportDir.clone();
-    this.appProfileDir.append(this.launchURI.host + ";" +
-                              this.launchURI.scheme + ";" +
-                              this.launchURI.port);
+    this.appProfileDir.append(this.uniqueName);
 
     this.installDir = Services.dirsvc.get("TmpD", Ci.nsILocalFile);
     this.installDir.append(this.appNameAsFilename + ".app");
     this.installDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0755);
 
     this.contentsDir = this.installDir.clone();
     this.contentsDir.append("Contents");
 
@@ -580,17 +561,17 @@ MacNativeApp.prototype = {
     <string>English</string>\n\
     <key>CFBundleDisplayName</key>\n\
     <string>' + escapeXML(this.appName) + '</string>\n\
     <key>CFBundleExecutable</key>\n\
     <string>webapprt</string>\n\
     <key>CFBundleIconFile</key>\n\
     <string>appicon</string>\n\
     <key>CFBundleIdentifier</key>\n\
-    <string>' + escapeXML(this.launchURI.prePath) + '</string>\n\
+    <string>' + escapeXML(this.uniqueName) + '</string>\n\
     <key>CFBundleInfoDictionaryVersion</key>\n\
     <string>6.0</string>\n\
     <key>CFBundleName</key>\n\
     <string>' + escapeXML(this.appName) + '</string>\n\
     <key>CFBundlePackageType</key>\n\
     <string>APPL</string>\n\
     <key>CFBundleVersion</key>\n\
     <string>0</string>\n\
@@ -665,24 +646,18 @@ MacNativeApp.prototype = {
 
 function LinuxNativeApp(aData) {
   NativeApp.call(this, aData);
   this._init();
 }
 
 LinuxNativeApp.prototype = {
   _init: function() {
-    // The ${InstallDir} and desktop entry filename format is as follows:
-    // host of the app origin + ";" +
-    // protocol
-    // + ";" + port (only if port is not default)
-
-    this.uniqueName = this.launchURI.scheme + ";" + this.launchURI.host;
-    if (this.launchURI.port != -1)
-      this.uniqueName += ";" + this.launchURI.port;
+    // The ${InstallDir} and desktop entry filename are: sanitized app name +
+    // "-" + manifest url hash
 
     this.installDir = Services.dirsvc.get("Home", Ci.nsIFile);
     this.installDir.append("." + this.uniqueName);
 
     this.iconFile = this.installDir.clone();
     this.iconFile.append("icon.png");
 
     this.webapprt = this.installDir.clone();