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 153348 f57e629bb4c3c84c8cef412989b6c577b52c6dae
parent 153347 b82c0f866f6487fc36e97e2015065818d47d4742
child 153349 3a9f93aa72c9386743b7cf2cb0e72572f5a27f37
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)
bugs777402
milestone25.0a1
backs out357e720b5a377ecf0e6fa6da9a82059dc5f98581
f1971c2f523249e5a369724fc870160225921a6f
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
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);