Backed out changesets 357e720b5a37 and f1971c2f5232 (bug 777402) for mochitest-other orange.
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 01 Aug 2013 21:51:12 -0400
changeset 141089 f57e629bb4c3c84c8cef412989b6c577b52c6dae
parent 141088 b82c0f866f6487fc36e97e2015065818d47d4742
child 141090 3a9f93aa72c9386743b7cf2cb0e72572f5a27f37
push id2013
push usergijskruitbosch@gmail.com
push dateFri, 02 Aug 2013 13:48:28 +0000
treeherderfx-team@bcbc5a0ed405 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs777402
milestone25.0a1
backs out357e720b5a377ecf0e6fa6da9a82059dc5f98581
f1971c2f523249e5a369724fc870160225921a6f
Backed out changesets 357e720b5a37 and f1971c2f5232 (bug 777402) for mochitest-other orange. CLOSED TREE
browser/modules/webappsUI.jsm
dom/apps/src/AppsUtils.jsm
dom/apps/src/Webapps.js
dom/apps/src/Webapps.jsm
dom/apps/src/moz.build
dom/interfaces/apps/nsIDOMApplicationRegistry.idl
dom/interfaces/apps/nsIDOMApplicationRegistry2.idl
dom/tests/mochitest/webapps/test_install_errors.xul
dom/tests/mochitest/webapps/test_list_api.xul
mobile/android/chrome/content/browser.js
toolkit/webapps/WebappOSUtils.jsm
toolkit/webapps/WebappsInstaller.jsm
webapprt/CommandLineHandler.js
webapprt/WebappRT.jsm
webapprt/WebappsHandler.jsm
webapprt/test/chrome/head.js
--- a/browser/modules/webappsUI.jsm
+++ b/browser/modules/webappsUI.jsm
@@ -101,40 +101,33 @@ this.webappsUI = {
 
   doInstall: function(aData, aBrowser, aWindow) {
     let bundle = aWindow.gNavigatorBundle;
 
     let mainAction = {
       label: bundle.getString("webapps.install"),
       accessKey: bundle.getString("webapps.install.accesskey"),
       callback: function() {
-        let app = WebappsInstaller.init(aData);
-
+        let app = WebappsInstaller.install(aData);
         if (app) {
           let localDir = null;
           if (app.appProfile) {
             localDir = app.appProfile.localDir;
           }
 
-          DOMApplicationRegistry.confirmInstall(aData, false, localDir, null,
-            function (aManifest) {
-              if (WebappsInstaller.install(aData, aManifest)) {
-                installationSuccessNotification(aData, app, aWindow);
-              }
-            }
-          );
+          DOMApplicationRegistry.confirmInstall(aData, false, localDir);
+          installationSuccessNotification(aData, app, aWindow);
         } else {
           DOMApplicationRegistry.denyInstall(aData);
         }
       }
     };
 
     let requestingURI = aWindow.makeURI(aData.from);
-    let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest;
-    let manifest = new ManifestHelper(jsonManifest, aData.app.origin);
+    let manifest = new ManifestHelper(aData.app.manifest, aData.app.origin);
 
     let host;
     try {
       host = requestingURI.host;
     } catch(e) {
       host = requestingURI.spec;
     }
 
--- a/dom/apps/src/AppsUtils.jsm
+++ b/dom/apps/src/AppsUtils.jsm
@@ -477,40 +477,16 @@ 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.js
+++ b/dom/apps/src/Webapps.js
@@ -113,57 +113,51 @@ WebappsRegistry.prototype = {
         Services.DOMRequest.fireError(aRequest, "BACKGROUND_APP");
       }
     }
     Services.tm.currentThread.dispatch(runnable,
                                        Ci.nsIThread.DISPATCH_NORMAL);
     return false;
   },
 
-  _prepareInstall: function(aURL, aRequest, aParams, isPackage) {
+  // mozIDOMApplicationRegistry implementation
+
+  install: function(aURL, aParams) {
+    let uri = this._validateURL(aURL);
+
+    let request = this.createRequest();
+
+    if (!this._ensureForeground(request)) {
+      return request;
+    }
+
     let installURL = this._window.location.href;
-    let requestID = this.getRequestId(aRequest);
+    let requestID = this.getRequestId(request);
     let receipts = (aParams && aParams.receipts &&
                     Array.isArray(aParams.receipts)) ? aParams.receipts
                                                      : [];
     let categories = (aParams && aParams.categories &&
                       Array.isArray(aParams.categories)) ? aParams.categories
                                                          : [];
 
     let principal = this._window.document.nodePrincipal;
-
-    return { app: {
-                    installOrigin: this._getOrigin(installURL),
-                    origin: this._getOrigin(aURL),
-                    manifestURL: aURL,
-                    receipts: receipts,
-                    categories: categories
-                  },
-
-             from: installURL,
-             oid: this._id,
-             requestID: requestID,
-             appId: principal.appId,
-             isBrowser: principal.isInBrowserElement,
-             isPackage: isPackage
-           };
-  },
-
-  // mozIDOMApplicationRegistry implementation
-
-  install: function(aURL, aParams) {
-    let uri = this._validateURL(aURL);
-
-    let request = this.createRequest();
-
-    if (this._ensureForeground(request)) {
-      cpmm.sendAsyncMessage("Webapps:Install",
-                            this._prepareInstall(uri, request, aParams, false));
-    }
-
+    cpmm.sendAsyncMessage("Webapps:Install",
+                          { app: {
+                              installOrigin: this._getOrigin(installURL),
+                              origin: this._getOrigin(uri),
+                              manifestURL: uri,
+                              receipts: receipts,
+                              categories: categories
+                            },
+                            from: installURL,
+                            oid: this._id,
+                            requestID: requestID,
+                            appId: principal.appId,
+                            isBrowser: principal.isInBrowserElement
+                          });
     return request;
   },
 
   getSelf: function() {
     let request = this.createRequest();
     cpmm.sendAsyncMessage("Webapps:GetSelf", { origin: this._getOrigin(this._window.location.href),
                                                appId: this._window.document.nodePrincipal.appId,
                                                oid: this._id,
@@ -202,26 +196,52 @@ WebappsRegistry.prototype = {
   },
 
   uninit: function() {
     this._mgmt = null;
     cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
                           ["Webapps:Install:Return:OK"]);
   },
 
+  // mozIDOMApplicationRegistry2 implementation
+
   installPackage: function(aURL, aParams) {
     let uri = this._validateURL(aURL);
 
     let request = this.createRequest();
 
-    if (this._ensureForeground(request)) {
-      cpmm.sendAsyncMessage("Webapps:InstallPackage",
-                            this._prepareInstall(uri, request, aParams, true));
+    if (!this._ensureForeground(request)) {
+      return request;
     }
 
+    let installURL = this._window.location.href;
+    let requestID = this.getRequestId(request);
+    let receipts = (aParams && aParams.receipts &&
+                    Array.isArray(aParams.receipts)) ? aParams.receipts
+                                                     : [];
+    let categories = (aParams && aParams.categories &&
+                      Array.isArray(aParams.categories)) ? aParams.categories
+                                                         : [];
+
+    let principal = this._window.document.nodePrincipal;
+    cpmm.sendAsyncMessage("Webapps:InstallPackage",
+                          { app: {
+                              installOrigin: this._getOrigin(installURL),
+                              origin: this._getOrigin(uri),
+                              manifestURL: uri,
+                              receipts: receipts,
+                              categories: categories
+                            },
+                            from: installURL,
+                            oid: this._id,
+                            requestID: requestID,
+                            isPackage: true,
+                            appId: principal.appId,
+                            isBrowser: principal.isInBrowserElement
+                          });
     return request;
   },
 
   // nsIDOMGlobalPropertyInitializer implementation
   init: function(aWindow) {
     this.initDOMRequestHelper(aWindow, ["Webapps:Install:Return:OK", "Webapps:Install:Return:KO",
                               "Webapps:GetInstalled:Return:OK",
                               "Webapps:GetSelf:Return:OK",
@@ -241,23 +261,32 @@ WebappsRegistry.prototype = {
     // the mgmt object.
     this.hasMgmtPrivilege = perm == Ci.nsIPermissionManager.ALLOW_ACTION;
   },
 
   classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
                                          Ci.mozIDOMApplicationRegistry,
+#ifdef MOZ_B2G
                                          Ci.mozIDOMApplicationRegistry2,
+#elifdef MOZ_WIDGET_ANDROID
+                                         Ci.mozIDOMApplicationRegistry2,
+#endif
                                          Ci.nsIDOMGlobalPropertyInitializer]),
 
   classInfo: XPCOMUtils.generateCI({classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"),
                                     contractID: "@mozilla.org/webapps;1",
                                     interfaces: [Ci.mozIDOMApplicationRegistry,
-                                                 Ci.mozIDOMApplicationRegistry2],
+#ifdef MOZ_B2G
+                                                 Ci.mozIDOMApplicationRegistry2,
+#elifdef MOZ_WIDGET_ANDROID
+                                                 Ci.mozIDOMApplicationRegistry2,
+#endif
+                                                 ],
                                     flags: Ci.nsIClassInfo.DOM_OBJECT,
                                     classDescription: "Webapps Registry"})
 }
 
 /**
   * mozIDOMApplication object
   */
 
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -15,17 +15,16 @@ 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
 
@@ -1368,17 +1367,36 @@ this.DOMApplicationRegistry = {
         debug("Error opening " + aFile.path);
         aCallback(null);
       }
     );
   },
 
   // Returns the MD5 hash of the manifest.
   computeManifestHash: function(aManifest) {
-    return AppsUtils.computeHash(JSON.stringify(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("");
   },
 
   // 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) {
@@ -1753,16 +1771,23 @@ 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"]
@@ -1782,28 +1807,16 @@ 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");
@@ -1836,16 +1849,25 @@ this.DOMApplicationRegistry = {
 
     let sendError = function sendError(aError) {
       aData.error = aError;
       aMm.sendAsyncMessage("Webapps:Install:Return:KO", aData);
       Cu.reportError("Error installing packaged app from: " +
                      app.installOrigin + ": " + aError);
     }.bind(this);
 
+    // Disallow reinstalls from the same manifest URL for now.
+    // See https://bugzilla.mozilla.org/show_bug.cgi?id=821288
+    if (this.getAppLocalIdByManifestURL(app.manifestURL) !==
+        Ci.nsIScriptSecurityManager.NO_APP_ID ||
+        this._appId(app.origin) !== null) {
+      sendError("REINSTALL_FORBIDDEN");
+      return;
+    }
+
     let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
                 .createInstance(Ci.nsIXMLHttpRequest);
     xhr.open("GET", app.manifestURL, true);
     xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
     xhr.channel.notificationCallbacks = this.createLoadContext(aData.appId,
                                                                aData.isBrowser);
     xhr.responseType = "json";
 
@@ -1858,23 +1880,16 @@ this.DOMApplicationRegistry = {
         }
 
         let manifest = app.updateManifest = xhr.response;
         if (!manifest) {
           sendError("MANIFEST_PARSE_ERROR");
           return;
         }
 
-        // Disallow reinstalls from the same manifest URL for now.
-        if (this._appIdForManifestURL(app.manifestURL) !== null &&
-            this._isLaunchable(app)) {
-          sendError("REINSTALL_FORBIDDEN");
-          return;
-        }
-
         if (!(AppsUtils.checkManifest(manifest, app) &&
               manifest.package_path)) {
           sendError("INVALID_MANIFEST");
         } else if (!AppsUtils.checkInstallAllowed(manifest, app.installOrigin)) {
           sendError("INSTALL_FROM_DENIED");
         } else {
           app.etag = xhr.getResponseHeader("Etag");
           app.manifestHash = this.computeManifestHash(manifest);
@@ -1981,17 +1996,17 @@ this.DOMApplicationRegistry = {
     appObject.installTime = app.installTime = Date.now();
     appObject.lastUpdateCheck = app.lastUpdateCheck = Date.now();
     let appNote = JSON.stringify(appObject);
     appNote.id = id;
 
     appObject.id = id;
     appObject.localId = localId;
     appObject.basePath = FileUtils.getDir(DIRECTORY_NAME, ["webapps"], true, true).path;
-    let dir = this._getAppDir(id);
+    let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
     let manFile = dir.clone();
     manFile.append(manifestName);
     let jsonManifest = aData.isPackage ? app.updateManifest : app.manifest;
     this._writeFile(manFile, JSON.stringify(jsonManifest), function() { });
 
     let manifest = new ManifestHelper(jsonManifest, app.origin);
 
     if (manifest.appcache_path) {
@@ -2055,29 +2070,29 @@ this.DOMApplicationRegistry = {
         this.broadcastMessage("Webapps:Install:Return:OK", aData);
         Services.obs.notifyObservers(this, "webapps-sync-install", appNote);
       }).bind(this));
     }
 
     if (!aData.isPackage) {
       this.updateAppHandlers(null, app.manifest, app);
       if (aInstallSuccessCallback) {
-        aInstallSuccessCallback(app.manifest);
+        aInstallSuccessCallback(manifest);
       }
     }
 
     if (manifest.package_path) {
       // origin for install apps is meaningless here, since it's app:// and this
       // can't be used to resolve package paths.
       manifest = new ManifestHelper(jsonManifest, app.manifestURL);
       this.downloadPackage(manifest, appObject, false, (function(aId, aManifest) {
         // Success! Move the zip out of TmpD.
         let app = DOMApplicationRegistry.webapps[aId];
         let zipFile = FileUtils.getFile("TmpD", ["webapps", aId, "application.zip"], true);
-        let dir = this._getAppDir(id);
+        let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", aId], true, true);
         zipFile.moveTo(dir, "application.zip");
         let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], true, true);
         try {
           tmpDir.remove(true);
         } catch(e) { }
 
         // Save the manifest
         let manFile = dir.clone();
@@ -2807,17 +2822,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])) {
+          this._isLaunchable(this.webapps[id].origin)) {
         let app = AppsUtils.cloneAppObject(this.webapps[id]);
         aData.apps.push(app);
         tmp.push({ id: id });
         break;
       }
     }
 
     if (!aData.apps.length) {
@@ -2854,17 +2869,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])) {
+          this._isLaunchable(this.webapps[id].origin)) {
         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;
@@ -2872,17 +2887,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])) {
+      if (!this._isLaunchable(this.webapps[id].origin)) {
         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;
@@ -2899,17 +2914,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))
+      if (!this._isLaunchable(app.origin))
         continue;
 
       apps.push(app);
       tmp.push({ id: id });
     }
 
     this._readManifests(tmp, (function(aResult) {
       for (let i = 0; i < aResult.length; i++)
@@ -3052,21 +3067,68 @@ this.DOMApplicationRegistry = {
     }
 
     // Clear the manifest cache.
     this._manifestCache = { };
 
     this._saveApps(aCallback);
   },
 
-  _isLaunchable: function(aApp) {
+  _isLaunchable: function(aOrigin) {
     if (this.allAppsLaunchable)
       return true;
 
-    return WebappOSUtils.isLaunchable(aApp);
+#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
+
   },
 
   _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/dom/apps/src/moz.build
+++ b/dom/apps/src/moz.build
@@ -2,18 +2,21 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXTRA_COMPONENTS += [
     'AppsService.js',
     'AppsService.manifest',
+    'Webapps.manifest',
+]
+
+EXTRA_PP_COMPONENTS += [
     'Webapps.js',
-    'Webapps.manifest',
 ]
 
 EXTRA_JS_MODULES += [
     'AppDownloadManager.jsm',
     'AppsServiceChild.jsm',
     'FreeSpaceWatcher.jsm',
     'OfflineCacheInstaller.jsm',
     'PermissionsInstaller.jsm',
--- a/dom/interfaces/apps/nsIDOMApplicationRegistry.idl
+++ b/dom/interfaces/apps/nsIDOMApplicationRegistry.idl
@@ -133,17 +133,17 @@ interface mozIDOMApplicationMgmt : nsISu
    *
    * @param app : the app object of the web app to be uninstalled.
    * @returns   : A DOMRequest object, returning the app's origin in |result|
    *              if uninstall succeeds; returning "NOT_INSTALLED" error otherwise.
    */
   nsIDOMDOMRequest uninstall(in mozIDOMApplication app);
 };
 
-[scriptable, uuid(52710c5f-b2a2-4b27-b5b9-f679a1bcc79b)]
+[scriptable, uuid(abfc6c15-8b92-4b9a-b892-52e6ae76f379)]
 interface mozIDOMApplicationRegistry : nsISupports
 {
   /**
    * Install a web app.
    *
    * @param manifestUrl : the URL of the webapps manifest.
    * @param parameters  : A structure with optional information.
    *                      {
@@ -164,23 +164,10 @@ interface mozIDOMApplicationRegistry : n
    */
   nsIDOMDOMRequest checkInstalled(in DOMString manifestUrl);
 
   /**
    * the request will return the applications installed from this origin, or null.
    */
   nsIDOMDOMRequest getInstalled();
 
-  /**
-   * Install a packaged web app.
-   *
-   * @param packageUrl : the URL of the webapps manifest.
-   * @param parameters : A structure with optional information.
-   *                      {
-   *                       receipts: ...    Will be used to specify the payment receipts for this installation.
-   *                       categories: ...  Will be used to specify the categories of the webapp.
-   *                      }
-   * @returns          : A DOMRequest object, returning the app object in |result| if install succeeds.
-   */
-  nsIDOMDOMRequest installPackage(in DOMString packageUrl, [optional] in jsval parameters);
-
   readonly attribute mozIDOMApplicationMgmt mgmt;
 };
--- a/dom/interfaces/apps/nsIDOMApplicationRegistry2.idl
+++ b/dom/interfaces/apps/nsIDOMApplicationRegistry2.idl
@@ -1,13 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIDOMApplicationRegistry.idl"
 
 interface nsIDOMDOMRequest;
 
-// This interface is still here for backwards compatibility.
-[scriptable, uuid(5bd838b2-cf3d-406e-bbef-f633cf9e68de)]
+[scriptable, uuid(34498a66-3aee-4b80-8b8b-a9c5d5ba32b6)]
 interface mozIDOMApplicationRegistry2 : mozIDOMApplicationRegistry
 {
+  /**
+   * Install a packaged web app.
+   *
+   * @param packageUrl : the URL of the webapps manifest.
+   * @param parameters : A structure with optional information.
+   *                      {
+   *                       receipts: ...    Will be used to specify the payment receipts for this installation.
+   *                       categories: ...  Will be used to specify the categories of the webapp.
+   *                      }
+   * @returns          : A DOMRequest object, returning the app object in |result| if install succeeds.
+   */
+  nsIDOMDOMRequest installPackage(in DOMString packageUrl, [optional] in jsval parameters);
 };
--- a/dom/tests/mochitest/webapps/test_install_errors.xul
+++ b/dom/tests/mochitest/webapps/test_install_errors.xul
@@ -19,16 +19,17 @@
 <script>
 
 var steps = [
   noArgs,
   parseError,
   invalidManifest,
   permissionDenied,
   invalidContent,
+  installPackageNotImplemented,
   invalidLaunchPath,
   invalidLaunchPath2,
   invalidEntryPoint,
   invalidLocaleEntryPoint,
   invalidActivityHref,
   invalidActivityHref2,
   invalidMessage,
   fileURL,
@@ -161,16 +162,22 @@ function invalidMessage(next) {
   var url = "http://test/chrome/dom/tests/mochitest/webapps/apps/invalid_message.webapp";
 
   navigator.mozApps.install(url, null).onerror = function onInstallError() {
     is(this.error.name, "INVALID_MANIFEST", "Manifest has absolute message href");
     next();
   };
 }
 
+function installPackageNotImplemented(next) {
+  ok(!("installPackage" in navigator.mozApps),
+     "installPackage not in navigator.mozApps");
+  next();
+}
+
 function fileURL(next) {
   try {
     navigator.mozApps.install("file:///nonexistent");
     ok(false,
        "attempt to install nonexistent file: URL doesn't throw exception");
   } catch(ex) {
     is(ex.message, "INVALID_URL_SCHEME: 'file'; must be 'http' or 'https'",
        "attempt to install nonexistent file: URL throws exception");
--- a/dom/tests/mochitest/webapps/test_list_api.xul
+++ b/dom/tests/mochitest/webapps/test_list_api.xul
@@ -19,17 +19,16 @@
 <script>
 
 var props = {
   QueryInterface: "function",
   checkInstalled: "function",
   getInstalled: "function",
   getSelf: "function",
   install: "function",
-  installPackage: "function",
   mgmt: "object",
 };
 
 isDeeply([p for (p in navigator.mozApps)].sort(), Object.keys(props).sort(),
          "navigator.mozApps has only the expected properties");
 
 for (var p in props) {
   is(typeof navigator.mozApps[p], props[p], "typeof " + p);
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -6717,36 +6717,35 @@ var WebappsUI = {
     }
 
     return iconURI ? iconURI.spec : DEFAULT_ICON;
   },
 
   doInstall: function doInstall(aData) {
     let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest;
     let manifest = new ManifestHelper(jsonManifest, aData.app.origin);
+    let name = manifest.name ? manifest.name : manifest.fullLaunchPath();
     let showPrompt = true;
 
-    if (!showPrompt || Services.prompt.confirm(null, Strings.browser.GetStringFromName("webapps.installTitle"), manifest.name + "\n" + aData.app.origin)) {
+    if (!showPrompt || Services.prompt.confirm(null, Strings.browser.GetStringFromName("webapps.installTitle"), name + "\n" + aData.app.origin)) {
       // Get a profile for the app to be installed in. We'll download everything before creating the icons.
       let origin = aData.app.origin;
       let profilePath = sendMessageToJava({
         type: "WebApps:PreInstall",
         name: manifest.name,
         manifestURL: aData.app.manifestURL,
         origin: origin
       });
       if (profilePath) {
         let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
         file.initWithPath(profilePath);
-
+  
         let self = this;
         DOMApplicationRegistry.confirmInstall(aData, false, file, null,
-          function (aManifest) {
-            let localeManifest = new ManifestHelper(aManifest, aData.app.origin);
-
+          function (manifest) {
             // the manifest argument is the manifest from within the zip file,
             // TODO so now would be a good time to ask about permissions.
             self.makeBase64Icon(self.getBiggestIcon(manifest.icons, Services.io.newURI(aData.app.origin, null, null)),
               function(scaledIcon, fullsizeIcon) {
                 // if java returned a profile path to us, try to use it to pre-populate the app cache
                 // also save the icon so that it can be used in the splash screen
                 try {
                   let iconFile = file.clone();
@@ -6756,36 +6755,36 @@ var WebappsUI = {
                   persist.persistFlags |= Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
 
                   let source = Services.io.newURI(fullsizeIcon, "UTF8", null);
                   persist.saveURI(source, null, null, null, null, iconFile, null);
 
                   // aData.app.origin may now point to the app: url that hosts this app
                   sendMessageToJava({
                     type: "WebApps:PostInstall",
-                    name: localeManifest.name,
+                    name: manifest.name,
                     manifestURL: aData.app.manifestURL,
                     originalOrigin: origin,
                     origin: aData.app.origin,
                     iconURL: fullsizeIcon
                   });
                   if (!!aData.isPackage) {
                     // For packaged apps, put a notification in the notification bar.
                     let message = Strings.browser.GetStringFromName("webapps.alertSuccess");
                     let alerts = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
-                    alerts.showAlertNotification("drawable://alert_app", localeManifest.name, message, true, "", {
+                    alerts.showAlertNotification("drawable://alert_app", manifest.name, message, true, "", {
                       observe: function () {
                         self.openURL(aData.app.manifestURL, aData.app.origin);
                       }
                     }, "webapp");
                   }
                 } catch(ex) {
                   console.log(ex);
                 }
-                self.writeDefaultPrefs(file, localeManifest);
+                self.writeDefaultPrefs(file, manifest);
               }
             );
           }
         );
       }
     } else {
       DOMApplicationRegistry.denyInstall(aData);
     }
--- a/toolkit/webapps/WebappOSUtils.jsm
+++ b/toolkit/webapps/WebappOSUtils.jsm
@@ -3,265 +3,105 @@
  * 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 = {
-  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.
-    // They're guaranteed to be always identical to the application
-    // name in the user locale.
-    if (aApp.name) {
-      name = aApp.name;
-    } else {
-      let manifest =
-        new ManifestHelper(aApp.updateManifest || aApp.manifest, aApp.origin);
-      name = 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);
-
+  launch: function(aData) {
 #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\\" +
-                       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) {}
+                       aData.origin, Ci.nsIWindowsRegKey.ACCESS_READ);
 
-    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");
+      let launchTarget = initWithPath(appRegKey.readStringValue("InstallLocation"));
+      launchTarget.append(appRegKey.readStringValue("AppFilename") + ".exe");
 
-      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;
+    try {
+      appPath = mwaUtils.pathForAppWithIdentifier(aData.origin);
+    } catch (e) {}
+
+    if (appPath) {
+      mwaUtils.launchAppWithIdentifier(aData.origin);
+      return true;
     }
 
-    let mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"]
-                     .createInstance(Ci.nsIMacWebAppUtils);
+    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 {
-      mwaUtils.launchAppWithIdentifier(launchIdentifier);
-    } catch(e) {
-      return false;
-    }
-
-    return true;
-#elifdef XP_UNIX
-    let exeFile = this.getLaunchTarget(aApp);
-    if (!exeFile) {
-      return false;
-    }
+    let exeFile = Services.dirsvc.get("Home", Ci.nsIFile);
+    exeFile.append(installDir);
+    exeFile.append("webapprt-stub");
 
     try {
-      let process = Cc["@mozilla.org/process/util;1"]
-                      .createInstance(Ci.nsIProcess);
-
-      process.init(exeFile);
-      process.runAsync([], 0);
-    } catch (e) {
-      return false;
-    }
-
-    return true;
-#endif
-  },
-
-  uninstall: function(aApp) {
-    let uniqueName = this.getUniqueName(aApp);
+      if (exeFile.exists()) {
+        let process = Cc["@mozilla.org/process/util;1"]
+                        .createInstance(Ci.nsIProcess);
+        process.init(exeFile);
+        process.runAsync([], 0);
+        return true;
+      }
+    } catch (e) {}
 
-#ifdef XP_UNIX
-#ifndef XP_MACOSX
-    let exeFile = this.getLaunchTarget(aApp);
-    if (!exeFile) {
-      return false;
-    }
-
-    try {
-      let process = Cc["@mozilla.org/process/util;1"]
-                      .createInstance(Ci.nsIProcess);
-
-      process.init(exeFile);
-      process.runAsync(["-remove"], 1);
-    } catch (e) {
-      return false;
-    }
-
-    return true;
-#endif
+    return false;
 #endif
   },
 
-  /**
-   * 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;
-    }
+  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 : "");
 
-    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) {}
+    let exeFile = Services.dirsvc.get("Home", Ci.nsIFile);
+    exeFile.append(installDir);
+    exeFile.append("webapprt-stub");
 
-    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"]);
+    try {
+      if (exeFile.exists()) {
+        var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+        process.init(exeFile);
+        process.runAsync(["-remove"], 1);
+        return true;
       }
+    } 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 desktopINI.exists();
-    }
-
-    return true;
+    return false;
 #endif
-  },
-
-  /**
-   * Sanitize the filename (accepts only a-z, 0-9, - and _)
-   */
-  sanitizeStringForFilename: function(aPossiblyBadFilenameString) {
-    return aPossiblyBadFilenameString.replace(/[^a-z0-9_\-]/gi, "");
+#endif
   }
 }
--- a/toolkit/webapps/WebappsInstaller.jsm
+++ b/toolkit/webapps/WebappsInstaller.jsm
@@ -8,195 +8,138 @@ 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 = {
-  shell: null,
-
-  /**
-   * Initializes the app object that takes care of the installation
-   * and creates the profile directory for an application
-   *
-   * @param aData the data provided to the install function
-   *
-   * @returns NativeApp on success, null on error
-   */
-  init: function(aData) {
-#ifdef XP_WIN
-    this.shell = new WinNativeApp(aData);
-#elifdef XP_MACOSX
-    this.shell = new MacNativeApp(aData);
-#elifdef XP_UNIX
-    this.shell = new LinuxNativeApp(aData);
-#else
-    return null;
-#endif
-
-    try {
-      if (Services.prefs.getBoolPref("browser.mozApps.installer.dry_run")) {
-        return this.shell;
-      }
-    } catch (ex) {}
-
-    try {
-      this.shell.createAppProfile();
-    } catch (ex) {
-      Cu.reportError("Error installing app: " + ex);
-      return null;
-    }
-
-    return this.shell;
-  },
-
   /**
    * Creates a native installation of the web app in the OS
    *
-   * @param aData the data provided to the install function
-   * @param aManifest the manifest data provided by the web app
+   * @param aData the manifest data provided by the web app
    *
-   * @returns true on success, false if an error was thrown
+   * @returns bool true on success, false if an error was thrown
    */
-  install: function(aData, aManifest) {
+  install: function(aData) {
+
     try {
       if (Services.prefs.getBoolPref("browser.mozApps.installer.dry_run")) {
         return true;
       }
     } catch (ex) {}
 
-    this.shell.init(aData, aManifest);
+#ifdef XP_WIN
+    let shell = new WinNativeApp(aData);
+#elifdef XP_MACOSX
+    let shell = new MacNativeApp(aData);
+#elifdef XP_UNIX
+    let shell = new LinuxNativeApp(aData);
+#else
+    return false;
+#endif
 
     try {
-      this.shell.install();
+      shell.install();
     } catch (ex) {
       Cu.reportError("Error installing app: " + ex);
-      return false;
+      return null;
     }
 
     let data = {
-      "installDir": this.shell.installDir.path,
-      "app": {
-        "manifest": aManifest,
-        "origin": aData.app.origin
-      }
+      "installDir": shell.installDir.path,
+      "app": aData.app
     };
     Services.obs.notifyObservers(null, "webapp-installed", JSON.stringify(data));
 
-    return true;
+    return shell;
   }
 }
 
 /**
  * This function implements the common constructor for
- * the Windows, Mac and Linux native app shells. It sets
- * the app unique name. It's meant to be called as
- * NativeApp.call(this, aData) from the platform-specific
- * constructor.
+ * the Windows, Mac and Linux native app shells. It reads and parses
+ * the data from the app manifest and stores it in the NativeApp
+ * object. It's meant to be called as NativeApp.call(this, aData)
+ * from the platform-specific constructor.
  *
- * @param aData the data object provided to the install function
+ * @param aData the data object provided by the web app with
+ *              all the app settings and specifications.
  *
  */
 function NativeApp(aData) {
-  this.uniqueName = WebappOSUtils.getUniqueName(aData.app);
+  let app = this.app = aData.app;
 
-  let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest;
-  let manifest = new ManifestHelper(jsonManifest, aData.app.origin);
-
-  this.appName = sanitize(manifest.name);
-  this.appNameAsFilename = stripStringForFilename(this.appName);
-}
+  let origin = Services.io.newURI(app.origin, null, null);
 
-NativeApp.prototype = {
-  uniqueName: null,
-  appName: null,
-  appNameAsFilename: null,
-  iconURI: null,
-  developerName: null,
-  shortDescription: null,
-  categories: null,
-  webappJson: null,
-  runtimeFolder: null,
+  if (app.manifest.launch_path) {
+    this.launchURI = Services.io.newURI(origin.resolve(app.manifest.launch_path),
+                                        null, null);
+  } else {
+    this.launchURI = origin.clone();
+  }
 
-  /**
-   * This function reads and parses the data from the app
-   * manifest and stores it in the NativeApp object.
-   *
-   * @param aData the data object provided to the install function
-   * @param aManifest the manifest data provided by the web app
-   *
-   */
-  init: function(aData, aManifest) {
-    let manifest = new ManifestHelper(aManifest, aData.app.origin);
+  let biggestIcon = getBiggestIconURL(app.manifest.icons);
+  try {
+    let iconURI = Services.io.newURI(biggestIcon, null, null);
+    if (iconURI.scheme == "data") {
+      this.iconURI = iconURI;
+    }
+  } catch (ex) {}
 
-    let origin = Services.io.newURI(aData.app.origin, null, null);
-
-    let biggestIcon = getBiggestIconURL(manifest.icons);
+  if (!this.iconURI) {
     try {
-      let iconURI = Services.io.newURI(biggestIcon, null, null);
-      if (iconURI.scheme == "data") {
-        this.iconURI = iconURI;
-      }
-    } catch (ex) {}
+      this.iconURI = Services.io.newURI(origin.resolve(biggestIcon), null, null);
+    }
+    catch (ex) {}
+  }
+
+  this.appName = sanitize(app.manifest.name);
+  this.appNameAsFilename = stripStringForFilename(this.appName);
 
-    if (!this.iconURI) {
-      try {
-        this.iconURI = Services.io.newURI(origin.resolve(biggestIcon), null, null);
-      }
-      catch (ex) {}
+  if(app.manifest.developer && app.manifest.developer.name) {
+    let devName = app.manifest.developer.name.substr(0, 128);
+    devName = sanitize(devName);
+    if (devName) {
+      this.developerName = devName;
     }
-
-    if (manifest.developer && manifest.developer.name) {
-      let devName = sanitize(manifest.developer.name.substr(0, 128));
-      if (devName) {
-        this.developerName = devName;
-      }
-    }
+  }
 
-    if (manifest.description) {
-      let firstLine = manifest.description.split("\n")[0];
-      let shortDesc = firstLine.length <= 256
-                      ? firstLine
-                      : firstLine.substr(0, 253) + "…";
-      this.shortDescription = sanitize(shortDesc);
-    } else {
-      this.shortDescription = this.appName;
-    }
-
-    this.categories = aData.app.categories.slice(0);
+  let shortDesc = this.appName;
+  if (app.manifest.description) {
+    let firstLine = app.manifest.description.split("\n")[0];
+    shortDesc = firstLine.length <= 256
+                ? firstLine
+                : firstLine.substr(0, 253) + "...";
+  }
+  this.shortDescription = sanitize(shortDesc);
 
-    // The app registry is the Firefox profile from which the app
-    // was installed.
-    let registryFolder = Services.dirsvc.get("ProfD", Ci.nsIFile);
+  // The app registry is the Firefox profile from which the app
+  // was installed.
+  this.registryFolder = Services.dirsvc.get("ProfD", Ci.nsIFile);
 
-    this.webappJson = {
-      "registryDir": registryFolder.path,
-      "app": {
-        "manifest": aManifest,
-        "origin": aData.app.origin
-      }
-    };
+  this.webappJson = {
+    "registryDir": this.registryFolder.path,
+    "app": app
+  };
 
-    this.runtimeFolder = Services.dirsvc.get("GreD", Ci.nsIFile);
-  }
-};
+  this.runtimeFolder = Services.dirsvc.get("GreD", Ci.nsIFile);
+}
 
 #ifdef XP_WIN
 /*************************************
  * Windows app installer
  *
  * The Windows installation process will generate the following files:
  *
- * ${FolderName} = sanitized app name + "-" + manifest url hash
+ * ${FolderName} = protocol;app-origin[;port]
+ *                 e.g.: subdomain.example.com;http;85
  *
  * %APPDATA%/${FolderName}
  *   - webapp.ini
  *   - webapp.json
  *   - ${AppName}.exe
  *   - ${AppName}.lnk
  *   / uninstall
  *     - webapp-uninstaller.exe
@@ -207,36 +150,40 @@ NativeApp.prototype = {
  *
  * After the app runs for the first time, a profiles/ folder will also be
  * created which will host the user profile for this app.
  */
 
 /**
  * Constructor for the Windows native app shell
  *
- * @param aData the data object provided to the install function
+ * @param aData the data object provided by the web app with
+ *              all the app settings and specifications.
  */
 function WinNativeApp(aData) {
   NativeApp.call(this, aData);
   this._init();
 }
 
 WinNativeApp.prototype = {
-  __proto__: NativeApp.prototype,
-
   /**
-   * Install the app in the system
+   * Install the app in the system by creating the folder structure,
    *
    */
   install: function() {
+    // Remove previously installed app (for update purposes)
+    this._removeInstallation(true);
+
     try {
+      this._createDirectoryStructure();
       this._copyPrebuiltFiles();
       this._createConfigFiles();
       this._createShortcutFiles();
       this._writeSystemKeys();
+      this._createAppProfile();
     } catch (ex) {
       this._removeInstallation(false);
       throw(ex);
     }
 
     getIconForApp(this, function() {});
   },
 
@@ -247,19 +194,28 @@ WinNativeApp.prototype = {
   _init: function() {
     let filenameRE = new RegExp("[<>:\"/\\\\|\\?\\*]", "gi");
 
     this.appNameAsFilename = this.appNameAsFilename.replace(filenameRE, "");
     if (this.appNameAsFilename == "") {
       this.appNameAsFilename = "webapp";
     }
 
-    // The ${InstallDir} is: sanitized app name + "-" + manifest url hash
+    // The ${InstallDir} format is as follows:
+    //  protocol
+    //  + ";" + host of the app origin
+    //  + ";" + port (only if port is not default)
     this.installDir = Services.dirsvc.get("AppData", Ci.nsIFile);
-    this.installDir.append(this.uniqueName);
+    let installDirLeaf = this.launchURI.scheme
+                       + ";"
+                       + this.launchURI.host;
+    if (this.launchURI.port != -1) {
+      installDirLeaf += ";" + this.launchURI.port;
+    }
+    this.installDir.append(installDirLeaf);
 
     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();
@@ -272,22 +228,18 @@ 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.uniqueName;
-
-    // Remove previously installed app (for update purposes)
-    this._removeInstallation(true);
-
-    this._createDirectoryStructure();
+    this.uninstallSubkeyStr = this.launchURI.scheme + "://" +
+                              this.launchURI.hostPort;
   },
 
   /**
    * Remove the current installation
    */
   _removeInstallation : function(keepProfile) {
     let uninstallKey;
     try {
@@ -326,27 +278,25 @@ WinNativeApp.prototype = {
 
     removeFiles(filesToRemove);
   },
 
   /**
    * Creates the main directory structure.
    */
   _createDirectoryStructure: function() {
-    if (!this.installDir.exists()) {
+    if (!this.installDir.exists())
       this.installDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
-    }
-
     this.uninstallDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
   },
 
   /**
    * Creates the profile to be used for this app.
    */
-  createAppProfile: function() {
+  _createAppProfile: function() {
     let profSvc = Cc["@mozilla.org/toolkit/profile-service;1"]
                     .getService(Ci.nsIToolkitProfileService);
 
     try {
       this.appProfile = profSvc.createDefaultProfileForApp(this.installDir.leafName,
                                                            null, null);
     } catch (ex if ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {}
   },
@@ -512,58 +462,59 @@ WinNativeApp.prototype = {
 #elifdef XP_MACOSX
 
 function MacNativeApp(aData) {
   NativeApp.call(this, aData);
   this._init();
 }
 
 MacNativeApp.prototype = {
-  __proto__: NativeApp.prototype,
-
   _init: function() {
     this.appSupportDir = Services.dirsvc.get("ULibDir", Ci.nsILocalFile);
     this.appSupportDir.append("Application Support");
 
     let filenameRE = new RegExp("[<>:\"/\\\\|\\?\\*]", "gi");
     this.appNameAsFilename = this.appNameAsFilename.replace(filenameRE, "");
     if (this.appNameAsFilename == "") {
       this.appNameAsFilename = "Webapp";
     }
 
-    // The ${ProfileDir} is: sanitized app name + "-" + manifest url hash
+    // The ${ProfileDir} format is as follows:
+    //  host of the app origin + ";" +
+    //  protocol + ";" +
+    //  port (-1 for default port)
     this.appProfileDir = this.appSupportDir.clone();
-    this.appProfileDir.append(this.uniqueName);
+    this.appProfileDir.append(this.launchURI.host + ";" +
+                              this.launchURI.scheme + ";" +
+                              this.launchURI.port);
 
     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");
 
     this.macOSDir = this.contentsDir.clone();
     this.macOSDir.append("MacOS");
 
     this.resourcesDir = this.contentsDir.clone();
     this.resourcesDir.append("Resources");
 
     this.iconFile = this.resourcesDir.clone();
     this.iconFile.append("appicon.icns");
-
-    // Remove previously installed app (for update purposes)
-    this._removeInstallation(true);
-
-    this._createDirectoryStructure();
   },
 
   install: function() {
+    this._removeInstallation(true);
     try {
+      this._createDirectoryStructure();
       this._copyPrebuiltFiles();
       this._createConfigFiles();
+      this._createAppProfile();
     } catch (ex) {
       this._removeInstallation(false);
       throw(ex);
     }
 
     getIconForApp(this, this._moveToApplicationsFolder);
   },
 
@@ -573,26 +524,25 @@ MacNativeApp.prototype = {
     if (!keepProfile) {
       filesToRemove.push(this.appProfileDir);
     }
 
     removeFiles(filesToRemove);
   },
 
   _createDirectoryStructure: function() {
-    if (!this.appProfileDir.exists()) {
+    if (!this.appProfileDir.exists())
       this.appProfileDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
-    }
 
     this.contentsDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
     this.macOSDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
     this.resourcesDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
   },
 
-  createAppProfile: function() {
+  _createAppProfile: function() {
     let profSvc = Cc["@mozilla.org/toolkit/profile-service;1"]
                     .getService(Ci.nsIToolkitProfileService);
 
     try {
       this.appProfile = profSvc.createDefaultProfileForApp(this.appProfileDir.leafName,
                                                            null, null);
     } catch (ex if ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {}
   },
@@ -630,17 +580,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.uniqueName) + '</string>\n\
+    <string>' + escapeXML(this.launchURI.prePath) + '</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\
@@ -714,21 +664,25 @@ MacNativeApp.prototype = {
 #elifdef XP_UNIX
 
 function LinuxNativeApp(aData) {
   NativeApp.call(this, aData);
   this._init();
 }
 
 LinuxNativeApp.prototype = {
-  __proto__: NativeApp.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)
 
-  _init: function() {
-    // The ${InstallDir} and desktop entry filename are: sanitized app name +
-    // "-" + manifest url hash
+    this.uniqueName = this.launchURI.scheme + ";" + this.launchURI.host;
+    if (this.launchURI.port != -1)
+      this.uniqueName += ";" + this.launchURI.port;
 
     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();
@@ -751,27 +705,26 @@ LinuxNativeApp.prototype = {
     else {
       this.desktopINI = Services.dirsvc.get("Home", Ci.nsIFile);
       this.desktopINI.append(".local");
       this.desktopINI.append("share");
     }
 
     this.desktopINI.append("applications");
     this.desktopINI.append("owa-" + this.uniqueName + ".desktop");
-
-    // Remove previously installed app (for update purposes)
-    this._removeInstallation(true);
-
-    this._createDirectoryStructure();
   },
 
   install: function() {
+    this._removeInstallation(true);
+
     try {
+      this._createDirectoryStructure();
       this._copyPrebuiltFiles();
       this._createConfigFiles();
+      this._createAppProfile();
     } catch (ex) {
       this._removeInstallation(false);
       throw(ex);
     }
 
     getIconForApp(this, function() {});
   },
 
@@ -796,17 +749,17 @@ LinuxNativeApp.prototype = {
   },
 
   _copyPrebuiltFiles: function() {
     let webapprtPre = this.runtimeFolder.clone();
     webapprtPre.append(this.webapprt.leafName);
     webapprtPre.copyTo(this.installDir, this.webapprt.leafName);
   },
 
-  createAppProfile: function() {
+  _createAppProfile: function() {
     let profSvc = Cc["@mozilla.org/toolkit/profile-service;1"]
                     .getService(Ci.nsIToolkitProfileService);
 
     try {
       this.appProfile = profSvc.createDefaultProfileForApp(this.installDir.leafName,
                                                            null, null);
     } catch (ex if ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {}
   },
@@ -837,17 +790,17 @@ LinuxNativeApp.prototype = {
       "travel": "Amusement",
       "reference": "Science;Education;Documentation",
       "maps-navigation": "Maps",
       "utilities": "Utility"
     };
 
     // The trailing semicolon is needed as written in the freedesktop specification
     let categories = "";
-    for (let category of this.categories) {
+    for (let category of this.app.categories) {
       let catLower = category.toLowerCase();
       if (catLower in translations) {
         categories += translations[catLower] + ";";
       }
     }
 
     return categories;
   },
--- a/webapprt/CommandLineHandler.js
+++ b/webapprt/CommandLineHandler.js
@@ -25,17 +25,17 @@ CommandLineHandler.prototype = {
     if (inTestMode) {
       // Open the mochitest shim window, which configures the runtime for tests.
       Services.ww.openWindow(null,
                              "chrome://webapprt/content/mochitest.xul",
                              "_blank",
                              "chrome,dialog=no",
                              args);
     } else {
-      args.setProperty("url", WebappRT.launchURI);
+      args.setProperty("url", WebappRT.launchURI.spec);
       Services.ww.openWindow(null,
                              "chrome://webapprt/content/webapp.xul",
                              "_blank",
                              "chrome,dialog=no,resizable,scrollbars,centerscreen",
                              args);
     }
   },
 
--- a/webapprt/WebappRT.jsm
+++ b/webapprt/WebappRT.jsm
@@ -5,23 +5,27 @@
 this.EXPORTED_SYMBOLS = ["WebappRT"];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/AppsUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "FileUtils", function() {
   Cu.import("resource://gre/modules/FileUtils.jsm");
   return FileUtils;
 });
 
+XPCOMUtils.defineLazyGetter(this, "DOMApplicationRegistry", function() {
+  Cu.import("resource://gre/modules/Webapps.jsm");
+  return DOMApplicationRegistry;
+});
+
 this.WebappRT = {
   _config: null,
 
   get config() {
     if (this._config)
       return this._config;
 
     let config;
@@ -42,13 +46,15 @@ this.WebappRT = {
   // will have a reference to its global object, so our reference to it
   // will leak that object (per bug 780674).  The setter enables us to clone
   // the new value so we don't actually retain a reference to it.
   set config(newVal) {
     this._config = JSON.parse(JSON.stringify(newVal));
   },
 
   get launchURI() {
-    let manifest = new ManifestHelper(this.config.app.manifest,
-                                      this.config.app.origin);
-    return manifest.fullLaunchPath();
+    let url = Services.io.newURI(this.config.app.origin, null, null);
+    if (this.config.app.manifest.launch_path) {
+      url = Services.io.newURI(this.config.app.manifest.launch_path, null, url);
+    }
+    return url;
   }
 };
--- a/webapprt/WebappsHandler.jsm
+++ b/webapprt/WebappsHandler.jsm
@@ -7,17 +7,16 @@
 this.EXPORTED_SYMBOLS = ["WebappsHandler"];
 
 let Cc = Components.classes;
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Webapps.jsm");
-Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import("resource://gre/modules/WebappsInstaller.jsm");
 Cu.import("resource://gre/modules/WebappOSUtils.jsm");
 
 this.WebappsHandler = {
   init: function() {
     Services.obs.addObserver(this, "webapps-ask-install", false);
     Services.obs.addObserver(this, "webapps-launch", false);
     Services.obs.addObserver(this, "webapps-uninstall", false);
@@ -38,19 +37,17 @@ this.WebappsHandler = {
         break;
       case "webapps-uninstall":
         WebappOSUtils.uninstall(data);
         break;
     }
   },
 
   doInstall: function(data, window) {
-    let jsonManifest = data.isPackage ? data.app.updateManifest : data.app.manifest;
-    let manifest = new ManifestHelper(jsonManifest, data.app.origin);
-    let name = manifest.name;
+    let {name} = data.app.manifest;
     let bundle = Services.strings.createBundle("chrome://webapprt/locale/webapp.properties");
 
     let choice = Services.prompt.confirmEx(
       window,
       bundle.formatStringFromName("webapps.install.title", [name], 1),
       bundle.formatStringFromName("webapps.install.description", [name], 1),
       // Set both buttons to strings with the cancel button being default
       Ci.nsIPromptService.BUTTON_POS_1_DEFAULT |
@@ -58,30 +55,16 @@ this.WebappsHandler = {
         Ci.nsIPromptService.BUTTON_TITLE_IS_STRING * Ci.nsIPromptService.BUTTON_POS_1,
       bundle.GetStringFromName("webapps.install.install"),
       bundle.GetStringFromName("webapps.install.dontinstall"),
       null,
       null,
       {});
 
     // Perform the install if the user allows it
-    if (choice == 0) {
-      let shell = WebappsInstaller.init(data);
-
-      if (shell) {
-        let localDir = null;
-        if (shell.appProfile) {
-          localDir = shell.appProfile.localDir;
-        }
-
-        DOMApplicationRegistry.confirmInstall(data, false, localDir, null,
-          function (aManifest) {
-            WebappsInstaller.install(data, aManifest);
-          }
-        );
-      } else {
-        DOMApplicationRegistry.denyInstall(data);
-      }
-    } else {
+    if (choice == 0 && WebappsInstaller.install(data)) {
+      DOMApplicationRegistry.confirmInstall(data);
+    }
+    else {
       DOMApplicationRegistry.denyInstall(data);
     }
   }
 };
--- a/webapprt/test/chrome/head.js
+++ b/webapprt/test/chrome/head.js
@@ -26,17 +26,17 @@ function loadWebapp(manifest, parameters
   let url = Services.io.newURI(manifest, null, MANIFEST_URL_BASE);
 
   becomeWebapp(url.spec, parameters, function onBecome() {
     function onLoadApp() {
       gAppBrowser.removeEventListener("load", onLoadApp, true);
       onLoad();
     }
     gAppBrowser.addEventListener("load", onLoadApp, true);
-    gAppBrowser.setAttribute("src", WebappRT.launchURI);
+    gAppBrowser.setAttribute("src", WebappRT.launchURI.spec);
   });
 
   registerCleanupFunction(function() {
     // We load DOMApplicationRegistry into a local scope to avoid appearing
     // to leak it.
     let scope = {};
     Cu.import("resource://gre/modules/Webapps.jsm", scope);
     scope.DOMApplicationRegistry.uninstall(url.spec);