Merge mozilla-central to mozilla-inbound.
authorSiddharth Agarwal <sid.bugzilla@gmail.com>
Wed, 29 Aug 2012 20:42:55 +0530
changeset 105818 3f8f9d41a525c910741f28e9a20fa6e1e8ad76bd
parent 105817 370b39cc967b38557ce38c47ea1c8479e5c688b7 (current diff)
parent 105816 9db0d3506b7e6678641c1b3dd3bdc61dfb9efe93 (diff)
child 105819 130e416aefd2da8e8ae96f9bfec07e3505a46146
push id55
push usershu@rfrn.org
push dateThu, 30 Aug 2012 01:33:09 +0000
milestone18.0a1
Merge mozilla-central to mozilla-inbound.
--- a/caps/src/nsPrincipal.cpp
+++ b/caps/src/nsPrincipal.cpp
@@ -25,16 +25,19 @@
 #include "nsContentUtils.h"
 #include "jswrapper.h"
 
 #include "nsPrincipal.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/HashFunctions.h"
 
+#include "nsIAppsService.h"
+#include "mozIApplication.h"
+
 using namespace mozilla;
 
 static bool gCodeBasePrincipalSupport = false;
 static bool gIsObservingCodeBasePrincipalSupport = false;
 
 static bool URIIsImmutable(nsIURI* aURI)
 {
   nsCOMPtr<nsIMutable> mutableObj(do_QueryInterface(aURI));
@@ -1294,20 +1297,55 @@ nsPrincipal::Write(nsIObjectOutputStream
 
 uint16_t
 nsPrincipal::GetAppStatus()
 {
   MOZ_ASSERT(mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
 
   // Installed apps have a valid app id (not NO_APP_ID or UNKNOWN_APP_ID)
   // and they are not inside a mozbrowser.
-  return mAppId != nsIScriptSecurityManager::NO_APP_ID &&
-         mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID && !mInMozBrowser
-          ? nsIPrincipal::APP_STATUS_INSTALLED
-          : nsIPrincipal::APP_STATUS_NOT_INSTALLED;
+  if (mAppId == nsIScriptSecurityManager::NO_APP_ID ||
+      mAppId == nsIScriptSecurityManager::UNKNOWN_APP_ID || mInMozBrowser) {
+    return nsIPrincipal::APP_STATUS_NOT_INSTALLED;
+  }
+
+  nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(appsService, nsIPrincipal::APP_STATUS_NOT_INSTALLED);
+
+  nsCOMPtr<mozIDOMApplication> domApp;
+  appsService->GetAppByLocalId(mAppId, getter_AddRefs(domApp));
+  nsCOMPtr<mozIApplication> app = do_QueryInterface(domApp);
+  NS_ENSURE_TRUE(app, nsIPrincipal::APP_STATUS_NOT_INSTALLED);
+
+  uint16_t status = nsIPrincipal::APP_STATUS_INSTALLED;
+  NS_ENSURE_SUCCESS(app->GetAppStatus(&status),
+                    nsIPrincipal::APP_STATUS_NOT_INSTALLED);
+
+  nsCAutoString origin;
+  NS_ENSURE_SUCCESS(GetOrigin(getter_Copies(origin)),
+                    nsIPrincipal::APP_STATUS_NOT_INSTALLED);
+  nsString appOrigin;
+  NS_ENSURE_SUCCESS(app->GetOrigin(appOrigin),
+                    nsIPrincipal::APP_STATUS_NOT_INSTALLED);
+
+  // We go from string -> nsIURI -> origin to be sure we
+  // compare two punny-encoded origins.
+  nsCOMPtr<nsIURI> appURI;
+  NS_ENSURE_SUCCESS(NS_NewURI(getter_AddRefs(appURI), appOrigin),
+                    nsIPrincipal::APP_STATUS_NOT_INSTALLED);
+
+  nsCAutoString appOriginPunned;
+  NS_ENSURE_SUCCESS(GetOriginForURI(appURI, getter_Copies(appOriginPunned)),
+                    nsIPrincipal::APP_STATUS_NOT_INSTALLED);
+
+  if (!appOriginPunned.Equals(origin)) {
+    return nsIPrincipal::APP_STATUS_NOT_INSTALLED;
+  }
+
+  return status;
 }
 
 /************************************************************************************************************************/
 
 static const char EXPANDED_PRINCIPAL_SPEC[] = "[Expanded Principal]";
 
 NS_IMPL_CLASSINFO(nsExpandedPrincipal, NULL, nsIClassInfo::MAIN_THREAD_ONLY,
                   NS_EXPANDEDPRINCIPAL_CID)
--- a/caps/tests/mochitest/test_principal_extendedorigin_appid_appstatus.html
+++ b/caps/tests/mochitest/test_principal_extendedorigin_appid_appstatus.html
@@ -8,17 +8,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <title>Test for nsIPrincipal extendedOrigin, appStatus and appId</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=758258">Mozilla Bug 758258</a>
 <p id="display"></p>
 <div id="content">
-  
+
 </div>
 <pre id="test">
 <script type="application/javascript;version=1.7">
 
 /** Test for Bug 758258 **/
 
 var Ci = Components.interfaces;
 
@@ -124,23 +124,23 @@ var gData = [
     src: "file:///tmp/",
     isapp: false,
     test: [ "eo-unique" ],
   },
   {
     app: "http://example.org/manifest.webapp",
     src: "file:///",
     isapp: true,
-    test: [ "eo-unique" ],
+    test: [ "eo-unique", "in-app-not-installed" ],
   },
   {
     app: "http://example.org/manifest.webapp",
     src: "file:///tmp/",
     isapp: true,
-    test: [ "eo-unique" ],
+    test: [ "eo-unique", "in-app-not-installed" ],
   },
   // iframe inside an app is part of the app.
   {
     app: "http://example.org/manifest.webapp",
     src: "http://example.org/",
     isapp: true,
     child: {
       src: "http://example.org/chrome/",
@@ -276,22 +276,21 @@ function checkIFrame(aFrame, data) {
        data.src.replace('file:///tmp/', 'file:///private/tmp/'),
        'the correct URL should have been loaded');
   } else {
     is(principal.URI.spec, data.src,
        'the correct URL should have been loaded');
   }
 
   if (data.isapp) {
-    is(principal.appStatus, Ci.nsIPrincipal.APP_STATUS_INSTALLED,
-       'this should be an installed app');
-    isnot(principal.appId, Ci.nsIScriptSecurityManager.NO_APP_ID,
-          "installed app should have a valid appId");
-    isnot(principal.appId, Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID,
-          "installed app should have a valid appId");
+    if (data.test.indexOf("in-app-not-installed") != -1) {
+      is(principal.appStatus, Ci.nsIPrincipal.APP_STATUS_NOT_INSTALLED, 'this should not be an installed app');
+    } else {
+      is(principal.appStatus, Ci.nsIPrincipal.APP_STATUS_INSTALLED, 'this should be an installed app');
+    }
   } else {
     is(principal.appStatus, Ci.nsIPrincipal.APP_STATUS_NOT_INSTALLED,
        'this should not be an installed app');
     is(principal.appId, Ci.nsIScriptSecurityManager.NO_APP_ID,
        "principals from non-installed app should have NO_APP_ID");
   }
 
   if (!data.isapp && !data.browser) {
--- a/dom/apps/src/AppsService.js
+++ b/dom/apps/src/AppsService.js
@@ -1,79 +1,54 @@
 /* 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"
 
 function debug(s) {
-  //dump("-*- AppsService: " + s + "\n");
+  //dump("-*- AppsService.js: " + s + "\n");
 }
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const APPS_SERVICE_CID = Components.ID("{05072afa-92fe-45bf-ae22-39b69c117058}");
 
 function AppsService()
 {
   debug("AppsService Constructor");
-  this.inParent = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
-                  .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
-  debug("inParent: " + this.inParent);
-  if (this.inParent) {
-    Cu.import("resource://gre/modules/Webapps.jsm");
-  } else {
-    this.cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
-                .getService(Ci.nsISyncMessageSender);
-  }
+  let inParent = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+                   .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+  debug("inParent: " + inParent);
+  Cu.import(inParent ? "resource://gre/modules/Webapps.jsm" :
+                       "resource://gre/modules/AppsServiceChild.jsm");
 }
 
 AppsService.prototype = {
   getAppByManifestURL: function getAppByManifestURL(aManifestURL) {
     debug("GetAppByManifestURL( " + aManifestURL + " )");
-    if (this.inParent) {
-      return DOMApplicationRegistry.getAppByManifestURL(aManifestURL);
-    } else {
-      return this.cpmm.sendSyncMessage("WebApps:GetAppByManifestURL",
-                                       { url: aManifestURL })[0];
-    }
+    return DOMApplicationRegistry.getAppByManifestURL(aManifestURL);
   },
 
   getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aManifestURL) {
     debug("getAppLocalIdByManifestURL( " + aManifestURL + " )");
-    if (this.inParent) {
-      return DOMApplicationRegistry.getAppLocalIdByManifestURL(aManifestURL);
-    } else {
-      let res = this.cpmm.sendSyncMessage("WebApps:GetAppLocalIdByManifestURL",
-                                          { url: aManifestURL })[0];
-      return res.id;
-    }
+    return DOMApplicationRegistry.getAppLocalIdByManifestURL(aManifestURL);
   },
 
   getAppByLocalId: function getAppByLocalId(aLocalId) {
     debug("getAppByLocalId( " + aLocalId + " )");
-    if (this.inParent) {
-      return DOMApplicationRegistry.getAppByLocalId(aLocalId);
-    } else {
-      return this.cpmm.sendSyncMessage("WebApps:GetAppByLocalId",
-                                       { id: aLocalId })[0];
-    }
+    return DOMApplicationRegistry.getAppByLocalId(aLocalId);
   },
 
   getManifestURLByLocalId: function getManifestURLByLocalId(aLocalId) {
     debug("getManifestURLByLocalId( " + aLocalId + " )");
-    if (this.inParent) {
-      return DOMApplicationRegistry.getManifestURLByLocalId(aLocalId);
-    } else {
-      return this.cpmm.sendSyncMessage("WebApps:GetManifestURLByLocalId",
-                                       { id: aLocalId })[0];
-    }
+    return DOMApplicationRegistry.getManifestURLByLocalId(aLocalId);
   },
 
   classID : APPS_SERVICE_CID,
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIAppsService])
 }
 
 const NSGetFactory = XPCOMUtils.generateNSGetFactory([AppsService])
new file mode 100644
--- /dev/null
+++ b/dom/apps/src/AppsServiceChild.jsm
@@ -0,0 +1,82 @@
+/* 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";
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+// This module exposes a subset of the functionnalities of the parent DOM
+// Registry to content processes, to be be used from the AppsService component.
+
+let EXPORTED_SYMBOLS = ["DOMApplicationRegistry"];
+
+Cu.import("resource://gre/modules/AppsUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+function debug(s) {
+  //dump("-*- AppsServiceChild.jsm: " + s + "\n");
+}
+
+let DOMApplicationRegistry = {
+  init: function init() {
+    debug("init");
+    this.cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
+                  .getService(Ci.nsISyncMessageSender);
+
+    ["Webapps:AddApp", "Webapps:RemoveApp"].forEach((function(aMsgName) {
+      this.cpmm.addMessageListener(aMsgName, this);
+    }).bind(this));
+
+    // We need to prime the cache with the list of apps.
+    // XXX shoud we do this async and block callers if it's not yet there?
+    this.webapps = this.cpmm.sendSyncMessage("Webapps:GetList", { })[0];
+    Services.obs.addObserver(this, "xpcom-shutdown", false);
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    // cpmm.addMessageListener causes the DOMApplicationRegistry object to live
+    // forever if we don't clean up properly.
+    this.webapps = null;
+    ["Webapps:AddApp", "Webapps:RemoveApp"].forEach((function(aMsgName) {
+      this.cpmm.removeMessageListener(aMsgName, this);
+    }).bind(this));
+  },
+
+  receiveMessage: function receiveMessage(aMessage) {
+    debug("Received " + aMessage.name + " message.");
+    let msg = aMessage.json;
+    switch (aMessage.name) {
+      case "Webapps:AddApp":
+        this.webapps[msg.id] = msg.app;
+        break;
+      case "Webapps:RemoveApp":
+        delete this.webapps[msg.id];
+        break;
+    }
+  },
+
+  getAppByManifestURL: function getAppByManifestURL(aManifestURL) {
+    debug("getAppByManifestURL " + aManifestURL);
+    return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL);
+  },
+
+  getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aManifestURL) {
+    debug("getAppLocalIdByManifestURL " + aManifestURL);
+    return AppsUtils.getAppLocalIdByManifestURL(this.webapps, aManifestURL);
+  },
+
+  getAppByLocalId: function getAppByLocalId(aLocalId) {
+    debug("getAppByLocalId " + aLocalId);
+    return AppsUtils.getAppByLocalId(this.webapps, aLocalId);
+  },
+
+  getManifestURLByLocalId: function getManifestURLByLocalId(aLocalId) {
+    debug("getManifestURLByLocalId " + aLocalId);
+    return AppsUtils.getManifestURLByLocalId(this.webapps, aLocalId);
+  }
+}
+
+DOMApplicationRegistry.init();
new file mode 100644
--- /dev/null
+++ b/dom/apps/src/AppsUtils.jsm
@@ -0,0 +1,108 @@
+/* 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";
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+// Shared code for AppsServiceChild.jsm and Webapps.jsm
+
+let EXPORTED_SYMBOLS = ["AppsUtils"];
+
+function debug(s) {
+  //dump("-*- AppsUtils.jsm: " + s + "\n");
+}
+
+let AppsUtils = {
+  // Clones a app, without the manifest.
+  cloneAppObject: function cloneAppObject(aApp) {
+    return {
+      installOrigin: aApp.installOrigin,
+      origin: aApp.origin,
+      receipts: aApp.receipts ? JSON.parse(JSON.stringify(aApp.receipts)) : null,
+      installTime: aApp.installTime,
+      manifestURL: aApp.manifestURL,
+      appStatus: aApp.appStatus,
+      localId: aApp.localId,
+      progress: aApp.progress || 0.0,
+      status: aApp.status || "installed"
+    };
+  },
+
+  cloneAsMozIApplication: function cloneAsMozIApplication(aApp) {
+    let res = this.cloneAppObject(aApp);
+    res.hasPermission = function(aPermission) {
+      let uri = Services.io.newURI(this.origin, null, null);
+      let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
+                     .getService(Ci.nsIScriptSecurityManager);
+      // This helper checks an URI inside |aApp|'s origin and part of |aApp| has a
+      // specific permission. It is not checking if browsers inside |aApp| have such
+      // permission.
+      let principal = secMan.getAppCodebasePrincipal(uri, aApp.localId,
+                                                     /*mozbrowser*/false);
+      let perm = Services.perms.testExactPermissionFromPrincipal(principal,
+                                                                 aPermission);
+      return (perm === Ci.nsIPermissionManager.ALLOW_ACTION);
+    };
+    res.QueryInterface = XPCOMUtils.generateQI([Ci.mozIDOMApplication,
+                                                Ci.mozIApplication]);
+    return res;
+  },
+
+  getAppByManifestURL: function getAppByManifestURL(aApps, aManifestURL) {
+    debug("getAppByManifestURL " + aManifestURL);
+    // This could be O(1) if |webapps| was a dictionary indexed on manifestURL
+    // which should be the unique app identifier.
+    // It's currently O(n).
+    for (let id in aApps) {
+      let app = aApps[id];
+      if (app.manifestURL == aManifestURL) {
+        return this.cloneAsMozIApplication(app);
+      }
+    }
+
+    return null;
+  },
+
+  getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aApps, aManifestURL) {
+    debug("getAppLocalIdByManifestURL " + aManifestURL);
+    for (let id in aApps) {
+      if (aApps[id].manifestURL == aManifestURL) {
+        return aApps[id].localId;
+      }
+    }
+
+    return Ci.nsIScriptSecurityManager.NO_APP_ID;
+  },
+
+  getAppByLocalId: function getAppByLocalId(aApps, aLocalId) {
+    debug("getAppByLocalId " + aLocalId);
+    for (let id in aApps) {
+      let app = aApps[id];
+      if (app.localId == aLocalId) {
+        return this.cloneAsMozIApplication(app);
+      }
+    }
+
+    return null;
+  },
+
+  getManifestURLByLocalId: function getManifestURLByLocalId(aApps, aLocalId) {
+    debug("getManifestURLByLocalId " + aLocalId);
+    for (let id in aApps) {
+      let app = aApps[id];
+      if (app.localId == aLocalId) {
+        return app.manifestURL;
+      }
+    }
+
+    return "";
+  }
+}
--- a/dom/apps/src/Makefile.in
+++ b/dom/apps/src/Makefile.in
@@ -13,11 +13,13 @@ EXTRA_COMPONENTS = \
   AppsService.js \
   AppsService.manifest \
   Webapps.js \
   Webapps.manifest \
   $(NULL)
 
 EXTRA_PP_JS_MODULES += \
   Webapps.jsm \
+  AppsServiceChild.jsm \
+  AppsUtils.jsm \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -10,16 +10,17 @@ const Ci = Components.interfaces;
 const Cr = Components.results;
 
 let EXPORTED_SYMBOLS = ["DOMApplicationRegistry", "DOMApplicationManifest"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import('resource://gre/modules/ActivitiesService.jsm');
+Cu.import("resource://gre/modules/AppsUtils.jsm");
 
 const WEBAPP_RUNTIME = Services.appinfo.ID == "webapprt@mozilla.org";
 
 XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
   Cu.import("resource://gre/modules/NetUtil.jsm");
   return NetUtil;
 });
 
@@ -45,26 +46,26 @@ XPCOMUtils.defineLazyGetter(this, "msgmg
   // are in a different directory (currently the Firefox profile that installed
   // the webapp); otherwise, they're in the current profile.
   const DIRECTORY_NAME = WEBAPP_RUNTIME ? "WebappRegD" : "ProfD";
 #endif
 
 let DOMApplicationRegistry = {
   appsFile: null,
   webapps: { },
+  children: [ ],
   allAppsLaunchable: false,
 
   init: function() {
     this.messages = ["Webapps:Install", "Webapps:Uninstall",
-                    "Webapps:GetSelf",
-                    "Webapps:GetInstalled", "Webapps:GetNotInstalled",
-                    "Webapps:Launch", "Webapps:GetAll",
-                    "Webapps:InstallPackage", "Webapps:GetBasePath",
-                    "WebApps:GetAppByManifestURL", "WebApps:GetAppLocalIdByManifestURL",
-                    "WebApps:GetAppByLocalId", "WebApps:GetManifestURLByLocalId"];
+                     "Webapps:GetSelf",
+                     "Webapps:GetInstalled", "Webapps:GetNotInstalled",
+                     "Webapps:Launch", "Webapps:GetAll",
+                     "Webapps:InstallPackage", "Webapps:GetBasePath",
+                     "Webapps:GetList"];
 
     this.messages.forEach((function(msgName) {
       ppmm.addMessageListener(msgName, this);
     }).bind(this));
 
     Services.obs.addObserver(this, "xpcom-shutdown", false);
 
     this.appsFile = FileUtils.getFile(DIRECTORY_NAME,
@@ -75,16 +76,21 @@ let DOMApplicationRegistry = {
         this.webapps = aData;
         for (let id in this.webapps) {
 #ifdef MOZ_SYS_MSG
           this._processManifestForId(id);
 #endif
           if (!this.webapps[id].localId) {
             this.webapps[id].localId = this._nextLocalId();
           }
+
+          // Default to a non privileged status.
+          if (this.webapps[id].appStatus === undefined) {
+            this.webapps[id].appStatus = Ci.nsIPrincipal.APP_STATUS_INSTALLED;
+          }
         };
       }).bind(this));
     }
 
     try {
       let hosts = Services.prefs.getCharPref("dom.mozApps.whitelist");
       hosts.split(",").forEach(function(aHost) {
         Services.perms.add(Services.io.newURI(aHost, null, null),
@@ -245,28 +251,19 @@ let DOMApplicationRegistry = {
           ppmm.broadcastAsyncMessage("Webapps:GetAll:Return:KO", msg);
         break;
       case "Webapps:InstallPackage":
         this.installPackage(msg);
         break;
       case "Webapps:GetBasePath":
         return FileUtils.getFile(DIRECTORY_NAME, ["webapps"], true).path;
         break;
-      case "WebApps:GetAppByManifestURL":
-        return this.getAppByManifestURL(msg.url);
-        break;
-      case "WebApps:GetAppLocalIdByManifestURL":
-        return { id: this.getAppLocalIdByManifestURL(msg.url) };
-        break;
-      case "WebApps:GetAppByLocalId":
-        return this.getAppByLocalId(msg.id);
-        break;
-      case "WebApps:GetManifestURLByLocalId":
-        return this.getManifestURLByLocalId(msg.id);
-        break;
+      case "Webapps:GetList":
+        this.children.push(aMessage.target);
+        return this.webapps;
     }
   },
 
   _writeFile: function ss_writeFile(aFile, aData, aCallbak) {
     // Initialize the file output stream.
     let ostream = FileUtils.openSafeFileOutputStream(aFile);
 
     // Obtain a converter to convert our data to a UTF-8 encoded input stream.
@@ -277,30 +274,16 @@ let DOMApplicationRegistry = {
     // Asynchronously copy the data to the file.
     let istream = converter.convertToInputStream(aData);
     NetUtil.asyncCopy(istream, ostream, function(rc) {
       if (aCallbak)
         aCallbak();
     });
   },
 
-  // clones a app object, without the manifest
-  _cloneAppObject: function(aApp) {
-    let clone = {
-      installOrigin: aApp.installOrigin,
-      origin: aApp.origin,
-      receipts: aApp.receipts ? JSON.parse(JSON.stringify(aApp.receipts)) : null,
-      installTime: aApp.installTime,
-      manifestURL: aApp.manifestURL,
-      progress: aApp.progress || 0.0,
-      status: aApp.status || "installed"
-    };
-    return clone;
-  },
-
   denyInstall: function(aData) {
     let packageId = aData.app.packageId;
     if (packageId) {
       let dir = FileUtils.getDir("TmpD", ["webapps", packageId],
                                  true, true);
       try {
         dir.remove(true);
       } catch(e) {
@@ -326,51 +309,55 @@ let DOMApplicationRegistry = {
       localId = this._nextLocalId();
     }
 
     if (app.packageId) {
       // Override the origin with the correct id.
       app.origin = "app://" + id;
     }
 
-    let appObject = this._cloneAppObject(app);
+    let appObject = AppsUtils.cloneAppObject(app);
+    appObject.appStatus = app.appStatus || Ci.nsIPrincipal.APP_STATUS_INSTALLED;
     appObject.installTime = app.installTime = Date.now();
     let appNote = JSON.stringify(appObject);
     appNote.id = id;
 
     appObject.localId = localId;
 
     let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
     let manFile = dir.clone();
     manFile.append("manifest.webapp");
     this._writeFile(manFile, JSON.stringify(app.manifest), function() {
       // If this a packaged app, move the zip file from the temp directory,
       // and delete the temp directory.
       if (app.packageId) {
-        let appFile = FileUtils.getFile("TmpD", ["webapps", app.packageId, "application.zip"], 
+        let appFile = FileUtils.getFile("TmpD", ["webapps", app.packageId, "application.zip"],
                                         true, true);
         appFile.moveTo(dir, "application.zip");
-        let tmpDir = FileUtils.getDir("TmpD", ["webapps", app.packageId], 
+        let tmpDir = FileUtils.getDir("TmpD", ["webapps", app.packageId],
                                         true, true);
         try {
           tmpDir.remove(true);
         } catch(e) {
         }
       }
     });
     this.webapps[id] = appObject;
 
     appObject.status = "installed";
-    
+
     let manifest = new DOMApplicationManifest(app.manifest, app.origin);
 
     if (!aFromSync)
       this._saveApps((function() {
         ppmm.broadcastAsyncMessage("Webapps:Install:Return:OK", aData);
         Services.obs.notifyObservers(this, "webapps-sync-install", appNote);
+        this.children.forEach(function(aMsgMgr) {
+          aMsgMgr.broadcastAsyncMessage("Webapps:AddApp", { id: id, app: appObject });
+        });
       }).bind(this));
 
 #ifdef MOZ_SYS_MSG
     this._registerSystemMessages(id, app);
 #endif
 
     // if the manifest has an appcache_path property, use it to populate the appcache
     if (manifest.appcache_path) {
@@ -561,17 +548,17 @@ let DOMApplicationRegistry = {
     let found = false;
     for (let id in this.webapps) {
       let app = this.webapps[id];
       if (app.origin != aData.origin) {
         continue;
       }
 
       found = true;
-      let appNote = JSON.stringify(this._cloneAppObject(app));
+      let appNote = JSON.stringify(AppsUtils.cloneAppObject(app));
       appNote.id = id;
 
       this._readManifests([{ id: id }], (function unregisterManifest(aResult) {
 #ifdef MOZ_SYS_MSG
         this._unregisterActivities(aResult[0].manifest, app);
 #endif
       }).bind(this));
 
@@ -580,31 +567,34 @@ let DOMApplicationRegistry = {
         dir.remove(true);
       } catch (e) {}
 
       delete this.webapps[id];
 
       this._saveApps((function() {
         ppmm.broadcastAsyncMessage("Webapps:Uninstall:Return:OK", aData);
         Services.obs.notifyObservers(this, "webapps-sync-uninstall", appNote);
+        this.children.forEach(function(aMsgMgr) {
+          aMsgMgr.broadcastAsyncMessage("Webapps:RemoveApp", { id: id });
+        });
       }).bind(this));
     }
 
     if (!found) {
       ppmm.broadcastAsyncMessage("Webapps:Uninstall:Return:KO", aData);
     }
   },
 
   getSelf: function(aData) {
     aData.apps = [];
     let tmp = [];
     let id = this._appId(aData.origin);
 
     if (id && this._isLaunchable(this.webapps[id].origin)) {
-      let app = this._cloneAppObject(this.webapps[id]);
+      let app = AppsUtils.cloneAppObject(this.webapps[id]);
       aData.apps.push(app);
       tmp.push({ id: id });
     }
 
     this._readManifests(tmp, (function(aResult) {
       for (let i = 0; i < aResult.length; i++)
         aData.apps[i].manifest = aResult[i].manifest;
       ppmm.broadcastAsyncMessage("Webapps:GetSelf:Return:OK", aData);
@@ -613,17 +603,17 @@ let DOMApplicationRegistry = {
 
   getInstalled: function(aData) {
     aData.apps = [];
     let tmp = [];
 
     for (let id in this.webapps) {
       if (this.webapps[id].installOrigin == aData.origin &&
           this._isLaunchable(this.webapps[id].origin)) {
-        aData.apps.push(this._cloneAppObject(this.webapps[id]));
+        aData.apps.push(AppsUtils.cloneAppObject(this.webapps[id]));
         tmp.push({ id: id });
       }
     }
 
     this._readManifests(tmp, (function(aResult) {
       for (let i = 0; i < aResult.length; i++)
         aData.apps[i].manifest = aResult[i].manifest;
       ppmm.broadcastAsyncMessage("Webapps:GetInstalled:Return:OK", aData);
@@ -631,34 +621,34 @@ let DOMApplicationRegistry = {
   },
 
   getNotInstalled: function(aData) {
     aData.apps = [];
     let tmp = [];
 
     for (let id in this.webapps) {
       if (!this._isLaunchable(this.webapps[id].origin)) {
-        aData.apps.push(this._cloneAppObject(this.webapps[id]));
+        aData.apps.push(AppsUtils.cloneAppObject(this.webapps[id]));
         tmp.push({ id: id });
       }
     }
 
     this._readManifests(tmp, (function(aResult) {
       for (let i = 0; i < aResult.length; i++)
         aData.apps[i].manifest = aResult[i].manifest;
       ppmm.broadcastAsyncMessage("Webapps:GetNotInstalled:Return:OK", aData);
     }).bind(this));
   },
 
   getAll: function(aData) {
     aData.apps = [];
     let tmp = [];
 
     for (let id in this.webapps) {
-      let app = this._cloneAppObject(this.webapps[id]);
+      let app = AppsUtils.cloneAppObject(this.webapps[id]);
       if (!this._isLaunchable(app.origin))
         continue;
 
       aData.apps.push(app);
       tmp.push({ id: id });
     }
 
     this._readManifests(tmp, (function(aResult) {
@@ -687,88 +677,40 @@ let DOMApplicationRegistry = {
   itemExists: function(aId) {
     return !!this.webapps[aId];
   },
 
   getAppById: function(aId) {
     if (!this.webapps[aId])
       return null;
 
-    let app = this._cloneAppObject(this.webapps[aId]);
+    let app = AppsUtils.cloneAppObject(this.webapps[aId]);
     return app;
   },
 
   getAppByManifestURL: function(aManifestURL) {
-    // This could be O(1) if |webapps| was a dictionary indexed on manifestURL
-    // which should be the unique app identifier.
-    // It's currently O(n).
-    for (let id in this.webapps) {
-      let app = this.webapps[id];
-      if (app.manifestURL == aManifestURL) {
-        let res = this._cloneAppObject(app);
-        res.hasPermission = function(permission) {
-          let localId = DOMApplicationRegistry.getAppLocalIdByManifestURL(
-            this.manifestURL);
-          let uri = Services.io.newURI(this.manifestURL, null, null);
-          let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
-                       .getService(Ci.nsIScriptSecurityManager);
-          // XXX for the purposes of permissions checking, this helper
-          // should always be called on !isBrowser frames, so we
-          // assume false here.
-          let principal = secMan.getAppCodebasePrincipal(uri, localId,
-                                                         /*mozbrowser*/false);
-          let perm = Services.perms.testExactPermissionFromPrincipal(principal,
-                                                                     permission);
-          return (perm === Ci.nsIPermissionManager.ALLOW_ACTION);
-        };
-        res.QueryInterface = XPCOMUtils.generateQI([Ci.mozIDOMApplication,
-                                                    Ci.mozIApplication]);
-        return res;
-      }
-    }
-
-    return null;
+    return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL);
   },
 
   getAppByLocalId: function(aLocalId) {
-    for (let id in this.webapps) {
-      let app = this.webapps[id];
-      if (app.localId == aLocalId) {
-        return this._cloneAppObject(app);
-      }
-    }
-
-    return null;
+    return AppsUtils.getAppByLocalId(this.webapps, aLocalId);
   },
 
   getManifestURLByLocalId: function(aLocalId) {
-    for (let id in this.webapps) {
-      let app = this.webapps[id];
-      if (app.localId == aLocalId) {
-        return app.manifestURL;
-      }
-    }
-
-    return null;
+    return AppsUtils.getManifestURLByLocalId(this.webapps, aLocalId);
   },
 
   getAppLocalIdByManifestURL: function(aManifestURL) {
-    for (let id in this.webapps) {
-      if (this.webapps[id].manifestURL == aManifestURL) {
-        return this.webapps[id].localId;
-      }
-    }
-
-    return Ci.nsIScriptSecurityManager.NO_APP_ID;
+    return AppsUtils.getAppLocalIdByManifestURL(this.webapps, aManifestURL);
   },
 
   getAllWithoutManifests: function(aCallback) {
     let result = {};
     for (let id in this.webapps) {
-      let app = this._cloneAppObject(this.webapps[id]);
+      let app = AppsUtils.cloneAppObject(this.webapps[id]);
       result[id] = app;
     }
     aCallback(result);
   },
 
   updateApps: function(aRecords, aCallback) {
     for (let i = 0; i < aRecords.length; i++) {
       let record = aRecords[i];
--- a/dom/interfaces/apps/mozIApplication.idl
+++ b/dom/interfaces/apps/mozIApplication.idl
@@ -6,14 +6,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIDOMApplicationRegistry.idl"
 
 /**
  * We expose Gecko-internal helpers related to "web apps" through this
  * sub-interface.
  */
-[scriptable, uuid(8de25e36-b4cb-4e89-9310-a199dce4e5f4)]
+[scriptable, uuid(acf46a46-729a-4ab4-9da3-8d59ecfd103d)]
 interface mozIApplication: mozIDOMApplication
 {
   /* Return true if this app has |permission|. */
   boolean hasPermission(in string permission);
+
+  /* Application status as defined in nsIPrincipal. */
+  readonly attribute unsigned short appStatus;
 };