Backed out changeset 4ce831d82c1f (bug 898647) for gaia-ui bustage on an already CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Fri, 14 Mar 2014 14:13:25 -0700
changeset 190905 f8a9b4867802000910ed715385ba2f7d39fbdd07
parent 190904 a582eefb45b211e65d24df5f2af9ea5da27bb78b
child 190906 46d18be48bc4210e60eb4f64224768650cb66382
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs898647
milestone30.0a1
backs out4ce831d82c1fef2be4dadebfee2e9b2f315bc8ec
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 changeset 4ce831d82c1f (bug 898647) for gaia-ui bustage on an already CLOSED TREE
browser/components/nsBrowserGlue.js
browser/installer/package-manifest.in
browser/modules/WebappManager.jsm
browser/modules/moz.build
browser/modules/webappsUI.jsm
dom/apps/src/AppsUtils.jsm
dom/apps/src/Webapps.jsm
dom/tests/browser/browser_webapps_permissions.js
toolkit/webapps/LinuxNativeApp.js
toolkit/webapps/MacNativeApp.js
toolkit/webapps/NativeApp.jsm
toolkit/webapps/WebappOSUtils.jsm
toolkit/webapps/WebappsInstaller.jsm
toolkit/webapps/WinNativeApp.js
toolkit/webapps/moz.build
toolkit/webapps/tests/chrome.ini
toolkit/webapps/tests/head.js
toolkit/webapps/tests/test_hosted.xul
toolkit/webapps/tests/test_packaged.xul
webapprt/Startup.jsm
webapprt/WebappManager.jsm
webapprt/WebappRT.jsm
webapprt/WebappsHandler.jsm
webapprt/moz.build
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -33,18 +33,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/PlacesUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
                                   "resource://gre/modules/BookmarkHTMLUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils",
                                   "resource://gre/modules/BookmarkJSONUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "WebappManager",
-                                  "resource:///modules/WebappManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "webappsUI",
+                                  "resource:///modules/webappsUI.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
                                   "resource://gre/modules/PageThumbs.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
                                   "resource://gre/modules/NewTabUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserNewTabPreloader",
@@ -463,17 +463,17 @@ BrowserGlue.prototype = {
     // prefs are applied in _onAppDefaults()
     this._distributionCustomizer.applyCustomizations();
 
     // handle any UI migration
     this._migrateUI();
 
     this._syncSearchEngines();
 
-    WebappManager.init();
+    webappsUI.init();
     PageThumbs.init();
     NewTabUtils.init();
     BrowserNewTabPreloader.init();
     SignInToWebsiteUX.init();
     PdfJs.init();
 #ifdef NIGHTLY_BUILD
     ShumwayUtils.init();
 #endif
@@ -649,17 +649,17 @@ BrowserGlue.prototype = {
                          .getService(Ci.nsIAppStartup);
       appStartup.trackStartupCrashEnd();
     } catch (e) {
       Cu.reportError("Could not end startup crash tracking in quit-application-granted: " + e);
     }
 
     BrowserNewTabPreloader.uninit();
     CustomizationTabPreloader.uninit();
-    WebappManager.uninit();
+    webappsUI.uninit();
     SignInToWebsiteUX.uninit();
     webrtcUI.uninit();
   },
 
   // All initial windows have opened.
   _onWindowsRestored: function BG__onWindowsRestored() {
     // Show update notification, if needed.
     if (Services.prefs.prefHasUserValue("app.update.postupdate"))
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -792,17 +792,17 @@ bin/libfreebl_32int64_3.so
 @BINPATH@/webapprt/components/CommandLineHandler.js
 @BINPATH@/webapprt/components/ContentPermission.js
 @BINPATH@/webapprt/components/DirectoryProvider.js
 @BINPATH@/webapprt/components/PaymentUIGlue.js
 @BINPATH@/webapprt/components/components.manifest
 @BINPATH@/webapprt/defaults/preferences/prefs.js
 @BINPATH@/webapprt/modules/Startup.jsm
 @BINPATH@/webapprt/modules/WebappRT.jsm
-@BINPATH@/webapprt/modules/WebappManager.jsm
+@BINPATH@/webapprt/modules/WebappsHandler.jsm
 @BINPATH@/webapprt/modules/RemoteDebugger.jsm
 @BINPATH@/webapprt/modules/WebRTCHandler.jsm
 #endif
 
 #ifdef MOZ_METRO
 @BINPATH@/components/MetroUIUtils.js
 @BINPATH@/components/MetroUIUtils.manifest
 [metro]
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -15,17 +15,17 @@ EXTRA_JS_MODULES += [
     'Feeds.jsm',
     'NetworkPrioritizer.jsm',
     'offlineAppCache.jsm',
     'SharedFrame.jsm',
     'SignInToWebsite.jsm',
     'SitePermissions.jsm',
     'Social.jsm',
     'TabCrashReporter.jsm',
-    'WebappManager.jsm',
+    'webappsUI.jsm',
     'webrtcUI.jsm',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     EXTRA_JS_MODULES += [
         'WindowsJumpLists.jsm',
         'WindowsPreviewPerTab.jsm',
     ]
rename from browser/modules/WebappManager.jsm
rename to browser/modules/webappsUI.jsm
--- a/browser/modules/WebappManager.jsm
+++ b/browser/modules/webappsUI.jsm
@@ -1,45 +1,45 @@
 /* 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/. */
 
-this.EXPORTED_SYMBOLS = ["WebappManager"];
+this.EXPORTED_SYMBOLS = ["webappsUI"];
 
 let Ci = Components.interfaces;
 let Cc = Components.classes;
 let Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Webapps.jsm");
 Cu.import("resource://gre/modules/AppsUtils.jsm");
-Cu.import("resource://gre/modules/NativeApp.jsm");
+Cu.import("resource://gre/modules/WebappsInstaller.jsm");
 Cu.import("resource://gre/modules/WebappOSUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsIMessageSender");
 
-this.WebappManager = {
+this.webappsUI = {
   // List of promises for in-progress installations
   installations: {},
 
-  init: function() {
+  init: function webappsUI_init() {
     Services.obs.addObserver(this, "webapps-ask-install", false);
     Services.obs.addObserver(this, "webapps-launch", false);
     Services.obs.addObserver(this, "webapps-uninstall", false);
     cpmm.addMessageListener("Webapps:Install:Return:OK", this);
     cpmm.addMessageListener("Webapps:Install:Return:KO", this);
     cpmm.addMessageListener("Webapps:UpdateState", this);
   },
 
-  uninit: function() {
+  uninit: function webappsUI_uninit() {
     Services.obs.removeObserver(this, "webapps-ask-install");
     Services.obs.removeObserver(this, "webapps-launch");
     Services.obs.removeObserver(this, "webapps-uninstall");
     cpmm.removeMessageListener("Webapps:Install:Return:OK", this);
     cpmm.removeMessageListener("Webapps:Install:Return:KO", this);
     cpmm.removeMessageListener("Webapps:UpdateState", this);
   },
 
@@ -66,17 +66,17 @@ this.WebappManager = {
       if (!manifest.appcache_path) {
         this.installations[manifestURL].resolve();
       }
     } else if (aMessage.name == "Webapps:Install:Return:KO") {
       this.installations[manifestURL].reject(data.error);
     }
   },
 
-  observe: function(aSubject, aTopic, aData) {
+  observe: function webappsUI_observe(aSubject, aTopic, aData) {
     let data = JSON.parse(aData);
     data.mm = aSubject;
 
     switch(aTopic) {
       case "webapps-ask-install":
         let win = this._getWindowForId(data.oid);
         if (win && win.location.href == data.from) {
           this.doInstall(data, win);
@@ -103,18 +103,16 @@ this.WebappManager = {
                          .chromeEventHandler;
     let chromeDoc = browser.ownerDocument;
     let chromeWin = chromeDoc.defaultView;
     let popupProgressContent =
       chromeDoc.getElementById("webapps-install-progress-content");
 
     let bundle = chromeWin.gNavigatorBundle;
 
-    let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest;
-
     let notification;
 
     let mainAction = {
       label: bundle.getString("webapps.install"),
       accessKey: bundle.getString("webapps.install.accesskey"),
       callback: () => {
         notification.remove();
 
@@ -125,60 +123,62 @@ this.WebappManager = {
                              "webapps-notification-icon");
 
         let progressMeter = chromeDoc.createElement("progressmeter");
         progressMeter.setAttribute("mode", "undetermined");
         popupProgressContent.appendChild(progressMeter);
 
         let manifestURL = aData.app.manifestURL;
 
-        let cleanup = () => {
+        let cleanup = (ex) => {
           popupProgressContent.removeChild(progressMeter);
           delete this.installations[manifestURL];
           if (Object.getOwnPropertyNames(this.installations).length == 0) {
             notification.remove();
           }
         };
 
         this.installations[manifestURL] = Promise.defer();
         this.installations[manifestURL].promise.then(null, (error) => {
           Cu.reportError("Error installing webapp: " + error);
           cleanup();
         });
 
-        let nativeApp = new NativeApp(aData.app, jsonManifest,
-                                      aData.app.categories);
-        let localDir;
-        try {
-          localDir = nativeApp.createProfile();
-        } catch (ex) {
-          Cu.reportError("Error installing webapp: " + ex);
+        let app = WebappsInstaller.init(aData);
+
+        if (app) {
+          let localDir = null;
+          if (app.appProfile) {
+            localDir = app.appProfile.localDir;
+          }
+
+          DOMApplicationRegistry.confirmInstall(aData, localDir,
+            (aManifest, aZipPath) => {
+              Task.spawn(function() {
+                try {
+                  yield WebappsInstaller.install(aData, aManifest, aZipPath);
+                  yield this.installations[manifestURL].promise;
+                  installationSuccessNotification(aData, app, bundle);
+                } catch (ex) {
+                  Cu.reportError("Error installing webapp: " + ex);
+                  // TODO: Notify user that the installation has failed
+                } finally {
+                  cleanup();
+                }
+              }.bind(this));
+            });
+        } else {
           DOMApplicationRegistry.denyInstall(aData);
           cleanup();
-          return;
         }
-
-        DOMApplicationRegistry.confirmInstall(aData, localDir,
-          (aManifest, aZipPath) => Task.spawn((function*() {
-            try {
-              yield nativeApp.install(aManifest, aZipPath);
-              yield this.installations[manifestURL].promise;
-              notifyInstallSuccess(aData.app, nativeApp, bundle);
-            } catch (ex) {
-              Cu.reportError("Error installing webapp: " + ex);
-              // TODO: Notify user that the installation has failed
-            } finally {
-              cleanup();
-            }
-          }).bind(this))
-        );
       }
     };
 
     let requestingURI = chromeWin.makeURI(aData.from);
+    let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest;
     let manifest = new ManifestHelper(jsonManifest, aData.app.origin);
 
     let host;
     try {
       host = requestingURI.host;
     } catch(e) {
       host = requestingURI.spec;
     }
@@ -190,27 +190,27 @@ this.WebappManager = {
                                                      "webapps-install",
                                                      message,
                                                      "webapps-notification-icon",
                                                      mainAction);
 
   }
 }
 
-function notifyInstallSuccess(aApp, aNativeApp, aBundle) {
+function installationSuccessNotification(aData, app, aBundle) {
   let launcher = {
     observe: function(aSubject, aTopic) {
       if (aTopic == "alertclickcallback") {
-        WebappOSUtils.launch(aApp);
+        WebappOSUtils.launch(aData.app);
       }
     }
   };
 
   try {
     let notifier = Cc["@mozilla.org/alerts-service;1"].
                    getService(Ci.nsIAlertsService);
 
-    notifier.showAlertNotification(aNativeApp.iconURI.spec,
+    notifier.showAlertNotification(app.iconURI.spec,
                                    aBundle.getString("webapps.install.success"),
-                                   aNativeApp.appNameAsFilename,
+                                   app.appNameAsFilename,
                                    true, null, launcher);
   } catch (ex) {}
 }
--- a/dom/apps/src/AppsUtils.jsm
+++ b/dom/apps/src/AppsUtils.jsm
@@ -203,17 +203,17 @@ this.AppsUtils = {
     // so we can't use the 'removable' property for isCoreApp
     // Instead, we check if the app is installed under /system/b2g
     let isCoreApp = false;
 
 #ifdef MOZ_WIDGET_GONK
     isCoreApp = app.basePath == this.getCoreAppsBasePath();
 #endif
     debug(app.basePath + " isCoreApp: " + isCoreApp);
-    return { "path": WebappOSUtils.getPackagePath(app),
+    return { "path":  WebappOSUtils.getInstallPath(app),
              "isCoreApp": isCoreApp };
   },
 
   /**
     * Remove potential HTML tags from displayable fields in the manifest.
     * We check name, description, developer name, and permission description
     */
   sanitizeManifest: function(aManifest) {
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -72,18 +72,16 @@ function supportSystemMessages() {
   return Services.prefs.getBoolPref("dom.sysmsg.enabled");
 }
 
 // Minimum delay between two progress events while downloading, in ms.
 const MIN_PROGRESS_EVENT_DELAY = 1500;
 
 const WEBAPP_RUNTIME = Services.appinfo.ID == "webapprt@mozilla.org";
 
-const chromeWindowType = WEBAPP_RUNTIME ? "webapprt:webapp" : "navigator:browser";
-
 XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
   Cu.import("resource://gre/modules/NetUtil.jsm");
   return NetUtil;
 });
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIMessageBroadcaster");
@@ -130,17 +128,16 @@ XPCOMUtils.defineLazyGetter(this, "updat
 const STORE_ID_PENDING_PREFIX = "#unknownID#";
 
 this.DOMApplicationRegistry = {
   // Path to the webapps.json file where we store the registry data.
   appsFile: null,
   webapps: { },
   children: [ ],
   allAppsLaunchable: false,
-  _updateHandlers: [ ],
 
   init: function() {
     this.messages = ["Webapps:Install", "Webapps:Uninstall",
                      "Webapps:GetSelf", "Webapps:CheckInstalled",
                      "Webapps:GetInstalled", "Webapps:GetNotInstalled",
                      "Webapps:Launch", "Webapps:GetAll",
                      "Webapps:InstallPackage",
                      "Webapps:GetList", "Webapps:RegisterForMessages",
@@ -1194,33 +1191,16 @@ this.DOMApplicationRegistry = {
     if (!(aMsgName in this.children)) {
       return;
     }
     this.children[aMsgName].forEach(function(mmRef) {
       mmRef.mm.sendAsyncMessage(aMsgName, aContent);
     });
   },
 
-  registerUpdateHandler: function(aHandler) {
-    this._updateHandlers.push(aHandler);
-  },
-
-  unregisterUpdateHandler: function(aHandler) {
-    let index = this._updateHandlers.indexOf(aHandler);
-    if (index != -1) {
-      this._updateHandlers.splice(index, 1);
-    }
-  },
-
-  notifyUpdateHandlers: function(aApp, aManifest, aZipPath) {
-    for (let updateHandler of this._updateHandlers) {
-      updateHandler(aApp, aManifest, aZipPath);
-    }
-  },
-
   _getAppDir: function(aId) {
     return FileUtils.getDir(DIRECTORY_NAME, ["webapps", aId], true, true);
   },
 
   _writeFile: function(aPath, aData) {
     debug("Saving " + aPath);
 
     return OS.File.writeAtomic(aPath,
@@ -1502,23 +1482,16 @@ this.DOMApplicationRegistry = {
           delete app.staged;
         }
 
         delete app.retryingDownload;
 
         this._saveApps().then(() => {
           // Update the handlers and permissions for this app.
           this.updateAppHandlers(aOldManifest, aData, app);
-
-          this._loadJSONAsync(staged.path).then((aUpdateManifest) => {
-            let appObject = AppsUtils.cloneAppObject(app);
-            appObject.updateManifest = aUpdateManifest;
-            this.notifyUpdateHandlers(appObject, aData, appFile.path);
-          });
-
           if (supportUseCurrentProfile()) {
             PermissionsInstaller.installPermissions(
               { manifest: aData,
                 origin: app.origin,
                 manifestURL: app.manifestURL },
               true);
           }
           this.updateDataStore(this.webapps[id].localId, app.origin,
@@ -1919,18 +1892,16 @@ this.DOMApplicationRegistry = {
     }
 
     aApp.manifest = aNewManifest || aOldManifest;
 
     let manifest;
     if (aNewManifest) {
       this.updateAppHandlers(aOldManifest, aNewManifest, aApp);
 
-      this.notifyUpdateHandlers(AppsUtils.cloneAppObject(aApp), aNewManifest);
-
       // Store the new manifest.
       let dir = this._getAppDir(aId).path;
       let manFile = OS.Path.join(dir, "manifest.webapp");
       this._writeFile(manFile, JSON.stringify(aNewManifest));
       manifest = new ManifestHelper(aNewManifest, aApp.origin);
 
       if (supportUseCurrentProfile()) {
         // Update the permissions for this app.
@@ -2693,17 +2664,17 @@ onInstallSuccessAck: function onInstallS
       aOnSuccess,
       this._revertDownloadPackage.bind(this, id, oldApp, aNewApp, aIsUpdate)
     );
   },
 
   _ensureSufficientStorage: function(aNewApp) {
     let deferred = Promise.defer();
 
-    let navigator = Services.wm.getMostRecentWindow(chromeWindowType)
+    let navigator = Services.wm.getMostRecentWindow("navigator:browser")
                             .navigator;
     let deviceStorage = null;
 
     if (navigator.getDeviceStorage) {
       deviceStorage = navigator.getDeviceStorage("apps");
     }
 
     if (deviceStorage) {
--- a/dom/tests/browser/browser_webapps_permissions.js
+++ b/dom/tests/browser/browser_webapps_permissions.js
@@ -58,17 +58,17 @@ function test() {
   var browser = gBrowser.selectedBrowser;
   PopupNotifications.panel.addEventListener("popupshown", handlePopup, false);
 
   registerCleanupFunction(function () {
     gWindow = null;
     gBrowser.removeTab(tab);
 
     // The installation may have created a XUL alert window
-    // (see notifyInstallSuccess in WebappManager.jsm).
+    // (see webappsUI.installationSuccessNotification).
     // It need to be closed before the test finishes.
     var browsers = windowMediator.getEnumerator('alert:alert');
       while (browsers.hasMoreElements()) {
       browsers.getNext().close();
     }
   });
 
   browser.addEventListener("DOMContentLoaded", function onLoad(event) {
deleted file mode 100644
--- a/toolkit/webapps/LinuxNativeApp.js
+++ /dev/null
@@ -1,335 +0,0 @@
-/* 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/. */
-
-/**
- * Constructor for the Linux native app shell
- *
- * @param aApp {Object} the app object provided to the install function
- * @param aManifest {Object} the manifest data provided by the web app
- * @param aCategories {Array} array of app categories
- * @param aRegistryDir {String} (optional) path to the registry
- */
-function NativeApp(aApp, aManifest, aCategories, aRegistryDir) {
-  CommonNativeApp.call(this, aApp, aManifest, aCategories, aRegistryDir);
-
-  this.iconFile = "icon.png";
-  this.webapprt = "webapprt-stub";
-  this.configJson = "webapp.json";
-  this.webappINI = "webapp.ini";
-  this.zipFile = "application.zip";
-
-  this.backupFiles = [ this.iconFile, this.configJson, this.webappINI ];
-  if (this.isPackaged) {
-    this.backupFiles.push(this.zipFile);
-  }
-
-  let xdg_data_home = Cc["@mozilla.org/process/environment;1"].
-                      getService(Ci.nsIEnvironment).
-                      get("XDG_DATA_HOME");
-  if (!xdg_data_home) {
-    xdg_data_home = OS.Path.join(HOME_DIR, ".local", "share");
-  }
-
-  // The desktop file name is: "owa-" + sanitized app name +
-  // "-" + manifest url hash.
-  this.desktopINI = OS.Path.join(xdg_data_home, "applications",
-                                 "owa-" + this.uniqueName + ".desktop");
-}
-
-NativeApp.prototype = {
-  __proto__: CommonNativeApp.prototype,
-
-  /**
-   * Creates a native installation of the web app in the OS
-   *
-   * @param aManifest {Object} the manifest data provided by the web app
-   * @param aZipPath {String} path to the zip file for packaged apps (undefined
-   *                          for hosted apps)
-   */
-  install: Task.async(function*(aManifest, aZipPath) {
-    if (this._dryRun) {
-      return;
-    }
-
-    // If the application is already installed, this is a reinstallation.
-    if (WebappOSUtils.getInstallPath(this.app)) {
-      return yield this.prepareUpdate(aManifest, aZipPath);
-    }
-
-    this._setData(aManifest);
-
-    // The installation directory name is: sanitized app name +
-    // "-" + manifest url hash.
-    let installDir = OS.Path.join(HOME_DIR, "." + this.uniqueName);
-
-    let dir = getFile(TMP_DIR, this.uniqueName);
-    dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
-    let tmpDir = dir.path;
-
-    // Create the installation in a temporary directory.
-    try {
-      this._copyPrebuiltFiles(tmpDir);
-      yield this._createConfigFiles(tmpDir);
-
-      if (aZipPath) {
-        yield OS.File.move(aZipPath, OS.Path.join(tmpDir, this.zipFile));
-      }
-
-      yield this._getIcon(tmpDir);
-    } catch (ex) {
-      yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
-      throw ex;
-    }
-
-    // Apply the installation.
-    this._removeInstallation(true, installDir);
-
-    try {
-      yield this._applyTempInstallation(tmpDir, installDir);
-    } catch (ex) {
-      this._removeInstallation(false, installDir);
-      yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
-      throw ex;
-    }
-  }),
-
-  /**
-   * Creates an update in a temporary directory to be applied later.
-   *
-   * @param aManifest {Object} the manifest data provided by the web app
-   * @param aZipPath {String} path to the zip file for packaged apps (undefined
-   *                          for hosted apps)
-   */
-  prepareUpdate: Task.async(function*(aManifest, aZipPath) {
-    if (this._dryRun) {
-      return;
-    }
-
-    this._setData(aManifest);
-
-    let installDir = WebappOSUtils.getInstallPath(this.app);
-    if (!installDir) {
-      throw ERR_NOT_INSTALLED;
-    }
-
-    let baseName = OS.Path.basename(installDir)
-    let oldUniqueName = baseName.substring(1, baseName.length);
-    if (this.uniqueName != oldUniqueName) {
-      // Bug 919799: If the app is still in the registry, migrate its data to
-      // the new format.
-      throw ERR_UPDATES_UNSUPPORTED_OLD_NAMING_SCHEME;
-    }
-
-    let updateDir = OS.Path.join(installDir, "update");
-    yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
-    yield OS.File.makeDir(updateDir);
-
-    try {
-      yield this._createConfigFiles(updateDir);
-
-      if (aZipPath) {
-        yield OS.File.move(aZipPath, OS.Path.join(updateDir, this.zipFile));
-      }
-
-      yield this._getIcon(updateDir);
-    } catch (ex) {
-      yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
-      throw ex;
-    }
-  }),
-
-  /**
-   * Applies an update.
-   */
-  applyUpdate: Task.async(function*() {
-    if (this._dryRun) {
-      return;
-    }
-
-    let installDir = WebappOSUtils.getInstallPath(this.app);
-    let updateDir = OS.Path.join(installDir, "update");
-
-    let backupDir = yield this._backupInstallation(installDir);
-
-    try {
-      yield this._applyTempInstallation(updateDir, installDir);
-    } catch (ex) {
-      yield this._restoreInstallation(backupDir, installDir);
-      throw ex;
-    } finally {
-      yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
-      yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
-    }
-  }),
-
-  _applyTempInstallation: Task.async(function*(aTmpDir, aInstallDir) {
-    yield moveDirectory(aTmpDir, aInstallDir);
-
-    this._createSystemFiles(aInstallDir);
-  }),
-
-  _removeInstallation: function(keepProfile, aInstallDir) {
-    let filesToRemove = [this.desktopINI];
-
-    if (keepProfile) {
-      for (let filePath of this.backupFiles) {
-        filesToRemove.push(OS.Path.join(aInstallDir, filePath));
-      }
-
-      filesToRemove.push(OS.Path.join(aInstallDir, this.webapprt));
-    } else {
-      filesToRemove.push(aInstallDir);
-    }
-
-    removeFiles(filesToRemove);
-  },
-
-  _backupInstallation: Task.async(function*(aInstallDir) {
-    let backupDir = OS.Path.join(aInstallDir, "backup");
-    yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
-    yield OS.File.makeDir(backupDir);
-
-    for (let filePath of this.backupFiles) {
-      yield OS.File.move(OS.Path.join(aInstallDir, filePath),
-                         OS.Path.join(backupDir, filePath));
-    }
-
-    return backupDir;
-  }),
-
-  _restoreInstallation: function(aBackupDir, aInstallDir) {
-    return moveDirectory(aBackupDir, aInstallDir);
-  },
-
-  _copyPrebuiltFiles: function(aDir) {
-    let destDir = getFile(aDir);
-    let stub = getFile(this.runtimeFolder, this.webapprt);
-    stub.copyTo(destDir, null);
-  },
-
-  /**
-   * Translate marketplace categories to freedesktop.org categories.
-   *
-   * @link http://standards.freedesktop.org/menu-spec/menu-spec-latest.html#category-registry
-   *
-   * @return an array of categories
-   */
-  _translateCategories: function() {
-    let translations = {
-      "books": "Education;Literature",
-      "business": "Finance",
-      "education": "Education",
-      "entertainment": "Amusement",
-      "sports": "Sports",
-      "games": "Game",
-      "health-fitness": "MedicalSoftware",
-      "lifestyle": "Amusement",
-      "music": "Audio;Music",
-      "news-weather": "News",
-      "photo-video": "Video;AudioVideo;Photography",
-      "productivity": "Office",
-      "shopping": "Amusement",
-      "social": "Chat",
-      "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) {
-      let catLower = category.toLowerCase();
-      if (catLower in translations) {
-        categories += translations[catLower] + ";";
-      }
-    }
-
-    return categories;
-  },
-
-  _createConfigFiles: function(aDir) {
-    // ${InstallDir}/webapp.json
-    yield writeToFile(OS.Path.join(aDir, this.configJson),
-                      JSON.stringify(this.webappJson));
-
-    let webappsBundle = Services.strings.createBundle("chrome://global/locale/webapps.properties");
-
-    // ${InstallDir}/webapp.ini
-    let webappINIfile = getFile(aDir, this.webappINI);
-
-    let writer = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
-                 getService(Ci.nsIINIParserFactory).
-                 createINIParser(webappINIfile).
-                 QueryInterface(Ci.nsIINIParserWriter);
-    writer.setString("Webapp", "Name", this.appName);
-    writer.setString("Webapp", "Profile", this.uniqueName);
-    writer.setString("Webapp", "UninstallMsg", webappsBundle.formatStringFromName("uninstall.notification", [this.appName], 1));
-    writer.setString("WebappRT", "InstallDir", this.runtimeFolder);
-    writer.writeFile();
-  },
-
-  _createSystemFiles: function(aInstallDir) {
-    let webappsBundle = Services.strings.createBundle("chrome://global/locale/webapps.properties");
-
-    let webapprtPath = OS.Path.join(aInstallDir, this.webapprt);
-
-    // $XDG_DATA_HOME/applications/owa-<webappuniquename>.desktop
-    let desktopINIfile = getFile(this.desktopINI);
-
-    let writer = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
-                 getService(Ci.nsIINIParserFactory).
-                 createINIParser(desktopINIfile).
-                 QueryInterface(Ci.nsIINIParserWriter);
-    writer.setString("Desktop Entry", "Name", this.appName);
-    writer.setString("Desktop Entry", "Comment", this.shortDescription);
-    writer.setString("Desktop Entry", "Exec", '"' + webapprtPath + '"');
-    writer.setString("Desktop Entry", "Icon", OS.Path.join(aInstallDir,
-                                                           this.iconFile));
-    writer.setString("Desktop Entry", "Type", "Application");
-    writer.setString("Desktop Entry", "Terminal", "false");
-
-    let categories = this._translateCategories();
-    if (categories)
-      writer.setString("Desktop Entry", "Categories", categories);
-
-    writer.setString("Desktop Entry", "Actions", "Uninstall;");
-    writer.setString("Desktop Action Uninstall", "Name", webappsBundle.GetStringFromName("uninstall.label"));
-    writer.setString("Desktop Action Uninstall", "Exec", webapprtPath + " -remove");
-
-    writer.writeFile();
-
-    desktopINIfile.permissions = PERMS_FILE | OS.Constants.libc.S_IXUSR;
-  },
-
-  /**
-   * Process the icon from the imageStream as retrieved from
-   * the URL by getIconForApp().
-   *
-   * @param aMimeType     ahe icon mimetype
-   * @param aImageStream  the stream for the image data
-   * @param aDir          the directory where the icon should be stored
-   */
-  _processIcon: function(aMimeType, aImageStream, aDir) {
-    let deferred = Promise.defer();
-
-    let imgTools = Cc["@mozilla.org/image/tools;1"].
-                   createInstance(Ci.imgITools);
-
-    let imgContainer = imgTools.decodeImage(aImageStream, aMimeType);
-    let iconStream = imgTools.encodeImage(imgContainer, "image/png");
-
-    let iconFile = getFile(aDir, this.iconFile);
-    let outputStream = FileUtils.openSafeFileOutputStream(iconFile);
-    NetUtil.asyncCopy(iconStream, outputStream, function(aResult) {
-      if (Components.isSuccessCode(aResult)) {
-        deferred.resolve();
-      } else {
-        deferred.reject("Failure copying icon: " + aResult);
-      }
-    });
-
-    return deferred.promise;
-  }
-}
deleted file mode 100644
--- a/toolkit/webapps/MacNativeApp.js
+++ /dev/null
@@ -1,308 +0,0 @@
-/* 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/. */
-
-const USER_LIB_DIR = OS.Constants.Path.macUserLibDir;
-const LOCAL_APP_DIR = OS.Constants.Path.macLocalApplicationsDir;
-
-/**
- * Constructor for the Mac native app shell
- *
- * @param aApp {Object} the app object provided to the install function
- * @param aManifest {Object} the manifest data provided by the web app
- * @param aCategories {Array} array of app categories
- * @param aRegistryDir {String} (optional) path to the registry
- */
-function NativeApp(aApp, aManifest, aCategories, aRegistryDir) {
-  CommonNativeApp.call(this, aApp, aManifest, aCategories, aRegistryDir);
-
-  // The ${ProfileDir} is: sanitized app name + "-" + manifest url hash
-  this.appProfileDir = OS.Path.join(USER_LIB_DIR, "Application Support",
-                                    this.uniqueName);
-  this.configJson = "webapp.json";
-
-  this.contentsDir = "Contents";
-  this.macOSDir = OS.Path.join(this.contentsDir, "MacOS");
-  this.resourcesDir = OS.Path.join(this.contentsDir, "Resources");
-  this.iconFile = OS.Path.join(this.resourcesDir, "appicon.icns");
-  this.zipFile = OS.Path.join(this.resourcesDir, "application.zip");
-}
-
-NativeApp.prototype = {
-  __proto__: CommonNativeApp.prototype,
-
-  /**
-   * Creates a native installation of the web app in the OS
-   *
-   * @param aManifest {Object} the manifest data provided by the web app
-   * @param aZipPath {String} path to the zip file for packaged apps (undefined
-   *                          for hosted apps)
-   */
-  install: Task.async(function*(aManifest, aZipPath) {
-    if (this._dryRun) {
-      return;
-    }
-
-    // If the application is already installed, this is a reinstallation.
-    if (WebappOSUtils.getInstallPath(this.app)) {
-      return yield this.prepareUpdate(aManifest, aZipPath);
-    }
-
-    this._setData(aManifest);
-
-    let localAppDir = getFile(LOCAL_APP_DIR);
-    if (!localAppDir.isWritable()) {
-      throw("Not enough privileges to install apps");
-    }
- 
-    let destinationName = yield getAvailableFileName([ LOCAL_APP_DIR ],
-                                                     this.appNameAsFilename,
-                                                     ".app");
-
-    let installDir = OS.Path.join(LOCAL_APP_DIR, destinationName);
-
-    let dir = getFile(TMP_DIR, this.appNameAsFilename + ".app");
-    dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
-    let tmpDir = dir.path;
-
-    try {
-      yield this._createDirectoryStructure(tmpDir);
-      this._copyPrebuiltFiles(tmpDir);
-      yield this._createConfigFiles(tmpDir);
-
-      if (aZipPath) {
-        yield OS.File.move(aZipPath, OS.Path.join(tmpDir, this.zipFile));
-      }
-
-      yield this._getIcon(tmpDir);
-    } catch (ex) {
-      yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
-      throw ex;
-    }
-
-    this._removeInstallation(true, installDir);
-
-    try {
-      // Move the temp installation directory to the /Applications directory
-      yield this._applyTempInstallation(tmpDir, installDir);
-    } catch (ex) {
-      this._removeInstallation(false, installDir);
-      yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
-      throw ex;
-    }
-  }),
-
-  /**
-   * Creates an update in a temporary directory to be applied later.
-   *
-   * @param aManifest {Object} the manifest data provided by the web app
-   * @param aZipPath {String} path to the zip file for packaged apps (undefined
-   *                          for hosted apps)
-   */
-  prepareUpdate: Task.async(function*(aManifest, aZipPath) {
-    if (this._dryRun) {
-      return;
-    }
-
-    this._setData(aManifest);
-
-    let [ oldUniqueName, installDir ] = WebappOSUtils.getLaunchTarget(this.app);
-    if (!installDir) {
-      throw ERR_NOT_INSTALLED;
-    }
-
-    if (this.uniqueName != oldUniqueName) {
-      // Bug 919799: If the app is still in the registry, migrate its data to
-      // the new format.
-      throw ERR_UPDATES_UNSUPPORTED_OLD_NAMING_SCHEME;
-    }
-
-    let updateDir = OS.Path.join(installDir, "update");
-    yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
-    yield OS.File.makeDir(updateDir);
-
-    try {
-      yield this._createDirectoryStructure(updateDir);
-      this._copyPrebuiltFiles(updateDir);
-      yield this._createConfigFiles(updateDir);
-
-      if (aZipPath) {
-        yield OS.File.move(aZipPath, OS.Path.join(updateDir, this.zipFile));
-      }
-
-      yield this._getIcon(updateDir);
-    } catch (ex) {
-      yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
-      throw ex;
-    }
-  }),
-
-  /**
-   * Applies an update.
-   */
-  applyUpdate: Task.async(function*() {
-    if (this._dryRun) {
-      return;
-    }
-
-    let installDir = WebappOSUtils.getInstallPath(this.app);
-    let updateDir = OS.Path.join(installDir, "update");
-
-    let backupDir = yield this._backupInstallation(installDir);
-
-    try {
-      // Move the update directory to the /Applications directory
-      yield this._applyTempInstallation(updateDir, installDir);
-    } catch (ex) {
-      yield this._restoreInstallation(backupDir, installDir);
-      throw ex;
-    } finally {
-      yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
-      yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
-    }
-  }),
-
-  _applyTempInstallation: Task.async(function*(aTmpDir, aInstallDir) {
-    yield OS.File.move(OS.Path.join(aTmpDir, this.configJson),
-                       OS.Path.join(this.appProfileDir, this.configJson));
-
-    yield moveDirectory(aTmpDir, aInstallDir);
-  }),
-
-  _removeInstallation: function(keepProfile, aInstallDir) {
-    let filesToRemove = [ aInstallDir ];
-
-    if (!keepProfile) {
-      filesToRemove.push(this.appProfileDir);
-    }
-
-    removeFiles(filesToRemove);
-  },
-
-  _backupInstallation: Task.async(function*(aInstallDir) {
-    let backupDir = OS.Path.join(aInstallDir, "backup");
-    yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
-    yield OS.File.makeDir(backupDir);
-
-    yield moveDirectory(OS.Path.join(aInstallDir, this.contentsDir),
-                        backupDir);
-    yield OS.File.move(OS.Path.join(this.appProfileDir, this.configJson),
-                       OS.Path.join(backupDir, this.configJson));
-
-    return backupDir;
-  }),
-
-  _restoreInstallation: Task.async(function*(aBackupDir, aInstallDir) {
-    yield OS.File.move(OS.Path.join(aBackupDir, this.configJson),
-                       OS.Path.join(this.appProfileDir, this.configJson));
-    yield moveDirectory(aBackupDir,
-                        OS.Path.join(aInstallDir, this.contentsDir));
-  }),
-
-  _createDirectoryStructure: Task.async(function*(aDir) {
-    yield OS.File.makeDir(this.appProfileDir,
-                          { unixMode: PERMS_DIRECTORY, ignoreExisting: true });
-
-    yield OS.File.makeDir(OS.Path.join(aDir, this.contentsDir),
-                          { unixMode: PERMS_DIRECTORY, ignoreExisting: true });
-
-    yield OS.File.makeDir(OS.Path.join(aDir, this.macOSDir),
-                          { unixMode: PERMS_DIRECTORY, ignoreExisting: true });
-
-    yield OS.File.makeDir(OS.Path.join(aDir, this.resourcesDir),
-                          { unixMode: PERMS_DIRECTORY, ignoreExisting: true });
-  }),
-
-  _copyPrebuiltFiles: function(aDir) {
-    let destDir = getFile(aDir, this.macOSDir);
-    let stub = getFile(this.runtimeFolder, "webapprt-stub");
-    stub.copyTo(destDir, "webapprt");
-  },
-
-  _createConfigFiles: function(aDir) {
-    // ${ProfileDir}/webapp.json
-    yield writeToFile(OS.Path.join(aDir, this.configJson),
-                      JSON.stringify(this.webappJson));
-
-    // ${InstallDir}/Contents/MacOS/webapp.ini
-    let applicationINI = getFile(aDir, this.macOSDir, "webapp.ini");
-
-    let writer = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
-                 getService(Ci.nsIINIParserFactory).
-                 createINIParser(applicationINI).
-                 QueryInterface(Ci.nsIINIParserWriter);
-    writer.setString("Webapp", "Name", this.appName);
-    writer.setString("Webapp", "Profile", this.uniqueName);
-    writer.writeFile();
-    applicationINI.permissions = PERMS_FILE;
-
-    // ${InstallDir}/Contents/Info.plist
-    let infoPListContent = '<?xml version="1.0" encoding="UTF-8"?>\n\
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n\
-<plist version="1.0">\n\
-  <dict>\n\
-    <key>CFBundleDevelopmentRegion</key>\n\
-    <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\
-    <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\
-    <key>NSHighResolutionCapable</key>\n\
-    <true/>\n\
-    <key>NSPrincipalClass</key>\n\
-    <string>GeckoNSApplication</string>\n\
-    <key>FirefoxBinary</key>\n\
-#expand     <string>__MOZ_MACBUNDLE_ID__</string>\n\
-  </dict>\n\
-</plist>';
-
-    yield writeToFile(OS.Path.join(aDir, this.contentsDir, "Info.plist"),
-                      infoPListContent);
-  },
-
-  /**
-   * Process the icon from the imageStream as retrieved from
-   * the URL by getIconForApp(). This will bundle the icon to the
-   * app package at Contents/Resources/appicon.icns.
-   *
-   * @param aMimeType     the icon mimetype
-   * @param aImageStream  the stream for the image data
-   * @param aDir          the directory where the icon should be stored
-   */
-  _processIcon: function(aMimeType, aIcon, aDir) {
-    let deferred = Promise.defer();
-
-    function conversionDone(aSubject, aTopic) {
-      if (aTopic == "process-finished") {
-        deferred.resolve();
-      } else {
-        deferred.reject("Failure converting icon, exit code: " + aSubject.exitValue);
-      }
-    }
-
-    let process = Cc["@mozilla.org/process/util;1"].
-                  createInstance(Ci.nsIProcess);
-    let sipsFile = getFile("/usr/bin/sips");
-
-    process.init(sipsFile);
-    process.runAsync(["-s", "format", "icns",
-                      aIcon.path,
-                      "--out", OS.Path.join(aDir, this.iconFile),
-                      "-z", "128", "128"],
-                      9, conversionDone);
-
-    return deferred.promise;
-  }
-}
--- a/toolkit/webapps/WebappOSUtils.jsm
+++ b/toolkit/webapps/WebappOSUtils.jsm
@@ -4,17 +4,16 @@
 
 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/FileUtils.jsm");
-Cu.import("resource://gre/modules/osfile.jsm");
 
 this.EXPORTED_SYMBOLS = ["WebappOSUtils"];
 
 // Returns the MD5 hash of a string.
 function computeHash(aString) {
   let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
                   createInstance(Ci.nsIScriptableUnicodeConverter);
   converter.charset = "UTF-8";
@@ -200,26 +199,16 @@ this.WebappOSUtils = {
     return execFile.parent.path;
 #endif
 
 #endif
     // Anything unsupported, like Metro
     throw new Error("Unsupported apps platform");
   },
 
-  getPackagePath: function(aApp) {
-    let packagePath = this.getInstallPath(aApp);
-
-#ifdef XP_MACOSX
-    packagePath = OS.Path.join(packagePath, "Contents", "Resources");
-#endif
-
-    return packagePath;
-  },
-
   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);
rename from toolkit/webapps/NativeApp.jsm
rename to toolkit/webapps/WebappsInstaller.jsm
--- a/toolkit/webapps/NativeApp.jsm
+++ b/toolkit/webapps/WebappsInstaller.jsm
@@ -1,109 +1,158 @@
 /* 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/. */
 
-this.EXPORTED_SYMBOLS = ["NativeApp"];
+this.EXPORTED_SYMBOLS = ["WebappsInstaller"];
 
 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");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 
-const ERR_NOT_INSTALLED = "The application isn't installed";
-const ERR_UPDATES_UNSUPPORTED_OLD_NAMING_SCHEME =
-  "Updates for apps installed with the old naming scheme unsupported";
-
 // 0755
 const PERMS_DIRECTORY = OS.Constants.libc.S_IRWXU |
                         OS.Constants.libc.S_IRGRP | OS.Constants.libc.S_IXGRP |
                         OS.Constants.libc.S_IROTH | OS.Constants.libc.S_IXOTH;
 
 // 0644
 const PERMS_FILE = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR |
                    OS.Constants.libc.S_IRGRP |
                    OS.Constants.libc.S_IROTH;
 
 const DESKTOP_DIR = OS.Constants.Path.desktopDir;
 const HOME_DIR = OS.Constants.Path.homeDir;
 const TMP_DIR = OS.Constants.Path.tmpDir;
 
+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 aZipPath path to the zip file for packaged apps (undefined for
+   *                 hosted apps)
+   */
+  install: function(aData, aManifest, aZipPath) {
+    try {
+      if (Services.prefs.getBoolPref("browser.mozApps.installer.dry_run")) {
+        return Promise.resolve();
+      }
+    } catch (ex) {}
+
+    this.shell.init(aData, aManifest);
+
+    return this.shell.install(aZipPath).then(() => {
+      let data = {
+        "installDir": this.shell.installDir,
+        "app": {
+          "manifest": aManifest,
+          "origin": aData.app.origin
+        }
+      };
+
+      Services.obs.notifyObservers(null, "webapp-installed", JSON.stringify(data));
+    });
+  }
+}
+
 /**
  * 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
- * CommonNativeApp.call(this, ...) from the platform-specific
+ * NativeApp.call(this, aData) from the platform-specific
  * constructor.
  *
- * @param aApp {Object} the app object provided to the install function
- * @param aManifest {Object} the manifest data provided by the web app
- * @param aCategories {Array} array of app categories
- * @param aRegistryDir {String} (optional) path to the registry
+ * @param aData the data object provided to the install function
  *
  */
-function CommonNativeApp(aApp, aManifest, aCategories, aRegistryDir) {
-  let manifest = new ManifestHelper(aManifest, aApp.origin);
+function NativeApp(aData) {
+  let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest;
+  let manifest = new ManifestHelper(jsonManifest, aData.app.origin);
 
-  aApp.name = manifest.name;
-  this.uniqueName = WebappOSUtils.getUniqueName(aApp);
+  aData.app.name = manifest.name;
+  this.uniqueName = WebappOSUtils.getUniqueName(aData.app);
 
   this.appName = sanitize(manifest.name);
   this.appNameAsFilename = stripStringForFilename(this.appName);
-
-  if (aApp.updateManifest) {
-    this.isPackaged = true;
-  }
-
-  this.categories = aCategories.slice(0);
-
-  this.registryDir = aRegistryDir || OS.Constants.Path.profileDir;
-
-  this.app = aApp;
-
-  this._dryRun = false;
-  try {
-    if (Services.prefs.getBoolPref("browser.mozApps.installer.dry_run")) {
-      this._dryRun = true;
-    }
-  } catch (ex) {}
 }
 
-CommonNativeApp.prototype = {
+NativeApp.prototype = {
   uniqueName: null,
   appName: null,
   appNameAsFilename: null,
   iconURI: null,
   developerName: null,
   shortDescription: null,
   categories: null,
   webappJson: null,
   runtimeFolder: null,
   manifest: null,
-  registryDir: null,
 
   /**
    * This function reads and parses the data from the app
    * manifest and stores it in the NativeApp object.
    *
-   * @param aManifest {Object} the manifest data provided by the web app
+   * @param aData the data object provided to the install function
+   * @param aManifest the manifest data provided by the web app
    *
    */
-  _setData: function(aManifest) {
-    let manifest = new ManifestHelper(aManifest, this.app.origin);
-    let origin = Services.io.newURI(this.app.origin, null, null);
+  init: function(aData, aManifest) {
+    let app = this.app = aData.app;
+    let manifest = this.manifest = new ManifestHelper(aManifest,
+                                                      app.origin);
+
+    let origin = Services.io.newURI(app.origin, null, null);
 
     let biggestIcon = getBiggestIconURL(manifest.icons);
     try {
       let iconURI = Services.io.newURI(biggestIcon, null, null);
       if (iconURI.scheme == "data") {
         this.iconURI = iconURI;
       }
     } catch (ex) {}
@@ -133,119 +182,905 @@ CommonNativeApp.prototype = {
       let shortDesc = firstLine.length <= 256
                       ? firstLine
                       : firstLine.substr(0, 253) + "…";
       this.shortDescription = sanitize(shortDesc);
     } else {
       this.shortDescription = this.appName;
     }
 
-    if (manifest.version) {
-      this.version = manifest.version;
-    }
+    this.categories = app.categories.slice(0);
 
     this.webappJson = {
       // The app registry is the Firefox profile from which the app
       // was installed.
-      "registryDir": this.registryDir,
+      "registryDir": OS.Constants.Path.profileDir,
       "app": {
         "manifest": aManifest,
-        "origin": this.app.origin,
-        "manifestURL": this.app.manifestURL,
-        "installOrigin": this.app.installOrigin,
-        "categories": this.categories,
-        "receipts": this.app.receipts,
-        "installTime": this.app.installTime,
+        "origin": app.origin,
+        "manifestURL": app.manifestURL,
+        "installOrigin": app.installOrigin,
+        "categories": app.categories,
+        "receipts": app.receipts,
+        "installTime": app.installTime,
       }
     };
 
-    if (this.app.etag) {
-      this.webappJson.app.etag = this.app.etag;
+    if (app.etag) {
+      this.webappJson.app.etag = app.etag;
     }
 
-    if (this.app.packageEtag) {
-      this.webappJson.app.packageEtag = this.app.packageEtag;
+    if (app.packageEtag) {
+      this.webappJson.app.packageEtag = app.packageEtag;
     }
 
-    if (this.app.updateManifest) {
-      this.webappJson.app.updateManifest = this.app.updateManifest;
+    if (app.updateManifest) {
+      this.webappJson.app.updateManifest = app.updateManifest;
     }
 
     this.runtimeFolder = OS.Constants.Path.libDir;
   },
 
   /**
    * This function retrieves the icon for an app.
    * If the retrieving fails, it uses the default chrome icon.
    */
-  _getIcon: function(aTmpDir) {
+  getIcon: function() {
     try {
       // If the icon is in the zip package, we should modify the url
       // to point to the zip file (we can't use the app protocol yet
       // because the app isn't installed yet).
       if (this.iconURI.scheme == "app") {
-        let zipUrl = OS.Path.toFileURI(OS.Path.join(aTmpDir,
+        let zipUrl = OS.Path.toFileURI(OS.Path.join(this.tmpInstallDir,
                                                     "application.zip"));
 
         let filePath = this.iconURI.QueryInterface(Ci.nsIURL).filePath;
 
         this.iconURI = Services.io.newURI("jar:" + zipUrl + "!" + filePath,
                                           null, null);
       }
 
 
       let [ mimeType, icon ] = yield downloadIcon(this.iconURI);
-      yield this._processIcon(mimeType, icon, aTmpDir);
+      yield this.processIcon(mimeType, icon);
     }
     catch(e) {
       Cu.reportError("Failure retrieving icon: " + e);
 
       let iconURI = Services.io.newURI(DEFAULT_ICON_URL, null, null);
 
       let [ mimeType, icon ] = yield downloadIcon(iconURI);
-      yield this._processIcon(mimeType, icon, aTmpDir);
+      yield this.processIcon(mimeType, icon);
 
       // Set the iconURI property so that the user notification will have the
       // correct icon.
       this.iconURI = iconURI;
     }
   },
 
   /**
    * Creates the profile to be used for this app.
    */
-  createProfile: function() {
-    if (this._dryRun) {
-      return null;
-    }
-
-    let profSvc = Cc["@mozilla.org/toolkit/profile-service;1"].
-                  getService(Ci.nsIToolkitProfileService);
+  createAppProfile: function() {
+    let profSvc = Cc["@mozilla.org/toolkit/profile-service;1"]
+                    .getService(Ci.nsIToolkitProfileService);
 
     try {
-      let appProfile = profSvc.createDefaultProfileForApp(this.uniqueName,
-                                                          null, null);
-      return appProfile.localDir;
-    } catch (ex if ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {
-      return null;
-    }
+      this.appProfile = profSvc.createDefaultProfileForApp(this.uniqueName,
+                                                           null, null);
+    } catch (ex if ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {}
   },
 };
 
 #ifdef XP_WIN
 
-#include WinNativeApp.js
+const PROGS_DIR = OS.Constants.Path.winStartMenuProgsDir;
+const APP_DATA_DIR = OS.Constants.Path.winAppDataDir;
+
+/*************************************
+ * Windows app installer
+ *
+ * The Windows installation process will generate the following files:
+ *
+ * ${FolderName} = sanitized app name + "-" + manifest url hash
+ *
+ * %APPDATA%/${FolderName}
+ *   - webapp.ini
+ *   - webapp.json
+ *   - ${AppName}.exe
+ *   - ${AppName}.lnk
+ *   / uninstall
+ *     - webapp-uninstaller.exe
+ *     - shortcuts_log.ini
+ *     - uninstall.log
+ *   / chrome/icons/default/
+ *     - default.ico
+ *
+ * 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
+ */
+function WinNativeApp(aData) {
+  NativeApp.call(this, aData);
+
+  if (aData.isPackage) {
+    this.size = aData.app.updateManifest.size / 1024;
+    this.isPackaged = true;
+  }
+
+  let filenameRE = new RegExp("[<>:\"/\\\\|\\?\\*]", "gi");
+
+  this.appNameAsFilename = this.appNameAsFilename.replace(filenameRE, "");
+  if (this.appNameAsFilename == "") {
+    this.appNameAsFilename = "webapp";
+  }
+
+  this.webapprt = this.appNameAsFilename + ".exe";
+  this.configJson = "webapp.json";
+  this.webappINI = "webapp.ini";
+  this.iconPath = OS.Path.join("chrome", "icons", "default", "default.ico");
+  this.uninstallDir = "uninstall";
+  this.uninstallerFile = OS.Path.join(this.uninstallDir,
+                                      "webapp-uninstaller.exe");
+  this.shortcutLogsINI = OS.Path.join(this.uninstallDir, "shortcuts_log.ini");
+
+  this.uninstallSubkeyStr = this.uniqueName;
+}
+
+WinNativeApp.prototype = {
+  __proto__: NativeApp.prototype,
+  size: null,
+
+  /**
+   * Install the app in the system
+   *
+   */
+  install: function(aZipPath) {
+    return Task.spawn(function() {
+      yield this._getInstallDir();
+
+      try {
+        yield this._createDirectoryStructure();
+        yield this._copyPrebuiltFiles();
+        yield this._createConfigFiles();
+
+        if (aZipPath) {
+          yield OS.File.move(aZipPath, OS.Path.join(this.tmpInstallDir,
+                                                    "application.zip"));
+        }
+
+        yield this.getIcon();
+
+        // Remove previously installed app
+        this._removeInstallation(true);
+      } catch (ex) {
+        removeFiles([this.tmpInstallDir]);
+        throw(ex);
+      }
+
+      try {
+        // On Windows, the webapprt executable can't be overwritten while it's
+        // running.
+        // As it takes care of updating itself, there's no need to update
+        // it here.
+        let filesToIgnore = [ this.webapprt ];
+        yield moveDirectory(this.tmpInstallDir, this.installDir, filesToIgnore);
+
+        this._createShortcutFiles();
+        this._writeSystemKeys();
+      } catch (ex) {
+        this._removeInstallation(false);
+        throw(ex);
+      }
+    }.bind(this));
+  },
+
+  _getInstallDir: function() {
+    // The ${InstallDir} is: sanitized app name + "-" + manifest url hash
+    this.installDir = WebappOSUtils.getInstallPath(this.app);
+    if (this.installDir) {
+      if (this.uniqueName != OS.Path.basename(this.installDir)) {
+        // Bug 919799: If the app is still in the registry, migrate its data to
+        // the new format.
+        throw("Updates for apps installed with the old naming scheme unsupported");
+      }
+
+      let shortcutLogsINIfile = getFile(this.installDir, this.shortcutLogsINI);
+      // If it's a reinstallation (or an update) get the shortcut names
+      // from the shortcut_log.ini file
+      let parser = Cc["@mozilla.org/xpcom/ini-processor-factory;1"]
+                     .getService(Ci.nsIINIParserFactory)
+                     .createINIParser(shortcutLogsINIfile);
+      this.shortcutName = parser.getString("STARTMENU", "Shortcut0");
+    } else {
+      this.installDir = OS.Path.join(APP_DATA_DIR, this.uniqueName);
+
+      // Check in both directories to see if a shortcut with the same name
+      // already exists.
+      this.shortcutName = yield getAvailableFileName([ PROGS_DIR, DESKTOP_DIR ],
+                                                     this.appNameAsFilename,
+                                                     ".lnk");
+    }
+  },
+
+  /**
+   * Remove the current installation
+   */
+  _removeInstallation: function(keepProfile) {
+    let uninstallKey;
+    try {
+      uninstallKey = Cc["@mozilla.org/windows-registry-key;1"]
+                     .createInstance(Ci.nsIWindowsRegKey);
+      uninstallKey.open(uninstallKey.ROOT_KEY_CURRENT_USER,
+                        "SOFTWARE\\Microsoft\\Windows\\" +
+                        "CurrentVersion\\Uninstall",
+                        uninstallKey.ACCESS_WRITE);
+      if(uninstallKey.hasChild(this.uninstallSubkeyStr)) {
+        uninstallKey.removeChild(this.uninstallSubkeyStr);
+      }
+    } catch (e) {
+    } finally {
+      if(uninstallKey)
+        uninstallKey.close();
+    }
+
+    let filesToRemove = [ OS.Path.join(DESKTOP_DIR, this.shortcutName),
+                          OS.Path.join(PROGS_DIR, this.shortcutName) ];
+
+    if (keepProfile) {
+      [ this.iconPath, this.webapprt, this.configJson,
+        this.webappINI, this.uninstallDir ].forEach((filePath) => {
+        filesToRemove.push(OS.Path.join(this.installDir, filePath));
+      });
+    } else {
+      filesToRemove.push(this.installDir);
+      filesToRemove.push(this.tmpInstallDir);
+    }
+
+    removeFiles(filesToRemove);
+  },
+
+  /**
+   * Creates the main directory structure.
+   */
+  _createDirectoryStructure: function() {
+    let dir = getFile(TMP_DIR, this.uniqueName);
+    dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
+    this.tmpInstallDir = dir.path;
+
+    yield OS.File.makeDir(OS.Path.join(this.tmpInstallDir, this.uninstallDir),
+                          { ignoreExisting: true });
+
+    // Recursively create the icon path's directory structure.
+    let path = this.tmpInstallDir;
+    let components = OS.Path.split(OS.Path.dirname(this.iconPath)).components;
+    for (let component of components) {
+      path = OS.Path.join(path, component);
+      yield OS.File.makeDir(path, { ignoreExisting: true });
+    }
+  },
+
+  /**
+   * Copy the pre-built files into their destination folders.
+   */
+  _copyPrebuiltFiles: function() {
+    yield OS.File.copy(OS.Path.join(this.runtimeFolder, "webapprt-stub.exe"),
+                       OS.Path.join(this.tmpInstallDir, this.webapprt));
+
+    yield OS.File.copy(OS.Path.join(this.runtimeFolder, "webapp-uninstaller.exe"),
+                       OS.Path.join(this.tmpInstallDir, this.uninstallerFile));
+  },
+
+  /**
+   * Creates the configuration files into their destination folders.
+   */
+  _createConfigFiles: function() {
+    // ${InstallDir}/webapp.json
+    yield writeToFile(OS.Path.join(this.tmpInstallDir, this.configJson),
+                      JSON.stringify(this.webappJson));
+
+    let factory = Cc["@mozilla.org/xpcom/ini-processor-factory;1"]
+                    .getService(Ci.nsIINIParserFactory);
+
+    // ${InstallDir}/webapp.ini
+    let webappINIfile = getFile(this.tmpInstallDir, this.webappINI);
+
+    let writer = factory.createINIParser(webappINIfile)
+                        .QueryInterface(Ci.nsIINIParserWriter);
+    writer.setString("Webapp", "Name", this.appName);
+    writer.setString("Webapp", "Profile", OS.Path.basename(this.installDir));
+    writer.setString("Webapp", "Executable", this.appNameAsFilename);
+    writer.setString("WebappRT", "InstallDir", this.runtimeFolder);
+    writer.writeFile(null, Ci.nsIINIParserWriter.WRITE_UTF16);
+
+    let shortcutLogsINIfile = getFile(this.tmpInstallDir, this.shortcutLogsINI);
+
+    writer = factory.createINIParser(shortcutLogsINIfile)
+                    .QueryInterface(Ci.nsIINIParserWriter);
+    writer.setString("STARTMENU", "Shortcut0", this.shortcutName);
+    writer.setString("DESKTOP", "Shortcut0", this.shortcutName);
+    writer.setString("TASKBAR", "Migrated", "true");
+    writer.writeFile(null, Ci.nsIINIParserWriter.WRITE_UTF16);
+
+    // ${UninstallDir}/uninstall.log
+    let uninstallContent = 
+      "File: \\webapp.ini\r\n" +
+      "File: \\webapp.json\r\n" +
+      "File: \\webapprt.old\r\n" +
+      "File: \\chrome\\icons\\default\\default.ico";
+    if (this.isPackaged) {
+      uninstallContent += "\r\nFile: \\application.zip";
+    }
+
+    yield writeToFile(OS.Path.join(this.tmpInstallDir, this.uninstallDir,
+                                   "uninstall.log"),
+                      uninstallContent);
+  },
+
+  /**
+   * Writes the keys to the system registry that are necessary for the app operation
+   * and uninstall process.
+   */
+  _writeSystemKeys: function() {
+    let parentKey;
+    let uninstallKey;
+    let subKey;
+
+    try {
+      parentKey = Cc["@mozilla.org/windows-registry-key;1"]
+                  .createInstance(Ci.nsIWindowsRegKey);
+      parentKey.open(parentKey.ROOT_KEY_CURRENT_USER,
+                     "SOFTWARE\\Microsoft\\Windows\\CurrentVersion",
+                     parentKey.ACCESS_WRITE);
+      uninstallKey = parentKey.createChild("Uninstall", parentKey.ACCESS_WRITE)
+      subKey = uninstallKey.createChild(this.uninstallSubkeyStr, uninstallKey.ACCESS_WRITE);
+
+      subKey.writeStringValue("DisplayName", this.appName);
+
+      let uninstallerPath = OS.Path.join(this.installDir,
+                                         this.uninstallerFile);
+
+      subKey.writeStringValue("UninstallString", '"' + uninstallerPath + '"');
+      subKey.writeStringValue("InstallLocation", '"' + this.installDir + '"');
+      subKey.writeStringValue("AppFilename", this.appNameAsFilename);
+      subKey.writeStringValue("DisplayIcon", OS.Path.join(this.installDir,
+                                                          this.iconPath));
+
+      let date = new Date();
+      let year = date.getYear().toString();
+      let month = date.getMonth();
+      if (month < 10) {
+        month = "0" + month;
+      }
+      let day = date.getDate();
+      if (day < 10) {
+        day = "0" + day;
+      }
+      subKey.writeStringValue("InstallDate", year + month + day);
+      if (this.manifest.version) {
+        subKey.writeStringValue("DisplayVersion", this.manifest.version);
+      }
+      if (this.developerName) {
+        subKey.writeStringValue("Publisher", this.developerName);
+      }
+      subKey.writeStringValue("URLInfoAbout", this.developerUrl);
+      if (this.size) {
+        subKey.writeIntValue("EstimatedSize", this.size);
+      }
+
+      subKey.writeIntValue("NoModify", 1);
+      subKey.writeIntValue("NoRepair", 1);
+    } catch(ex) {
+      throw(ex);
+    } finally {
+      if(subKey) subKey.close();
+      if(uninstallKey) uninstallKey.close();
+      if(parentKey) parentKey.close();
+    }
+  },
+
+  /**
+   * Creates a shortcut file inside the app installation folder and makes
+   * two copies of it: one into the desktop and one into the start menu.
+   */
+  _createShortcutFiles: function() {
+    let shortcut = getFile(this.installDir, this.shortcutName).
+                      QueryInterface(Ci.nsILocalFileWin);
+
+    /* function nsILocalFileWin.setShortcut(targetFile, workingDir, args,
+                                            description, iconFile, iconIndex) */
+
+    shortcut.setShortcut(getFile(this.installDir, this.webapprt),
+                         getFile(this.installDir),
+                         null,
+                         this.shortDescription,
+                         getFile(this.installDir, this.iconPath),
+                         0);
+
+    shortcut.copyTo(getFile(DESKTOP_DIR), this.shortcutName);
+    shortcut.copyTo(getFile(PROGS_DIR), this.shortcutName);
+
+    shortcut.followLinks = false;
+    shortcut.remove(false);
+  },
+
+  /**
+   * Process the icon from the imageStream as retrieved from
+   * the URL by getIconForApp(). This will save the icon to the
+   * topwindow.ico file.
+   *
+   * @param aMimeType     ahe icon mimetype
+   * @param aImageStream  the stream for the image data
+   */
+  processIcon: function(aMimeType, aImageStream) {
+    let deferred = Promise.defer();
+
+    let imgTools = Cc["@mozilla.org/image/tools;1"]
+                     .createInstance(Ci.imgITools);
+
+    let imgContainer = imgTools.decodeImage(aImageStream, aMimeType);
+    let iconStream = imgTools.encodeImage(imgContainer,
+                                          "image/vnd.microsoft.icon",
+                                          "format=bmp;bpp=32");
+
+    let tmpIconFile = getFile(this.tmpInstallDir, this.iconPath);
+
+    let outputStream = FileUtils.openSafeFileOutputStream(tmpIconFile);
+    NetUtil.asyncCopy(iconStream, outputStream, function(aResult) {
+      if (Components.isSuccessCode(aResult)) {
+        deferred.resolve();
+      } else {
+        deferred.reject("Failure copying icon: " + aResult);
+      }
+    });
+
+    return deferred.promise;
+  }
+}
 
 #elifdef XP_MACOSX
 
-#include MacNativeApp.js
+const USER_LIB_DIR = OS.Constants.Path.macUserLibDir;
+const LOCAL_APP_DIR = OS.Constants.Path.macLocalApplicationsDir;
+
+function MacNativeApp(aData) {
+  NativeApp.call(this, aData);
+
+  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
+  this.appProfileDir = OS.Path.join(USER_LIB_DIR, "Application Support",
+                                    this.uniqueName);
+
+  this.contentsDir = "Contents";
+  this.macOSDir = OS.Path.join(this.contentsDir, "MacOS");
+  this.resourcesDir = OS.Path.join(this.contentsDir, "Resources");
+  this.iconFile = OS.Path.join(this.resourcesDir, "appicon.icns");
+}
+
+MacNativeApp.prototype = {
+  __proto__: NativeApp.prototype,
+
+  install: function(aZipPath) {
+    return Task.spawn(function() {
+      yield this._getInstallDir();
+
+      try {
+        yield this._createDirectoryStructure();
+        this._copyPrebuiltFiles();
+        yield this._createConfigFiles();
+
+        if (aZipPath) {
+          yield OS.File.move(aZipPath, OS.Path.join(this.tmpInstallDir,
+                                                    "application.zip"));
+        }
+
+        yield this.getIcon();
+
+        // Remove previously installed app
+        this._removeInstallation(true);
+      } catch (ex) {
+        removeFiles([this.tmpInstallDir]);
+        throw(ex);
+      }
+
+      try {
+        // Move the temp installation directory to the /Applications directory
+        yield moveDirectory(this.tmpInstallDir, this.installDir, []);
+      } catch (ex) {
+        this._removeInstallation(false);
+        throw(ex);
+      }
+    }.bind(this));
+  },
+
+  _getInstallDir: function() {
+    let [ oldUniqueName, installPath ] = WebappOSUtils.getLaunchTarget(this.app);
+    if (installPath) {
+      this.installDir = installPath;
+
+      if (this.uniqueName != oldUniqueName) {
+        // Bug 919799: If the app is still in the registry, migrate its data to
+        // the new format.
+        throw("Updates for apps installed with the old naming scheme unsupported");
+      }
+    } else {
+      let localAppDir = getFile(LOCAL_APP_DIR);
+      if (!localAppDir.isWritable()) {
+        throw("Not enough privileges to install apps");
+      }
+
+      let destinationName = yield getAvailableFileName([ LOCAL_APP_DIR ],
+                                                       this.appNameAsFilename,
+                                                       ".app");
+
+      this.installDir = OS.Path.join(LOCAL_APP_DIR, destinationName);
+    }
+  },
+
+  _removeInstallation: function(keepProfile) {
+    let filesToRemove = [this.installDir];
+
+    if (!keepProfile) {
+      filesToRemove.push(this.appProfileDir);
+    }
+
+    removeFiles(filesToRemove);
+  },
+
+  _createDirectoryStructure: function() {
+    let dir = getFile(TMP_DIR, this.appNameAsFilename + ".app");
+    dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
+    this.tmpInstallDir = dir.path;
+
+    yield OS.File.makeDir(this.appProfileDir,
+                          { unixMode: PERMS_DIRECTORY, ignoreExisting: true });
+
+    yield OS.File.makeDir(OS.Path.join(this.tmpInstallDir, this.contentsDir),
+                          { unixMode: PERMS_DIRECTORY, ignoreExisting: true });
+
+    yield OS.File.makeDir(OS.Path.join(this.tmpInstallDir, this.macOSDir),
+                          { unixMode: PERMS_DIRECTORY, ignoreExisting: true });
+
+    yield OS.File.makeDir(OS.Path.join(this.tmpInstallDir, this.resourcesDir),
+                          { unixMode: PERMS_DIRECTORY, ignoreExisting: true });
+  },
+
+  _copyPrebuiltFiles: function() {
+    let destDir = getFile(this.tmpInstallDir, this.macOSDir);
+    let stub = getFile(this.runtimeFolder, "webapprt-stub");
+    stub.copyTo(destDir, "webapprt");
+  },
+
+  _createConfigFiles: function() {
+    // ${ProfileDir}/webapp.json
+    yield writeToFile(OS.Path.join(this.appProfileDir, "webapp.json"),
+                      JSON.stringify(this.webappJson));
+
+    // ${InstallDir}/Contents/MacOS/webapp.ini
+    let applicationINI = getFile(this.tmpInstallDir, this.macOSDir, "webapp.ini");
+
+    let writer = Cc["@mozilla.org/xpcom/ini-processor-factory;1"]
+                   .getService(Ci.nsIINIParserFactory)
+                   .createINIParser(applicationINI)
+                   .QueryInterface(Ci.nsIINIParserWriter);
+    writer.setString("Webapp", "Name", this.appName);
+    writer.setString("Webapp", "Profile", OS.Path.basename(this.appProfileDir));
+    writer.writeFile();
+    applicationINI.permissions = PERMS_FILE;
+
+    // ${InstallDir}/Contents/Info.plist
+    let infoPListContent = '<?xml version="1.0" encoding="UTF-8"?>\n\
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n\
+<plist version="1.0">\n\
+  <dict>\n\
+    <key>CFBundleDevelopmentRegion</key>\n\
+    <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\
+    <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\
+    <key>NSHighResolutionCapable</key>\n\
+    <true/>\n\
+    <key>NSPrincipalClass</key>\n\
+    <string>GeckoNSApplication</string>\n\
+    <key>FirefoxBinary</key>\n\
+#expand     <string>__MOZ_MACBUNDLE_ID__</string>\n\
+  </dict>\n\
+</plist>';
+
+    yield writeToFile(OS.Path.join(this.tmpInstallDir, this.contentsDir, "Info.plist"),
+                      infoPListContent);
+  },
+
+  /**
+   * Process the icon from the imageStream as retrieved from
+   * the URL by getIconForApp(). This will bundle the icon to the
+   * app package at Contents/Resources/appicon.icns.
+   *
+   * @param aMimeType     the icon mimetype
+   * @param aImageStream  the stream for the image data
+   */
+  processIcon: function(aMimeType, aIcon) {
+    let deferred = Promise.defer();
+
+    function conversionDone(aSubject, aTopic) {
+      if (aTopic == "process-finished") {
+        deferred.resolve();
+      } else {
+        deferred.reject("Failure converting icon.");
+      }
+    }
+
+    let process = Cc["@mozilla.org/process/util;1"].
+                  createInstance(Ci.nsIProcess);
+    let sipsFile = getFile("/usr/bin/sips");
+
+    process.init(sipsFile);
+    process.runAsync(["-s",
+                "format", "icns",
+                aIcon.path,
+                "--out", OS.Path.join(this.tmpInstallDir, this.iconFile),
+                "-z", "128", "128"],
+                9, conversionDone);
+
+    return deferred.promise;
+  }
+
+}
 
 #elifdef XP_UNIX
 
-#include LinuxNativeApp.js
+function LinuxNativeApp(aData) {
+  NativeApp.call(this, aData);
+
+  this.iconFile = "icon.png";
+  this.webapprt = "webapprt-stub";
+  this.configJson = "webapp.json";
+  this.webappINI = "webapp.ini";
+
+  let xdg_data_home = Cc["@mozilla.org/process/environment;1"]
+                        .getService(Ci.nsIEnvironment)
+                        .get("XDG_DATA_HOME");
+  if (!xdg_data_home) {
+    xdg_data_home = OS.Path.join(HOME_DIR, ".local", "share");
+  }
+
+  this.desktopINI = OS.Path.join(xdg_data_home, "applications",
+                                 "owa-" + this.uniqueName + ".desktop");
+}
+
+LinuxNativeApp.prototype = {
+  __proto__: NativeApp.prototype,
+
+  install: function(aZipPath) {
+    return Task.spawn(function() {
+      this._getInstallDir();
+
+      try {
+        this._createDirectoryStructure();
+        this._copyPrebuiltFiles();
+        yield this._createConfigFiles();
+
+        if (aZipPath) {
+          yield OS.File.move(aZipPath, OS.Path.join(this.tmpInstallDir,
+                                                    "application.zip"));
+        }
+
+        yield this.getIcon();
+
+        // Remove previously installed app
+        this._removeInstallation(true);
+      } catch (ex) {
+        removeFiles([this.tmpInstallDir]);
+        throw(ex);
+      }
+
+      try {
+        yield moveDirectory(this.tmpInstallDir, this.installDir, []);
+
+        this._createSystemFiles();
+      } catch (ex) {
+        this._removeInstallation(false);
+        throw(ex);
+      }
+    }.bind(this));
+  },
+
+  _getInstallDir: function() {
+    // The ${InstallDir} and desktop entry filename are: sanitized app name +
+    // "-" + manifest url hash
+    this.installDir = WebappOSUtils.getInstallPath(this.app);
+    if (this.installDir) {
+      let baseName = OS.Path.basename(this.installDir)
+      let oldUniqueName = baseName.substring(1, baseName.length);
+      if (this.uniqueName != oldUniqueName) {
+        // Bug 919799: If the app is still in the registry, migrate its data to
+        // the new format.
+        throw("Updates for apps installed with the old naming scheme unsupported");
+      }
+    } else {
+      this.installDir = OS.Path.join(HOME_DIR, "." + this.uniqueName);
+    }
+  },
+
+  _removeInstallation: function(keepProfile) {
+    let filesToRemove = [this.desktopINI];
+
+    if (keepProfile) {
+      [ this.iconFile, this.webapprt,
+        this.configJson, this.webappINI ].forEach((filePath) => {
+        filesToRemove.push(OS.Path.join(this.installDir, filePath));
+      });
+    } else {
+      filesToRemove.push(this.installDir);
+      filesToRemove.push(this.tmpInstallDir);
+    }
+
+    removeFiles(filesToRemove);
+  },
+
+  _createDirectoryStructure: function() {
+    let dir = getFile(TMP_DIR, this.uniqueName);
+    dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
+    this.tmpInstallDir = dir.path;
+  },
+
+  _copyPrebuiltFiles: function() {
+    let destDir = getFile(this.tmpInstallDir);
+    let stub = getFile(this.runtimeFolder, this.webapprt);
+
+    stub.copyTo(destDir, null);
+  },
+
+  /**
+   * Translate marketplace categories to freedesktop.org categories.
+   *
+   * @link http://standards.freedesktop.org/menu-spec/menu-spec-latest.html#category-registry
+   *
+   * @return an array of categories
+   */
+  _translateCategories: function() {
+    let translations = {
+      "books": "Education;Literature",
+      "business": "Finance",
+      "education": "Education",
+      "entertainment": "Amusement",
+      "sports": "Sports",
+      "games": "Game",
+      "health-fitness": "MedicalSoftware",
+      "lifestyle": "Amusement",
+      "music": "Audio;Music",
+      "news-weather": "News",
+      "photo-video": "Video;AudioVideo;Photography",
+      "productivity": "Office",
+      "shopping": "Amusement",
+      "social": "Chat",
+      "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) {
+      let catLower = category.toLowerCase();
+      if (catLower in translations) {
+        categories += translations[catLower] + ";";
+      }
+    }
+
+    return categories;
+  },
+
+  _createConfigFiles: function() {
+    // ${InstallDir}/webapp.json
+    yield writeToFile(OS.Path.join(this.tmpInstallDir, this.configJson),
+                      JSON.stringify(this.webappJson));
+
+    let webappsBundle = Services.strings.createBundle("chrome://global/locale/webapps.properties");
+
+    // ${InstallDir}/webapp.ini
+    let webappINIfile = getFile(this.tmpInstallDir, this.webappINI);
+
+    let writer = Cc["@mozilla.org/xpcom/ini-processor-factory;1"]
+                   .getService(Ci.nsIINIParserFactory)
+                   .createINIParser(webappINIfile)
+                   .QueryInterface(Ci.nsIINIParserWriter);
+    writer.setString("Webapp", "Name", this.appName);
+    writer.setString("Webapp", "Profile", this.uniqueName);
+    writer.setString("Webapp", "UninstallMsg", webappsBundle.formatStringFromName("uninstall.notification", [this.appName], 1));
+    writer.setString("WebappRT", "InstallDir", this.runtimeFolder);
+    writer.writeFile();
+  },
+
+  _createSystemFiles: function() {
+    let webappsBundle = Services.strings.createBundle("chrome://global/locale/webapps.properties");
+
+    let webapprtPath = OS.Path.join(this.installDir, this.webapprt);
+
+    // $XDG_DATA_HOME/applications/owa-<webappuniquename>.desktop
+    let desktopINIfile = getFile(this.desktopINI);
+
+    let writer = Cc["@mozilla.org/xpcom/ini-processor-factory;1"]
+                   .getService(Ci.nsIINIParserFactory)
+                   .createINIParser(desktopINIfile)
+                   .QueryInterface(Ci.nsIINIParserWriter);
+    writer.setString("Desktop Entry", "Name", this.appName);
+    writer.setString("Desktop Entry", "Comment", this.shortDescription);
+    writer.setString("Desktop Entry", "Exec", '"' + webapprtPath + '"');
+    writer.setString("Desktop Entry", "Icon", OS.Path.join(this.installDir,
+                                                           this.iconFile));
+    writer.setString("Desktop Entry", "Type", "Application");
+    writer.setString("Desktop Entry", "Terminal", "false");
+
+    let categories = this._translateCategories();
+    if (categories)
+      writer.setString("Desktop Entry", "Categories", categories);
+
+    writer.setString("Desktop Entry", "Actions", "Uninstall;");
+    writer.setString("Desktop Action Uninstall", "Name", webappsBundle.GetStringFromName("uninstall.label"));
+    writer.setString("Desktop Action Uninstall", "Exec", webapprtPath + " -remove");
+
+    writer.writeFile();
+
+    desktopINIfile.permissions = PERMS_FILE | OS.Constants.libc.S_IXUSR;
+  },
+
+  /**
+   * Process the icon from the imageStream as retrieved from
+   * the URL by getIconForApp().
+   *
+   * @param aMimeType     ahe icon mimetype
+   * @param aImageStream  the stream for the image data
+   */
+  processIcon: function(aMimeType, aImageStream) {
+    let deferred = Promise.defer();
+
+    let imgTools = Cc["@mozilla.org/image/tools;1"]
+                     .createInstance(Ci.imgITools);
+
+    let imgContainer = imgTools.decodeImage(aImageStream, aMimeType);
+    let iconStream = imgTools.encodeImage(imgContainer, "image/png");
+
+    let iconFile = getFile(this.tmpInstallDir, this.iconFile);
+    let outputStream = FileUtils.openSafeFileOutputStream(iconFile);
+    NetUtil.asyncCopy(iconStream, outputStream, function(aResult) {
+      if (Components.isSuccessCode(aResult)) {
+        deferred.resolve();
+      } else {
+        deferred.reject("Failure copying icon: " + aResult);
+      }
+    });
+
+    return deferred.promise;
+  }
+}
 
 #endif
 
 /* Helper Functions */
 
 /**
  * Async write a data string into a file
  *
@@ -271,39 +1106,26 @@ function writeToFile(aPath, aData) {
  * Removes unprintable characters from a string.
  */
 function sanitize(aStr) {
   let unprintableRE = new RegExp("[\\x00-\\x1F\\x7F]" ,"gi");
   return aStr.replace(unprintableRE, "");
 }
 
 /**
- * Strips all non-word characters from the beginning and end of a string.
- * Strips invalid characters from the string.
- *
+ * Strips all non-word characters from the beginning and end of a string
  */
 function stripStringForFilename(aPossiblyBadFilenameString) {
-  // Strip everything from the front up to the first [0-9a-zA-Z]
-  let stripFrontRE = new RegExp("^\\W*", "gi");
+  //strip everything from the front up to the first [0-9a-zA-Z]
 
-  // Strip white space characters starting from the last [0-9a-zA-Z]
-  let stripBackRE = new RegExp("\\s*$", "gi");
-
-  // Strip invalid characters from the filename
-  let filenameRE = new RegExp("[<>:\"/\\\\|\\?\\*]", "gi");
+  let stripFrontRE = new RegExp("^\\W*","gi");
+  let stripBackRE = new RegExp("\\s*$","gi");
 
   let stripped = aPossiblyBadFilenameString.replace(stripFrontRE, "");
   stripped = stripped.replace(stripBackRE, "");
-  stripped = stripped.replace(filenameRE, "");
-
-  // If the filename ends up empty, let's call it "webapp".
-  if (stripped == "") {
-    stripped = "webapp";
-  }
-
   return stripped;
 }
 
 /**
  * Finds a unique name available in a folder (i.e., non-existent file)
  *
  * @param aPathSet a set of paths that represents the set of
  * directories where we want to write
@@ -365,29 +1187,40 @@ function removeFiles(aPaths) {
   }
 }
 
 /**
  * Move (overwriting) the contents of one directory into another.
  *
  * @param srcPath A path to the source directory
  * @param destPath A path to the destination directory
+ * @param filesToIgnore An array of files. If one of those files can't be
+ *                      overwritten the function will not fail.
  */
-function moveDirectory(srcPath, destPath) {
+function moveDirectory(srcPath, destPath, filesToIgnore) {
   let srcDir = getFile(srcPath);
   let destDir = getFile(destPath);
 
   let entries = srcDir.directoryEntries;
   let array = [];
   while (entries.hasMoreElements()) {
     let entry = entries.getNext().QueryInterface(Ci.nsIFile);
+
     if (entry.isDirectory()) {
       yield moveDirectory(entry.path, OS.Path.join(destPath, entry.leafName));
     } else {
-      entry.moveTo(destDir, entry.leafName);
+      try {
+        entry.moveTo(destDir, entry.leafName);
+      } catch (ex if ex.result == Cr.NS_ERROR_FILE_ACCESS_DENIED) {
+        if (filesToIgnore.indexOf(entry.leafName) != -1) {
+          yield OS.File.remove(entry.path);
+        } else {
+          throw(ex);
+        }
+      }
     }
   }
 
   // The source directory is now empty, remove it.
   yield OS.File.removeEmptyDir(srcPath);
 }
 
 function escapeXML(aStr) {
deleted file mode 100644
--- a/toolkit/webapps/WinNativeApp.js
+++ /dev/null
@@ -1,474 +0,0 @@
-/* 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/. */
-
-const PROGS_DIR = OS.Constants.Path.winStartMenuProgsDir;
-const APP_DATA_DIR = OS.Constants.Path.winAppDataDir;
-
-/*************************************
- * Windows app installer
- *
- * The Windows installation process will generate the following files:
- *
- * ${FolderName} = sanitized app name + "-" + manifest url hash
- *
- * %APPDATA%/${FolderName}
- *   - webapp.ini
- *   - webapp.json
- *   - ${AppName}.exe
- *   - ${AppName}.lnk
- *   / uninstall
- *     - webapp-uninstaller.exe
- *     - shortcuts_log.ini
- *     - uninstall.log
- *   / chrome/icons/default/
- *     - default.ico
- *
- * 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 aApp {Object} the app object provided to the install function
- * @param aManifest {Object} the manifest data provided by the web app
- * @param aCategories {Array} array of app categories
- * @param aRegistryDir {String} (optional) path to the registry
- */
-function NativeApp(aApp, aManifest, aCategories, aRegistryDir) {
-  CommonNativeApp.call(this, aApp, aManifest, aCategories, aRegistryDir);
-
-  if (this.isPackaged) {
-    this.size = aApp.updateManifest.size / 1024;
-  }
-
-  this.webapprt = this.appNameAsFilename + ".exe";
-  this.configJson = "webapp.json";
-  this.webappINI = "webapp.ini";
-  this.iconPath = OS.Path.join("chrome", "icons", "default", "default.ico");
-  this.uninstallDir = "uninstall";
-  this.uninstallerFile = OS.Path.join(this.uninstallDir,
-                                      "webapp-uninstaller.exe");
-  this.shortcutLogsINI = OS.Path.join(this.uninstallDir, "shortcuts_log.ini");
-  this.zipFile = "application.zip";
-
-  this.backupFiles = [ "chrome", this.configJson, this.webappINI, "uninstall" ];
-  if (this.isPackaged) {
-    this.backupFiles.push(this.zipFile);
-  }
-
-  this.uninstallSubkeyStr = this.uniqueName;
-}
-
-NativeApp.prototype = {
-  __proto__: CommonNativeApp.prototype,
-  size: null,
-
-  /**
-   * Creates a native installation of the web app in the OS
-   *
-   * @param aManifest {Object} the manifest data provided by the web app
-   * @param aZipPath {String} path to the zip file for packaged apps (undefined
-   *                          for hosted apps)
-   */
-  install: Task.async(function*(aManifest, aZipPath) {
-    if (this._dryRun) {
-      return;
-    }
-
-    // If the application is already installed, this is a reinstallation.
-    if (WebappOSUtils.getInstallPath(this.app)) {
-      return yield this.prepareUpdate(aManifest, aZipPath);
-    }
-
-    this._setData(aManifest);
-
-    let installDir = OS.Path.join(APP_DATA_DIR, this.uniqueName);
-
-    // Create a temporary installation directory.
-    let dir = getFile(TMP_DIR, this.uniqueName);
-    dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
-    let tmpDir = dir.path;
-
-    // Perform the installation in the temp directory.
-    try {
-      yield this._createDirectoryStructure(tmpDir);
-      yield this._getShortcutName(installDir);
-      yield this._copyWebapprt(tmpDir);
-      yield this._copyUninstaller(tmpDir);
-      yield this._createConfigFiles(tmpDir);
-
-      if (aZipPath) {
-        yield OS.File.move(aZipPath, OS.Path.join(tmpDir, this.zipFile));
-      }
-
-      yield this._getIcon(tmpDir);
-    } catch (ex) {
-      yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
-      throw ex;
-    }
-
-    // Apply the installation.
-    this._removeInstallation(true, installDir);
-
-    try {
-      yield this._applyTempInstallation(tmpDir, installDir);
-    } catch (ex) {
-      this._removeInstallation(false, installDir);
-      yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
-      throw ex;
-    }
-  }),
-
-  /**
-   * Creates an update in a temporary directory to be applied later.
-   *
-   * @param aManifest {Object} the manifest data provided by the web app
-   * @param aZipPath {String} path to the zip file for packaged apps (undefined
-   *                          for hosted apps)
-   */
-  prepareUpdate: Task.async(function*(aManifest, aZipPath) {
-    if (this._dryRun) {
-      return;
-    }
-
-    this._setData(aManifest);
-
-    let installDir = WebappOSUtils.getInstallPath(this.app);
-    if (!installDir) {
-      throw ERR_NOT_INSTALLED;
-    }
-
-    if (this.uniqueName != OS.Path.basename(installDir)) {
-      // Bug 919799: If the app is still in the registry, migrate its data to
-      // the new format.
-      throw ERR_UPDATES_UNSUPPORTED_OLD_NAMING_SCHEME;
-    }
-
-    let updateDir = OS.Path.join(installDir, "update");
-    yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
-    yield OS.File.makeDir(updateDir);
-
-    // Perform the update in the "update" subdirectory.
-    try {
-      yield this._createDirectoryStructure(updateDir);
-      yield this._getShortcutName(installDir);
-      yield this._copyUninstaller(updateDir);
-      yield this._createConfigFiles(updateDir);
-
-      if (aZipPath) {
-        yield OS.File.move(aZipPath, OS.Path.join(updateDir, this.zipFile));
-      }
-
-      yield this._getIcon(updateDir);
-    } catch (ex) {
-      yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
-      throw ex;
-    }
-  }),
-
-  /**
-   * Applies an update.
-   */
-  applyUpdate: Task.async(function*() {
-    if (this._dryRun) {
-      return;
-    }
-
-    let installDir = WebappOSUtils.getInstallPath(this.app);
-    let updateDir = OS.Path.join(installDir, "update");
-
-    yield this._getShortcutName(installDir);
-
-    let backupDir = yield this._backupInstallation(installDir);
-
-    try {
-      yield this._applyTempInstallation(updateDir, installDir);
-    } catch (ex) {
-      yield this._restoreInstallation(backupDir, installDir);
-      throw ex;
-    } finally {
-      yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
-      yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
-    }
-  }),
-
-  _applyTempInstallation: Task.async(function*(aTmpDir, aInstallDir) {
-    yield moveDirectory(aTmpDir, aInstallDir);
-
-    this._createShortcutFiles(aInstallDir);
-    this._writeSystemKeys(aInstallDir);
-  }),
-
-  _getShortcutName: Task.async(function*(aInstallDir) {
-    let shortcutLogsINIfile = getFile(aInstallDir, this.shortcutLogsINI);
-
-    if (shortcutLogsINIfile.exists()) {
-      // If it's a reinstallation (or an update) get the shortcut names
-      // from the shortcut_log.ini file
-      let parser = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
-                   getService(Ci.nsIINIParserFactory).
-                   createINIParser(shortcutLogsINIfile);
-      this.shortcutName = parser.getString("STARTMENU", "Shortcut0");
-    } else {
-      // Check in both directories to see if a shortcut with the same name
-      // already exists.
-      this.shortcutName = yield getAvailableFileName([ PROGS_DIR, DESKTOP_DIR ],
-                                                     this.appNameAsFilename,
-                                                     ".lnk");
-    }
-  }),
-
-  /**
-   * Remove the current installation
-   */
-  _removeInstallation: function(keepProfile, aInstallDir) {
-    let uninstallKey;
-    try {
-      uninstallKey = Cc["@mozilla.org/windows-registry-key;1"].
-                     createInstance(Ci.nsIWindowsRegKey);
-      uninstallKey.open(uninstallKey.ROOT_KEY_CURRENT_USER,
-                        "SOFTWARE\\Microsoft\\Windows\\" +
-                        "CurrentVersion\\Uninstall",
-                        uninstallKey.ACCESS_WRITE);
-      if (uninstallKey.hasChild(this.uninstallSubkeyStr)) {
-        uninstallKey.removeChild(this.uninstallSubkeyStr);
-      }
-    } catch (e) {
-    } finally {
-      if (uninstallKey) {
-        uninstallKey.close();
-      }
-    }
-
-    let filesToRemove = [ OS.Path.join(DESKTOP_DIR, this.shortcutName),
-                          OS.Path.join(PROGS_DIR, this.shortcutName) ];
-
-    if (keepProfile) {
-      for (let filePath of this.backupFiles) {
-        filesToRemove.push(OS.Path.join(aInstallDir, filePath));
-      }
-
-      filesToRemove.push(OS.Path.join(aInstallDir, this.webapprt));
-    } else {
-      filesToRemove.push(aInstallDir);
-    }
-
-    removeFiles(filesToRemove);
-  },
-
-  _backupInstallation: Task.async(function*(aInstallDir) {
-    let backupDir = OS.Path.join(aInstallDir, "backup");
-    yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
-    yield OS.File.makeDir(backupDir);
-
-    for (let filePath of this.backupFiles) {
-      yield OS.File.move(OS.Path.join(aInstallDir, filePath),
-                         OS.Path.join(backupDir, filePath));
-    }
-
-    return backupDir;
-  }),
-
-  _restoreInstallation: function(aBackupDir, aInstallDir) {
-    return moveDirectory(aBackupDir, aInstallDir);
-  },
-
-  /**
-   * Creates the main directory structure.
-   */
-  _createDirectoryStructure: Task.async(function*(aDir) {
-    yield OS.File.makeDir(OS.Path.join(aDir, this.uninstallDir));
-
-    // Recursively create the icon path's directory structure.
-    let path = aDir;
-    let components = OS.Path.split(OS.Path.dirname(this.iconPath)).components;
-    for (let component of components) {
-      path = OS.Path.join(path, component);
-      yield OS.File.makeDir(path);
-    }
-  }),
-
-  /**
-   * Copy the webrt executable into the installation directory.
-   */
-  _copyWebapprt: function(aDir) {
-    return OS.File.copy(OS.Path.join(this.runtimeFolder, "webapprt-stub.exe"),
-                        OS.Path.join(aDir, this.webapprt));
-  },
-
-  /**
-   * Copy the uninstaller executable into the installation directory.
-   */
-  _copyUninstaller: function(aDir) {
-    return OS.File.copy(OS.Path.join(this.runtimeFolder, "webapp-uninstaller.exe"),
-                        OS.Path.join(aDir, this.uninstallerFile));
-  },
-
-  /**
-   * Creates the configuration files into their destination folders.
-   */
-  _createConfigFiles: function(aDir) {
-    // ${InstallDir}/webapp.json
-    yield writeToFile(OS.Path.join(aDir, this.configJson),
-                      JSON.stringify(this.webappJson));
-
-    let factory = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
-                  getService(Ci.nsIINIParserFactory);
-
-    // ${InstallDir}/webapp.ini
-    let webappINIfile = getFile(aDir, this.webappINI);
-
-    let writer = factory.createINIParser(webappINIfile)
-                        .QueryInterface(Ci.nsIINIParserWriter);
-    writer.setString("Webapp", "Name", this.appName);
-    writer.setString("Webapp", "Profile", this.uniqueName);
-    writer.setString("Webapp", "Executable", this.appNameAsFilename);
-    writer.setString("WebappRT", "InstallDir", this.runtimeFolder);
-    writer.writeFile(null, Ci.nsIINIParserWriter.WRITE_UTF16);
-
-    let shortcutLogsINIfile = getFile(aDir, this.shortcutLogsINI);
-
-    writer = factory.createINIParser(shortcutLogsINIfile)
-                    .QueryInterface(Ci.nsIINIParserWriter);
-    writer.setString("STARTMENU", "Shortcut0", this.shortcutName);
-    writer.setString("DESKTOP", "Shortcut0", this.shortcutName);
-    writer.setString("TASKBAR", "Migrated", "true");
-    writer.writeFile(null, Ci.nsIINIParserWriter.WRITE_UTF16);
-
-    // ${UninstallDir}/uninstall.log
-    let uninstallContent =
-      "File: \\webapp.ini\r\n" +
-      "File: \\webapp.json\r\n" +
-      "File: \\webapprt.old\r\n" +
-      "File: \\chrome\\icons\\default\\default.ico";
-    if (this.isPackaged) {
-      uninstallContent += "\r\nFile: \\application.zip";
-    }
-
-    yield writeToFile(OS.Path.join(aDir, this.uninstallDir, "uninstall.log"),
-                      uninstallContent);
-  },
-
-  /**
-   * Writes the keys to the system registry that are necessary for the app
-   * operation and uninstall process.
-   */
-  _writeSystemKeys: function(aInstallDir) {
-    let parentKey;
-    let uninstallKey;
-    let subKey;
-
-    try {
-      parentKey = Cc["@mozilla.org/windows-registry-key;1"].
-                  createInstance(Ci.nsIWindowsRegKey);
-      parentKey.open(parentKey.ROOT_KEY_CURRENT_USER,
-                     "SOFTWARE\\Microsoft\\Windows\\CurrentVersion",
-                     parentKey.ACCESS_WRITE);
-      uninstallKey = parentKey.createChild("Uninstall", parentKey.ACCESS_WRITE)
-      subKey = uninstallKey.createChild(this.uninstallSubkeyStr,
-                                        uninstallKey.ACCESS_WRITE);
-
-      subKey.writeStringValue("DisplayName", this.appName);
-
-      let uninstallerPath = OS.Path.join(aInstallDir, this.uninstallerFile);
-
-      subKey.writeStringValue("UninstallString", '"' + uninstallerPath + '"');
-      subKey.writeStringValue("InstallLocation", '"' + aInstallDir + '"');
-      subKey.writeStringValue("AppFilename", this.appNameAsFilename);
-      subKey.writeStringValue("DisplayIcon", OS.Path.join(aInstallDir,
-                                                          this.iconPath));
-
-      let date = new Date();
-      let year = date.getYear().toString();
-      let month = date.getMonth();
-      if (month < 10) {
-        month = "0" + month;
-      }
-      let day = date.getDate();
-      if (day < 10) {
-        day = "0" + day;
-      }
-      subKey.writeStringValue("InstallDate", year + month + day);
-      if (this.version) {
-        subKey.writeStringValue("DisplayVersion", this.version);
-      }
-      if (this.developerName) {
-        subKey.writeStringValue("Publisher", this.developerName);
-      }
-      subKey.writeStringValue("URLInfoAbout", this.developerUrl);
-      if (this.size) {
-        subKey.writeIntValue("EstimatedSize", this.size);
-      }
-
-      subKey.writeIntValue("NoModify", 1);
-      subKey.writeIntValue("NoRepair", 1);
-    } catch(ex) {
-      throw ex;
-    } finally {
-      if(subKey) subKey.close();
-      if(uninstallKey) uninstallKey.close();
-      if(parentKey) parentKey.close();
-    }
-  },
-
-  /**
-   * Creates a shortcut file inside the app installation folder and makes
-   * two copies of it: one into the desktop and one into the start menu.
-   */
-  _createShortcutFiles: function(aInstallDir) {
-    let shortcut = getFile(aInstallDir, this.shortcutName).
-                      QueryInterface(Ci.nsILocalFileWin);
-
-    /* function nsILocalFileWin.setShortcut(targetFile, workingDir, args,
-                                            description, iconFile, iconIndex) */
-
-    shortcut.setShortcut(getFile(aInstallDir, this.webapprt),
-                         getFile(aInstallDir),
-                         null,
-                         this.shortDescription,
-                         getFile(aInstallDir, this.iconPath),
-                         0);
-
-    shortcut.copyTo(getFile(DESKTOP_DIR), this.shortcutName);
-    shortcut.copyTo(getFile(PROGS_DIR), this.shortcutName);
-
-    shortcut.followLinks = false;
-    shortcut.remove(false);
-  },
-
-  /**
-   * Process the icon from the imageStream as retrieved from
-   * the URL by getIconForApp(). This will save the icon to the
-   * topwindow.ico file.
-   *
-   * @param aMimeType     the icon mimetype
-   * @param aImageStream  the stream for the image data
-   * @param aDir          the directory where the icon should be stored
-   */
-  _processIcon: function(aMimeType, aImageStream, aDir) {
-    let deferred = Promise.defer();
-
-    let imgTools = Cc["@mozilla.org/image/tools;1"].
-                   createInstance(Ci.imgITools);
-
-    let imgContainer = imgTools.decodeImage(aImageStream, aMimeType);
-    let iconStream = imgTools.encodeImage(imgContainer,
-                                          "image/vnd.microsoft.icon",
-                                          "format=bmp;bpp=32");
-
-    let tmpIconFile = getFile(aDir, this.iconPath);
-
-    let outputStream = FileUtils.openSafeFileOutputStream(tmpIconFile);
-    NetUtil.asyncCopy(iconStream, outputStream, function(aResult) {
-      if (Components.isSuccessCode(aResult)) {
-        deferred.resolve();
-      } else {
-        deferred.reject("Failure copying icon: " + aResult);
-      }
-    });
-
-    return deferred.promise;
-  }
-}
--- a/toolkit/webapps/moz.build
+++ b/toolkit/webapps/moz.build
@@ -1,15 +1,13 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # 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_PP_JS_MODULES += [
-    'NativeApp.jsm',
     'WebappOSUtils.jsm',
+    'WebappsInstaller.jsm',
 ]
 
-MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
-
 if CONFIG['MOZ_BUILD_APP'] == 'mobile/android':
     DEFINES['MOZ_FENNEC'] = True
deleted file mode 100644
--- a/toolkit/webapps/tests/chrome.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[DEFAULT]
-support-files =
-  head.js
-
-[test_hosted.xul]
-skip-if = os == "mac"
-[test_packaged.xul]
-skip-if = os == "mac"
deleted file mode 100644
--- a/toolkit/webapps/tests/head.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/osfile.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-
-function checkFiles(files) {
-  return Task.spawn(function*() {
-    for (let file of files) {
-      if (!(yield OS.File.exists(file))) {
-        info("File doesn't exist: " + file);
-        return false;
-      }
-    }
-
-    return true;
-  });
-}
-
-function checkDateHigherThan(files, date) {
-  return Task.spawn(function*() {
-    for (let file of files) {
-      if (!(yield OS.File.exists(file))) {
-        info("File doesn't exist: " + file);
-        return false;
-      }
-
-      let stat = yield OS.File.stat(file);
-      if (!(stat.lastModificationDate > date)) {
-        info("File not newer: " + file);
-        return false;
-      }
-    }
-
-    return true;
-  });
-}
-
-function wait(time) {
-  let deferred = Promise.defer();
-
-  setTimeout(function() {
-    deferred.resolve();
-  }, time);
-
-  return deferred.promise;
-}
deleted file mode 100644
--- a/toolkit/webapps/tests/test_hosted.xul
+++ /dev/null
@@ -1,311 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
-<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=898647
--->
-<window title="Mozilla Bug 898647"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript" src="head.js"/>
-
-  <!-- test results are displayed in the html:body -->
-  <body xmlns="http://www.w3.org/1999/xhtml">
-  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=898647"
-     target="_blank">Mozilla Bug 898647</a>
-  </body>
-
-<script type="application/javascript">
-<![CDATA[
-
-/** Test for Bug 898647 **/
-
-"use strict";
-
-SimpleTest.waitForExplicitFinish();
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/NativeApp.jsm");
-Cu.import("resource://gre/modules/WebappOSUtils.jsm");
-
-let manifest = {
-  name: "Sample hosted app",
-};
-
-let app = {
-  name: "Sample hosted app",
-  manifestURL: "http://example.com/sample.manifest",
-  manifest: manifest,
-  origin: "http://example.com/",
-  categories: [],
-  installOrigin: "http://example.com/",
-  receipts: [],
-  installTime: Date.now(),
-};
-
-let profileDir;
-let profilesIni;
-let installPath;
-
-let installedFiles;
-let tempUpdatedFiles;
-let updatedFiles;
-
-let cleanup;
-
-if (navigator.platform.startsWith("Linux")) {
-  installPath = OS.Path.join(OS.Constants.Path.homeDir, "." + WebappOSUtils.getUniqueName(app));
-
-  let xdg_data_home = Cc["@mozilla.org/process/environment;1"].
-                      getService(Ci.nsIEnvironment).
-                      get("XDG_DATA_HOME");
-  if (!xdg_data_home) {
-    xdg_data_home = OS.Path.join(OS.Constants.Path.homeDir, ".local", "share");
-  }
-
-  let desktopINI = OS.Path.join(xdg_data_home, "applications",
-                                "owa-" + WebappOSUtils.getUniqueName(app) + ".desktop");
-
-  installedFiles = [
-    OS.Path.join(installPath, "icon.png"),
-    OS.Path.join(installPath, "webapprt-stub"),
-    OS.Path.join(installPath, "webapp.json"),
-    OS.Path.join(installPath, "webapp.ini"),
-    desktopINI,
-  ];
-  tempUpdatedFiles = [
-    OS.Path.join(installPath, "update", "icon.png"),
-    OS.Path.join(installPath, "update", "webapp.json"),
-    OS.Path.join(installPath, "update", "webapp.ini"),
-  ];
-  updatedFiles = [
-    OS.Path.join(installPath, "icon.png"),
-    OS.Path.join(installPath, "webapp.json"),
-    OS.Path.join(installPath, "webapp.ini"),
-    desktopINI,
-  ];
-
-  profilesIni = OS.Path.join(installPath, "profiles.ini");
-
-  cleanup = function() {
-    return Task.spawn(function*() {
-      if (profileDir) {
-        yield OS.File.removeDir(profileDir.parent.path, { ignoreAbsent: true });
-      }
-
-      yield OS.File.removeDir(installPath, { ignoreAbsent: true });
-
-      yield OS.File.remove(desktopINI, { ignoreAbsent: true });
-    });
-  };
-} else if (navigator.platform.startsWith("Win")) {
-  installPath = OS.Path.join(OS.Constants.Path.winAppDataDir, WebappOSUtils.getUniqueName(app));
-
-  let desktopShortcut = OS.Path.join(OS.Constants.Path.desktopDir, "Sample hosted app.lnk");
-  let startMenuShortcut = OS.Path.join(OS.Constants.Path.winStartMenuProgsDir, "Sample hosted app.lnk");
-
-  installedFiles = [
-    OS.Path.join(installPath, "Sample hosted app.exe"),
-    OS.Path.join(installPath, "chrome", "icons", "default", "default.ico"),
-    OS.Path.join(installPath, "webapp.json"),
-    OS.Path.join(installPath, "webapp.ini"),
-    OS.Path.join(installPath, "uninstall", "shortcuts_log.ini"),
-    OS.Path.join(installPath, "uninstall", "uninstall.log"),
-    OS.Path.join(installPath, "uninstall", "webapp-uninstaller.exe"),
-    desktopShortcut,
-    startMenuShortcut,
-  ];
-  tempUpdatedFiles = [
-    OS.Path.join(installPath, "update", "chrome", "icons", "default", "default.ico"),
-    OS.Path.join(installPath, "update", "webapp.json"),
-    OS.Path.join(installPath, "update", "webapp.ini"),
-    OS.Path.join(installPath, "update", "uninstall", "shortcuts_log.ini"),
-    OS.Path.join(installPath, "update", "uninstall", "uninstall.log"),
-    OS.Path.join(installPath, "update", "uninstall", "webapp-uninstaller.exe"),
-  ];
-  updatedFiles = [
-    OS.Path.join(installPath, "chrome", "icons", "default", "default.ico"),
-    OS.Path.join(installPath, "webapp.json"),
-    OS.Path.join(installPath, "webapp.ini"),
-    OS.Path.join(installPath, "uninstall", "shortcuts_log.ini"),
-    OS.Path.join(installPath, "uninstall", "uninstall.log"),
-    desktopShortcut,
-    startMenuShortcut,
-  ];
-
-  profilesIni = OS.Path.join(installPath, "profiles.ini");
-
-  cleanup = function() {
-    return Task.spawn(function*() {
-      let uninstallKey;
-      try {
-        uninstallKey = Cc["@mozilla.org/windows-registry-key;1"].
-                       createInstance(Ci.nsIWindowsRegKey);
-        uninstallKey.open(uninstallKey.ROOT_KEY_CURRENT_USER,
-                          "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
-                          uninstallKey.ACCESS_WRITE);
-        if (uninstallKey.hasChild(WebappOSUtils.getUniqueName(app))) {
-          uninstallKey.removeChild(WebappOSUtils.getUniqueName(app));
-        }
-      } catch (e) {
-      } finally {
-        if (uninstallKey) {
-          uninstallKey.close();
-        }
-      }
-
-      if (profileDir) {
-        yield OS.File.removeDir(profileDir.parent.parent.path, { ignoreAbsent: true });
-      }
-
-      yield OS.File.removeDir(installPath, { ignoreAbsent: true });
-
-      yield OS.File.remove(desktopShortcut, { ignoreAbsent: true });
-      yield OS.File.remove(startMenuShortcut, { ignoreAbsent: true });
-    });
-  };
-} else if (navigator.platform.startsWith("Mac")) {
-  installPath = OS.Path.join(OS.Constants.Path.macLocalApplicationsDir, "Sample hosted app.app");
-  let appProfileDir = OS.Path.join(OS.Constants.Path.macUserLibDir, "Application Support",
-                                   WebappOSUtils.getUniqueName(app));
-
-  installedFiles = [
-    OS.Path.join(installPath, "Contents", "Info.plist"),
-    OS.Path.join(installPath, "Contents", "MacOS", "webapprt"),
-    OS.Path.join(installPath, "Contents", "MacOS", "webapp.ini"),
-    OS.Path.join(installPath, "Contents", "Resources", "appicon.icns"),
-    OS.Path.join(appProfileDir, "webapp.json"),
-  ];
-  tempUpdatedFiles = [
-    OS.Path.join(installPath, "update", "Contents", "Info.plist"),
-    OS.Path.join(installPath, "update", "Contents", "MacOS", "webapp.ini"),
-    OS.Path.join(installPath, "update", "Contents", "Resources", "appicon.icns"),
-    OS.Path.join(installPath, "update", "webapp.json")
-  ];
-  updatedFiles = [
-    OS.Path.join(installPath, "Contents", "Info.plist"),
-    OS.Path.join(installPath, "Contents", "MacOS", "webapp.ini"),
-    OS.Path.join(installPath, "Contents", "Resources", "appicon.icns"),
-    OS.Path.join(appProfileDir, "webapp.json"),
-  ];
-
-  profilesIni = OS.Path.join(appProfileDir, "profiles.ini");
-
-  cleanup = function() {
-    return Task.spawn(function*() {
-      if (profileDir) {
-        yield OS.File.removeDir(profileDir.parent.path, { ignoreAbsent: true });
-      }
-
-      yield OS.File.removeDir(installPath, { ignoreAbsent: true });
-
-      yield OS.File.removeDir(appProfileDir, { ignoreAbsent: true });
-    });
-  };
-}
-
-let old_dry_run;
-try {
-  old_dry_run = Services.prefs.getBoolPref("browser.mozApps.installer.dry_run");
-} catch (ex) {}
-
-Services.prefs.setBoolPref("browser.mozApps.installer.dry_run", false);
-
-SimpleTest.registerCleanupFunction(function() {
-  if (old_dry_run === undefined) {
-    Services.prefs.clearUserPref("browser.mozApps.installer.dry_run");
-  } else {
-    Services.prefs.setBoolPref("browser.mozApps.installer.dry_run", old_dry_run);
-  }
-
-  cleanup();
-});
-
-Task.spawn(function() {
-  // Get to a clean state before the test
-  yield cleanup();
-
-  let nativeApp = new NativeApp(app, manifest, app.categories);
-  ok(nativeApp, "NativeApp object created");
-
-  info("Test update for an uninstalled application");
-  try {
-    yield nativeApp.prepareUpdate(manifest);
-    ok(false, "Didn't thrown");
-  } catch (ex) {
-    is(ex, "The application isn't installed", "Exception thrown");
-  }
-
-  profileDir = nativeApp.createProfile();
-  ok(profileDir && profileDir.exists(), "Profile directory created");
-  ok((yield OS.File.exists(profilesIni)), "profiles.ini file created");
-
-  // Install application
-  info("Test installation");
-  yield nativeApp.install(manifest);
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
-  }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Files correctly written");
-
-  let stat = yield OS.File.stat(installPath);
-  let installTime = stat.lastModificationDate;
-
-  // Wait one second, otherwise the last modification date is the same.
-  yield wait(1000);
-
-  // Reinstall application
-  info("Test reinstallation");
-  yield nativeApp.install(manifest);
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
-  }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Installation not corrupted");
-  ok((yield checkFiles(tempUpdatedFiles)), "Files correctly written in the update subdirectory");
-
-  yield nativeApp.applyUpdate();
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
-  }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Installation not corrupted");
-  ok(!(yield OS.File.exists(OS.Path.join(installPath, "update"))), "Update directory removed");
-  ok((yield checkDateHigherThan(updatedFiles, installTime)), "Modification date higher");
-
-  stat = yield OS.File.stat(installPath);
-  installTime = stat.lastModificationDate;
-
-  // Wait one second, otherwise the last modification date is the same.
-  yield wait(1000);
-
-  // Update application
-  info("Test update");
-  yield nativeApp.prepareUpdate(manifest);
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
-  }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Installation not corrupted");
-  ok((yield checkFiles(tempUpdatedFiles)), "Files correctly written in the update subdirectory");
-
-  yield nativeApp.applyUpdate();
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
-  }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Installation not corrupted");
-  ok(!(yield OS.File.exists(OS.Path.join(installPath, "update"))), "Update directory removed");
-  ok((yield checkDateHigherThan(updatedFiles, installTime)), "Modification date higher");
-
-  SimpleTest.finish();
-}).then(null, function(e) {
-  ok(false, "Error during test: " + e);
-  SimpleTest.finish();
-});
-
-]]>
-</script>
-</window>
deleted file mode 100644
--- a/toolkit/webapps/tests/test_packaged.xul
+++ /dev/null
@@ -1,337 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
-<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=898647
--->
-<window title="Mozilla Bug 898647"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript" src="head.js"/>
-
-  <!-- test results are displayed in the html:body -->
-  <body xmlns="http://www.w3.org/1999/xhtml">
-  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=898647"
-     target="_blank">Mozilla Bug 898647</a>
-  </body>
-
-<script type="application/javascript">
-<![CDATA[
-
-/** Test for Bug 898647 **/
-
-"use strict";
-
-SimpleTest.waitForExplicitFinish();
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/NativeApp.jsm");
-Cu.import("resource://gre/modules/WebappOSUtils.jsm");
-
-let zipPath = OS.Path.join(OS.Constants.Path.profileDir, "sample.zip");
-
-let manifest = {
-  name: "Sample packaged app",
-  version: "0.1a",
-  size: 777,
-  package_path: "/sample.zip",
-};
-
-let app = {
-  name: "Sample packaged app",
-  manifestURL: "http://example.com/sample.manifest",
-  manifest: manifest,
-  updateManifest: manifest,
-  origin: "http://example.com/",
-  categories: [],
-  installOrigin: "http://example.com/",
-  receipts: [],
-  installTime: Date.now(),
-};
-
-let profileDir;
-let profilesIni;
-let installPath;
-
-let installedFiles;
-let tempUpdatedFiles;
-let updatedFiles;
-
-let cleanup;
-
-if (navigator.platform.startsWith("Linux")) {
-  installPath = OS.Path.join(OS.Constants.Path.homeDir, "." + WebappOSUtils.getUniqueName(app));
-
-  let xdg_data_home = Cc["@mozilla.org/process/environment;1"].
-                      getService(Ci.nsIEnvironment).
-                      get("XDG_DATA_HOME");
-  if (!xdg_data_home) {
-    xdg_data_home = OS.Path.join(OS.Constants.Path.homeDir, ".local", "share");
-  }
-
-  let desktopINI = OS.Path.join(xdg_data_home, "applications",
-                                "owa-" + WebappOSUtils.getUniqueName(app) + ".desktop");
-
-  installedFiles = [
-    OS.Path.join(installPath, "icon.png"),
-    OS.Path.join(installPath, "webapprt-stub"),
-    OS.Path.join(installPath, "webapp.json"),
-    OS.Path.join(installPath, "webapp.ini"),
-    OS.Path.join(installPath, "application.zip"),
-    desktopINI,
-  ];
-  tempUpdatedFiles = [
-    OS.Path.join(installPath, "update", "icon.png"),
-    OS.Path.join(installPath, "update", "webapp.json"),
-    OS.Path.join(installPath, "update", "webapp.ini"),
-    OS.Path.join(installPath, "update", "application.zip"),
-  ];
-  updatedFiles = [
-    OS.Path.join(installPath, "icon.png"),
-    OS.Path.join(installPath, "webapp.json"),
-    OS.Path.join(installPath, "webapp.ini"),
-    OS.Path.join(installPath, "application.zip"),
-    desktopINI,
-  ];
-
-  profilesIni = OS.Path.join(installPath, "profiles.ini");
-
-  cleanup = function() {
-    return Task.spawn(function*() {
-      if (profileDir) {
-        yield OS.File.removeDir(profileDir.parent.path, { ignoreAbsent: true });
-      }
-
-      yield OS.File.removeDir(installPath, { ignoreAbsent: true });
-
-      yield OS.File.remove(desktopINI, { ignoreAbsent: true });
-    });
-  };
-} else if (navigator.platform.startsWith("Win")) {
-  installPath = OS.Path.join(OS.Constants.Path.winAppDataDir, WebappOSUtils.getUniqueName(app));
-
-  let desktopShortcut = OS.Path.join(OS.Constants.Path.desktopDir, "Sample packaged app.lnk");
-  let startMenuShortcut = OS.Path.join(OS.Constants.Path.winStartMenuProgsDir, "Sample packaged app.lnk");
-
-  installedFiles = [
-    OS.Path.join(installPath, "Sample packaged app.exe"),
-    OS.Path.join(installPath, "chrome", "icons", "default", "default.ico"),
-    OS.Path.join(installPath, "webapp.json"),
-    OS.Path.join(installPath, "webapp.ini"),
-    OS.Path.join(installPath, "application.zip"),
-    OS.Path.join(installPath, "uninstall", "shortcuts_log.ini"),
-    OS.Path.join(installPath, "uninstall", "uninstall.log"),
-    OS.Path.join(installPath, "uninstall", "webapp-uninstaller.exe"),
-    desktopShortcut,
-    startMenuShortcut,
-  ];
-  tempUpdatedFiles = [
-    OS.Path.join(installPath, "update", "chrome", "icons", "default", "default.ico"),
-    OS.Path.join(installPath, "update", "webapp.json"),
-    OS.Path.join(installPath, "update", "webapp.ini"),
-    OS.Path.join(installPath, "update", "application.zip"),
-    OS.Path.join(installPath, "update", "uninstall", "shortcuts_log.ini"),
-    OS.Path.join(installPath, "update", "uninstall", "uninstall.log"),
-    OS.Path.join(installPath, "update", "uninstall", "webapp-uninstaller.exe"),
-  ];
-  updatedFiles = [
-    OS.Path.join(installPath, "chrome", "icons", "default", "default.ico"),
-    OS.Path.join(installPath, "webapp.json"),
-    OS.Path.join(installPath, "webapp.ini"),
-    OS.Path.join(installPath, "application.zip"),
-    OS.Path.join(installPath, "uninstall", "shortcuts_log.ini"),
-    OS.Path.join(installPath, "uninstall", "uninstall.log"),
-    desktopShortcut,
-    startMenuShortcut,
-  ];
-
-  profilesIni = OS.Path.join(installPath, "profiles.ini");
-
-  cleanup = function() {
-    return Task.spawn(function*() {
-      let uninstallKey;
-      try {
-        uninstallKey = Cc["@mozilla.org/windows-registry-key;1"].
-                       createInstance(Ci.nsIWindowsRegKey);
-        uninstallKey.open(uninstallKey.ROOT_KEY_CURRENT_USER,
-                          "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
-                          uninstallKey.ACCESS_WRITE);
-        if (uninstallKey.hasChild(WebappOSUtils.getUniqueName(app))) {
-          uninstallKey.removeChild(WebappOSUtils.getUniqueName(app));
-        }
-      } catch (e) {
-      } finally {
-        if (uninstallKey) {
-          uninstallKey.close();
-        }
-      }
-
-      if (profileDir) {
-        yield OS.File.removeDir(profileDir.parent.parent.path, { ignoreAbsent: true });
-      }
-
-      yield OS.File.removeDir(installPath, { ignoreAbsent: true });
-
-      yield OS.File.remove(desktopShortcut, { ignoreAbsent: true });
-      yield OS.File.remove(startMenuShortcut, { ignoreAbsent: true });
-    });
-  };
-} else if (navigator.platform.startsWith("Mac")) {
-  installPath = OS.Path.join(OS.Constants.Path.macLocalApplicationsDir, "Sample packaged app.app");
-  let appProfileDir = OS.Path.join(OS.Constants.Path.macUserLibDir, "Application Support",
-                                   WebappOSUtils.getUniqueName(app));
-
-  installedFiles = [
-    OS.Path.join(installPath, "Contents", "Info.plist"),
-    OS.Path.join(installPath, "Contents", "MacOS", "webapprt"),
-    OS.Path.join(installPath, "Contents", "MacOS", "webapp.ini"),
-    OS.Path.join(installPath, "Contents", "Resources", "appicon.icns"),
-    OS.Path.join(installPath, "Contents", "Resources", "application.zip"),
-    OS.Path.join(appProfileDir, "webapp.json"),
-  ];
-  tempUpdatedFiles = [
-    OS.Path.join(installPath, "update", "Contents", "Info.plist"),
-    OS.Path.join(installPath, "update", "Contents", "MacOS", "webapp.ini"),
-    OS.Path.join(installPath, "update", "Contents", "Resources", "appicon.icns"),
-    OS.Path.join(installPath, "update", "Contents", "Resources", "application.zip"),
-    OS.Path.join(installPath, "update", "webapp.json"),
-  ];
-  updatedFiles = [
-    OS.Path.join(installPath, "Contents", "Info.plist"),
-    OS.Path.join(installPath, "Contents", "MacOS", "webapp.ini"),
-    OS.Path.join(installPath, "Contents", "Resources", "appicon.icns"),
-    OS.Path.join(installPath, "Contents", "Resources", "application.zip"),
-    OS.Path.join(appProfileDir, "webapp.json"),
-  ];
-
-  profilesIni = OS.Path.join(appProfileDir, "profiles.ini");
-
-  cleanup = function() {
-    return Task.spawn(function*() {
-      if (profileDir) {
-        yield OS.File.removeDir(profileDir.parent.path, { ignoreAbsent: true });
-      }
-
-      yield OS.File.removeDir(installPath, { ignoreAbsent: true });
-
-      yield OS.File.removeDir(appProfileDir, { ignoreAbsent: true });
-    });
-  };
-}
-
-let old_dry_run;
-try {
-  old_dry_run = Services.prefs.getBoolPref("browser.mozApps.installer.dry_run");
-} catch (ex) {}
-
-Services.prefs.setBoolPref("browser.mozApps.installer.dry_run", false);
-
-SimpleTest.registerCleanupFunction(function() {
-  if (old_dry_run === undefined) {
-    Services.prefs.clearUserPref("browser.mozApps.installer.dry_run");
-  } else {
-    Services.prefs.setBoolPref("browser.mozApps.installer.dry_run", old_dry_run);
-  }
-
-  cleanup();
-});
-
-Task.spawn(function() {
-  // Get to a clean state before the test
-  yield cleanup();
-
-  let zipFile = yield OS.File.open(zipPath, { create: true });
-  yield zipFile.close();
-
-  let nativeApp = new NativeApp(app, manifest, app.categories);
-  ok(nativeApp, "NativeApp object created");
-
-  info("Test update for an application that isn't installed");
-  try {
-    yield nativeApp.prepareUpdate(manifest, zipPath);
-    ok(false, "Didn't thrown");
-  } catch (ex) {
-    is(ex, "The application isn't installed", "Exception thrown");
-  }
-
-  profileDir = nativeApp.createProfile();
-  ok(profileDir && profileDir.exists(), "Profile directory created");
-  ok((yield OS.File.exists(profilesIni)), "profiles.ini file created");
-
-  // Install application
-  info("Test installation");
-  yield nativeApp.install(manifest, zipPath);
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
-  }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Files correctly written");
-
-  let stat = yield OS.File.stat(installPath);
-  let installTime = stat.lastModificationDate;
-
-  // Wait one second, otherwise the last modification date is the same.
-  yield wait(1000);
-
-  // Reinstall application
-  info("Test reinstallation");
-
-  zipFile = yield OS.File.open(zipPath, { create: true });
-  yield zipFile.close();
-
-  yield nativeApp.install(manifest, zipPath);
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
-  }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Installation not corrupted");
-  ok((yield checkFiles(tempUpdatedFiles)), "Files correctly written in the update subdirectory");
-
-  yield nativeApp.applyUpdate();
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
-  }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Installation not corrupted");
-  ok(!(yield OS.File.exists(OS.Path.join(installPath, "update"))), "Update directory removed");
-  ok((yield checkDateHigherThan(updatedFiles, installTime)), "Modification date higher");
-
-  stat = yield OS.File.stat(installPath);
-  installTime = stat.lastModificationDate;
-
-  // Wait one second, otherwise the last modification date is the same.
-  yield wait(1000);
-
-  // Update application
-  info("Test update");
-
-  zipFile = yield OS.File.open(zipPath, { create: true });
-  yield zipFile.close();
-
-  yield nativeApp.prepareUpdate(manifest, zipPath);
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
-  }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Installation not corrupted");
-  ok((yield checkFiles(tempUpdatedFiles)), "Files correctly written in the update subdirectory");
-
-  yield nativeApp.applyUpdate();
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
-  }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Installation not corrupted");
-  ok(!(yield OS.File.exists(OS.Path.join(installPath, "update"))), "Update directory removed");
-  ok((yield checkDateHigherThan(updatedFiles, installTime)), "Modification date higher");
-
-  SimpleTest.finish();
-}).then(null, function(e) {
-  ok(false, "Error during test: " + e);
-  SimpleTest.finish();
-});
-
-]]>
-</script>
-</window>
--- a/webapprt/Startup.jsm
+++ b/webapprt/Startup.jsm
@@ -8,23 +8,27 @@
  * loaded.  */
 
 this.EXPORTED_SYMBOLS = ["startup"];
 
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
+// Initialize DOMApplicationRegistry by importing Webapps.jsm.
+Cu.import("resource://gre/modules/Webapps.jsm");
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import("resource://gre/modules/PermissionsInstaller.jsm");
 Cu.import('resource://gre/modules/Payment.jsm');
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 
+// Initialize window-independent handling of webapps- notifications.
+Cu.import("resource://webapprt/modules/WebappsHandler.jsm");
 Cu.import("resource://webapprt/modules/WebappRT.jsm");
 Cu.import("resource://webapprt/modules/WebRTCHandler.jsm");
 
 const PROFILE_DIR = OS.Constants.Path.profileDir;
 
 function isFirstRunOrUpdate() {
   let savedBuildID = null;
   try {
@@ -84,37 +88,25 @@ this.startup = function(window) {
       deferredWindowLoad.resolve();
     } else {
       window.addEventListener("DOMContentLoaded", function onLoad() {
         window.removeEventListener("DOMContentLoaded", onLoad, false);
         deferredWindowLoad.resolve();
       });
     }
 
-    let appUpdated = false;
-    let updatePending = yield WebappRT.isUpdatePending();
-    if (updatePending) {
-      appUpdated = yield WebappRT.applyUpdate();
-    }
-
-    yield WebappRT.loadConfig();
-
-    // Initialize DOMApplicationRegistry by importing Webapps.jsm.
-    Cu.import("resource://gre/modules/Webapps.jsm");
-    // Initialize window-independent handling of webapps- notifications.
-    Cu.import("resource://webapprt/modules/WebappManager.jsm");
-
     // Wait for webapps registry loading.
     yield DOMApplicationRegistry.registryStarted;
 
     let manifestURL = WebappRT.config.app.manifestURL;
     if (manifestURL) {
       // On firstrun, set permissions to their default values.
       // When the webapp runtime is updated, update the permissions.
-      if (isFirstRunOrUpdate(Services.prefs) || appUpdated) {
+      // TODO: Update the permissions when the application is updated.
+      if (isFirstRunOrUpdate(Services.prefs)) {
         PermissionsInstaller.installPermissions(WebappRT.config.app, true);
         yield createBrandingFiles();
       }
     }
 
     // Branding substitution
     let aliasFile = Components.classes["@mozilla.org/file/local;1"]
                               .createInstance(Ci.nsIFile);
@@ -138,12 +130,10 @@ this.startup = function(window) {
 
     if (WebappRT.config.app.manifest.fullscreen) {
       appBrowser.addEventListener("load", function onLoad() {
         appBrowser.removeEventListener("load", onLoad, true);
         appBrowser.contentDocument.
           documentElement.mozRequestFullScreen();
       }, true);
     }
-
-    WebappRT.startUpdateService();
   }).then(null, Cu.reportError.bind(Cu));
 }
--- a/webapprt/WebappRT.jsm
+++ b/webapprt/WebappRT.jsm
@@ -10,30 +10,48 @@ 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.defineLazyModuleGetter(this, "FileUtils",
   "resource://gre/modules/FileUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "OS",
-  "resource://gre/modules/osfile.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
-  "resource://gre/modules/Task.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, 'NativeApp',
-  'resource://gre/modules/NativeApp.jsm');
-
 XPCOMUtils.defineLazyServiceGetter(this, "appsService",
                                   "@mozilla.org/AppsService;1",
                                   "nsIAppsService");
 
 this.WebappRT = {
+  _config: null,
+
+  get config() {
+    if (this._config)
+      return this._config;
+
+    let webappFile = FileUtils.getFile("AppRegD", ["webapp.json"]);
+
+    let inputStream = Cc["@mozilla.org/network/file-input-stream;1"].
+                      createInstance(Ci.nsIFileInputStream);
+    inputStream.init(webappFile, -1, 0, Ci.nsIFileInputStream.CLOSE_ON_EOF);
+    let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
+    let config = json.decodeFromStream(inputStream, webappFile.fileSize);
+
+    return this._config = config;
+  },
+
+  // This exists to support test mode, which installs webapps after startup.
+  // Ideally we wouldn't have to have a setter, as tests can just delete
+  // the getter and then set the property.  But the object to which they set it
+  // 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 = this.localeManifest;
     return manifest.fullLaunchPath();
   },
 
   get localeManifest() {
     return new ManifestHelper(this.config.app.manifest,
                               this.config.app.origin);
@@ -42,98 +60,9 @@ this.WebappRT = {
   get appID() {
     let manifestURL = WebappRT.config.app.manifestURL;
     if (!manifestURL) {
       return Ci.nsIScriptSecurityManager.NO_APP_ID;
     }
 
     return appsService.getAppLocalIdByManifestURL(manifestURL);
   },
-
-  loadConfig: function() {
-    if (this.config) {
-      return;
-    }
-
-    let webappJson = OS.Path.join(Services.dirsvc.get("AppRegD", Ci.nsIFile).path,
-                                  "webapp.json");
-    this.config = yield AppsUtils.loadJSONAsync(webappJson);
-  },
-
-  isUpdatePending: Task.async(function*() {
-    let webappJson = OS.Path.join(Services.dirsvc.get("AppRegD", Ci.nsIFile).path,
-                                  "update", "webapp.json");
-
-    if (!(yield OS.File.exists(webappJson))) {
-      return false;
-    }
-
-    return true;
-  }),
-
-  applyUpdate: Task.async(function*() {
-    let webappJson = OS.Path.join(Services.dirsvc.get("AppRegD", Ci.nsIFile).path,
-                                  "update", "webapp.json");
-    let config = yield AppsUtils.loadJSONAsync(webappJson);
-
-    let nativeApp = new NativeApp(config.app, config.app.manifest,
-                                  config.app.categories,
-                                  config.registryDir);
-    try {
-      yield nativeApp.applyUpdate();
-    } catch (ex) {
-      return false;
-    }
-
-    // The update has been applied successfully, the new config file
-    // is the config file that was in the update directory.
-    this.config = config;
-
-    return true;
-  }),
-
-  startUpdateService: function() {
-    let manifestURL = WebappRT.config.app.manifestURL;
-    // We used to install apps without storing their manifest URL.
-    // Now we can't update them.
-    if (!manifestURL) {
-      return;
-    }
-
-    // Check for updates once a day.
-    let timerManager = Cc["@mozilla.org/updates/timer-manager;1"].
-                       getService(Ci.nsIUpdateTimerManager);
-    timerManager.registerTimer("updateTimer", () => {
-      let window = Services.wm.getMostRecentWindow("webapprt:webapp");
-      window.navigator.mozApps.mgmt.getAll().onsuccess = function() {
-        let thisApp = null;
-        for (let app of this.result) {
-          if (app.manifestURL == manifestURL) {
-            thisApp = app;
-            break;
-          }
-        }
-
-        // This shouldn't happen if the app is installed.
-        if (!thisApp) {
-          Cu.reportError("Couldn't find the app in the webapps registry");
-          return;
-        }
-
-        thisApp.ondownloadavailable = () => {
-          // Download available, download it!
-          thisApp.download();
-        };
-
-        thisApp.ondownloadsuccess = () => {
-          // Update downloaded, apply it!
-          window.navigator.mozApps.mgmt.applyDownload(thisApp);
-        };
-
-        thisApp.ondownloadapplied = () => {
-          // Application updated, nothing to do.
-        };
-
-        thisApp.checkForUpdate();
-      }
-    }, 24 * 60 * 60);
-  },
 };
rename from webapprt/WebappManager.jsm
rename to webapprt/WebappsHandler.jsm
--- a/webapprt/WebappManager.jsm
+++ b/webapprt/WebappsHandler.jsm
@@ -1,29 +1,28 @@
 /* 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/. */
 
 "use strict";
 
-this.EXPORTED_SYMBOLS = ["WebappManager"];
+this.EXPORTED_SYMBOLS = ["WebappsHandler"];
 
 let Cc = Components.classes;
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 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/NativeApp.jsm");
+Cu.import("resource://gre/modules/WebappsInstaller.jsm");
 Cu.import("resource://gre/modules/WebappOSUtils.jsm");
-Cu.import("resource://webapprt/modules/WebappRT.jsm");
 
-this.WebappManager = {
+this.WebappsHandler = {
   observe: function(subject, topic, data) {
     data = JSON.parse(data);
     data.mm = subject;
 
     switch (topic) {
       case "webapps-ask-install":
         let chromeWin = Services.wm.getOuterWindowWithId(data.oid);
         if (chromeWin)
@@ -33,23 +32,16 @@ this.WebappManager = {
         WebappOSUtils.launch(data);
         break;
       case "webapps-uninstall":
         WebappOSUtils.uninstall(data);
         break;
     }
   },
 
-  update: function(aApp, aManifest, aZipPath) {
-    let nativeApp = new NativeApp(aApp, aManifest,
-                                  WebappRT.config.app.categories,
-                                  WebappRT.config.registryDir);
-    nativeApp.prepareUpdate(aManifest, aZipPath);
-  },
-
   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 bundle = Services.strings.createBundle("chrome://webapprt/locale/webapp.properties");
 
     let choice = Services.prompt.confirmEx(
       window,
@@ -62,39 +54,36 @@ this.WebappManager = {
       bundle.GetStringFromName("webapps.install.install"),
       bundle.GetStringFromName("webapps.install.dontinstall"),
       null,
       null,
       {});
 
     // Perform the install if the user allows it
     if (choice == 0) {
-      let nativeApp = new NativeApp(data.app, jsonManifest,
-                                    WebappRT.config.app.categories,
-                                    WebappRT.config.registryDir);
-      let localDir;
-      try {
-        localDir = nativeApp.createProfile();
-      } catch (ex) {
-        DOMApplicationRegistry.denyInstall(aData);
-        return;
+      let shell = WebappsInstaller.init(data);
+
+      if (shell) {
+        let localDir = null;
+        if (shell.appProfile) {
+          localDir = shell.appProfile.localDir;
+        }
+
+        DOMApplicationRegistry.confirmInstall(data, localDir,
+          function (aManifest) {
+            WebappsInstaller.install(data, aManifest);
+          }
+        );
+      } else {
+        DOMApplicationRegistry.denyInstall(data);
       }
-
-      DOMApplicationRegistry.confirmInstall(data, localDir,
-        function (aManifest, aZipPath) {
-          nativeApp.install(aManifest, aZipPath);
-        }
-      );
     } else {
       DOMApplicationRegistry.denyInstall(data);
     }
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference])
 };
 
-Services.obs.addObserver(WebappManager, "webapps-ask-install", false);
-Services.obs.addObserver(WebappManager, "webapps-launch", false);
-Services.obs.addObserver(WebappManager, "webapps-uninstall", false);
-Services.obs.addObserver(WebappManager, "webapps-update", false);
-
-DOMApplicationRegistry.registerUpdateHandler(WebappManager.update);
+Services.obs.addObserver(WebappsHandler, "webapps-ask-install", false);
+Services.obs.addObserver(WebappsHandler, "webapps-launch", false);
+Services.obs.addObserver(WebappsHandler, "webapps-uninstall", false);
--- a/webapprt/moz.build
+++ b/webapprt/moz.build
@@ -19,18 +19,18 @@ EXTRA_COMPONENTS += [
     'ContentPermission.js',
     'DirectoryProvider.js',
     'PaymentUIGlue.js',
 ]
 
 EXTRA_JS_MODULES += [
     'RemoteDebugger.jsm',
     'Startup.jsm',
-    'WebappManager.jsm',
     'WebappRT.jsm',
+    'WebappsHandler.jsm',
     'WebRTCHandler.jsm',
 ]
 
 MOCHITEST_WEBAPPRT_CHROME_MANIFESTS += ['test/chrome/webapprt.ini']
 MOCHITEST_MANIFESTS += ['test/content/mochitest.ini']
 
 # Place webapprt resources in a separate app dir
 DIST_SUBDIR = 'webapprt'