Bug 786296 - Remove permissions when an app is uninstalled. r=jlebar,fabrice
authorMounir Lamouri <mounir.lamouri@gmail.com>
Fri, 31 Aug 2012 11:34:28 -0300
changeset 104010 b4eaa2264afe258c95ded42ebcfc309ef0c5c407
parent 104009 bb5225ea88ba5ea12fbcbfd441f66661cda7c914
child 104011 1af3aa614b6d65ec43e441ca55028ac068974c9d
push id23392
push userryanvm@gmail.com
push dateSat, 01 Sep 2012 01:35:58 +0000
treeherdermozilla-central@a21fd4d085ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlebar, fabrice
bugs786296
milestone18.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 786296 - Remove permissions when an app is uninstalled. r=jlebar,fabrice
dom/apps/src/AppsService.js
dom/apps/src/AppsServiceChild.jsm
dom/apps/src/AppsUtils.jsm
dom/apps/src/Webapps.jsm
dom/interfaces/apps/mozIApplication.idl
dom/interfaces/apps/nsIAppsService.idl
dom/tests/mochitest/webapps/test_install_app.xul
extensions/cookie/nsPermissionManager.cpp
extensions/cookie/nsPermissionManager.h
extensions/cookie/test/Makefile.in
extensions/cookie/test/test_app_uninstall.html
layout/build/Makefile.in
layout/build/nsLayoutStatics.cpp
netwerk/base/public/nsIPermissionManager.idl
--- a/dom/apps/src/AppsService.js
+++ b/dom/apps/src/AppsService.js
@@ -42,13 +42,18 @@ AppsService.prototype = {
     return DOMApplicationRegistry.getAppByLocalId(aLocalId);
   },
 
   getManifestURLByLocalId: function getManifestURLByLocalId(aLocalId) {
     debug("getManifestURLByLocalId( " + aLocalId + " )");
     return DOMApplicationRegistry.getManifestURLByLocalId(aLocalId);
   },
 
+  getAppFromObserverMessage: function getAppFromObserverMessage(aMessage) {
+    debug("getAppFromObserverMessage( " + aMessage + " )");
+    return DOMApplicationRegistry.getAppFromObserverMessage(aMessage);
+  },
+
   classID : APPS_SERVICE_CID,
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIAppsService])
 }
 
 const NSGetFactory = XPCOMUtils.generateNSGetFactory([AppsService])
--- a/dom/apps/src/AppsServiceChild.jsm
+++ b/dom/apps/src/AppsServiceChild.jsm
@@ -71,12 +71,17 @@ let DOMApplicationRegistry = {
   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);
+  },
+
+  getAppFromObserverMessage: function getAppFromObserverMessage(aMessage) {
+    debug("getAppFromObserverMessage " + aMessage);
+    return AppsUtils.getAppFromObserverMessage(this.webapps. aMessage);
   }
 }
 
 DOMApplicationRegistry.init();
--- a/dom/apps/src/AppsUtils.jsm
+++ b/dom/apps/src/AppsUtils.jsm
@@ -100,10 +100,25 @@ let AppsUtils = {
     for (let id in aApps) {
       let app = aApps[id];
       if (app.localId == aLocalId) {
         return app.manifestURL;
       }
     }
 
     return "";
+  },
+
+  getAppFromObserverMessage: function(aApps, aMessage) {
+    let data = JSON.parse(aMessage);
+
+    for (let id in aApps) {
+      let app = aApps[id];
+      if (app.origin != data.origin) {
+        continue;
+      }
+
+      return this.cloneAsMozIApplication(app);
+    }
+
+    return null;
   }
 }
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -775,16 +775,20 @@ let DOMApplicationRegistry = {
   getManifestURLByLocalId: function(aLocalId) {
     return AppsUtils.getManifestURLByLocalId(this.webapps, aLocalId);
   },
 
   getAppLocalIdByManifestURL: function(aManifestURL) {
     return AppsUtils.getAppLocalIdByManifestURL(this.webapps, aManifestURL);
   },
 
+  getAppFromObserverMessage: function(aMessage) {
+    return AppsUtils.getAppFromObserverMessage(this.webapps, aMessage);
+  },
+
   getAllWithoutManifests: function(aCallback) {
     let result = {};
     for (let id in this.webapps) {
       let app = AppsUtils.cloneAppObject(this.webapps[id]);
       result[id] = app;
     }
     aCallback(result);
   },
--- a/dom/interfaces/apps/mozIApplication.idl
+++ b/dom/interfaces/apps/mozIApplication.idl
@@ -6,17 +6,20 @@
  * 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(acf46a46-729a-4ab4-9da3-8d59ecfd103d)]
+[scriptable, uuid(764e8930-ff06-4f23-9a6a-8523b93ac09f)]
 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;
+
+  /* Returns the local id of the app (not the uuid used for sync). */
+  readonly attribute unsigned long localId;
 };
--- a/dom/interfaces/apps/nsIAppsService.idl
+++ b/dom/interfaces/apps/nsIAppsService.idl
@@ -1,26 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "domstubs.idl"
 
 interface mozIDOMApplication;
+interface mozIApplication;
 
 %{C++
 #define APPS_SERVICE_CID { 0x05072afa, 0x92fe, 0x45bf, { 0xae, 0x22, 0x39, 0xb6, 0x9c, 0x11, 0x70, 0x58 } }
 #define APPS_SERVICE_CONTRACTID "@mozilla.org/AppsService;1"
 %}
 
 /*
  * This service allows accessing some DOMApplicationRegistry methods from
  * non-javascript code.
  */
-[scriptable, uuid(04e4ef3c-1a30-45bc-ab08-291820f13872)]
+[scriptable, uuid(1f0ec00c-57c7-4ad2-a648-1359aa390360)]
 interface nsIAppsService : nsISupports
 {
   mozIDOMApplication getAppByManifestURL(in DOMString manifestURL);
 
   /**
    * Returns the |localId| of the app associated with the |manifestURL| passed
    * in parameter.
    * Returns nsIScriptSecurityManager::NO_APP_ID if |manifestURL| isn't a valid
@@ -32,9 +33,16 @@ interface nsIAppsService : nsISupports
    * Returns the application associated to this localId.
    */
   mozIDOMApplication getAppByLocalId(in unsigned long localId);
 
   /**
    * Returns the manifest URL associated to this localId.
    */
   DOMString getManifestURLByLocalId(in unsigned long localId);
+
+  /**
+   * Returns the app that is related to the message.
+   * This is a helper to not have to worry about what is the actual structure
+   * of the message when listening to one.
+   */
+  mozIApplication getAppFromObserverMessage(in DOMString message);
 };
--- a/dom/tests/mochitest/webapps/test_install_app.xul
+++ b/dom/tests/mochitest/webapps/test_install_app.xul
@@ -19,38 +19,30 @@
   <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=741549"
      target="_blank">Mozilla Bug 741549</a> <br />
   <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=734294"
      target="_blank">Mozilla Bug 734294</a>
   </body>
 
 <script> 
 
-steps = [get_installed_returns_nothing, install_super_crazy, get_self_returns_nothing, 
+steps = [get_installed_returns_nothing, get_self_returns_nothing, 
          install_wild_crazy, uninstall_wild_crazy, tearDown];
 
 runAll(steps);
 
 function get_installed_returns_nothing(next) {
   debug("in " + arguments.callee.name);
   mozAppscb(navigator.mozApps.getInstalled(), 
             [{}],
             "success",
             ok,
             next);
 }
 
-function install_super_crazy(next)  {
-  debug("in " + arguments.callee.name);
-  var appURL = SERVERS['super_crazy'];
-  install(appURL, ok, function() { 
-    getInstalled([appURL], ok, next);
-  });
-}
-
 function get_self_returns_nothing(next) {
   debug("in " + arguments.callee.name);
   mozAppscb(navigator.mozApps.getSelf(), 
             [{}],
             "success",
             ok, 
             next);
 }
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -19,16 +19,18 @@
 #include "nsAppDirectoryServiceDefs.h"
 #include "prprf.h"
 #include "mozilla/storage.h"
 #include "mozilla/Attributes.h"
 #include "nsXULAppAPI.h"
 #include "nsIPrincipal.h"
 #include "nsContentUtils.h"
 #include "nsIScriptSecurityManager.h"
+#include "nsIAppsService.h"
+#include "mozIApplication.h"
 
 static nsPermissionManager *gPermissionManager = nullptr;
 
 using mozilla::dom::ContentParent;
 using mozilla::dom::ContentChild;
 using mozilla::unused; // ha!
 
 static bool
@@ -116,16 +118,43 @@ GetHostForPrincipal(nsIPrincipal* aPrinc
   rv = uri->GetAsciiHost(aHost);
   if (NS_FAILED(rv) || aHost.IsEmpty()) {
     return NS_ERROR_UNEXPECTED;
   }
 
   return NS_OK;
 }
 
+class AppUninstallObserver : public nsIObserver {
+public:
+  NS_DECL_ISUPPORTS
+
+  // nsIObserver implementation.
+  NS_IMETHODIMP
+  Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *data)
+  {
+    MOZ_ASSERT(!nsCRT::strcmp(aTopic, "webapps-uninstall"));
+
+    nsCOMPtr<nsIAppsService> appsService = do_GetService("@mozilla.org/AppsService;1");
+    nsCOMPtr<mozIApplication> app;
+
+    appsService->GetAppFromObserverMessage(nsAutoString(data), getter_AddRefs(app));
+    NS_ENSURE_TRUE(app, NS_ERROR_UNEXPECTED);
+
+    uint32_t appId;
+    app->GetLocalId(&appId);
+    MOZ_ASSERT(appId != nsIScriptSecurityManager::NO_APP_ID);
+
+    nsCOMPtr<nsIPermissionManager> permManager = do_GetService("@mozilla.org/permissionmanager;1");
+    return permManager->RemovePermissionsForApp(appId);
+  }
+};
+
+NS_IMPL_ISUPPORTS1(AppUninstallObserver, nsIObserver)
+
 } // anonymous namespace
 
 ////////////////////////////////////////////////////////////////////////////////
 
 nsPermissionManager::PermissionKey::PermissionKey(nsIPrincipal* aPrincipal)
 {
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(GetHostForPrincipal(aPrincipal, mHost)));
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aPrincipal->GetAppId(&mAppId)));
@@ -233,16 +262,23 @@ NS_IMETHODIMP DeleteFromMozHostListener:
 
   if (aReason == REASON_ERROR) {
     manager->CloseDB(true);
   }
 
   return NS_OK;
 }
 
+/* static */ void
+nsPermissionManager::AppUninstallObserverInit()
+{
+  nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1");
+  observerService->AddObserver(new AppUninstallObserver(), "webapps-uninstall", /* holdsWeak= */ false);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // nsPermissionManager Implementation
 
 static const char kPermissionsFileName[] = "permissions.sqlite";
 #define HOSTS_SCHEMA_VERSION 3
 
 static const char kHostpermFileName[] = "hostperm.1";
 
@@ -1066,16 +1102,95 @@ NS_IMETHODIMP nsPermissionManager::Obser
   else if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
     // the profile has already changed; init the db from the new location
     InitDB(false);
   }
 
   return NS_OK;
 }
 
+PLDHashOperator
+nsPermissionManager::GetPermissionsForApp(nsPermissionManager::PermissionHashKey* entry, void* arg)
+{
+  GetPermissionsForAppStruct* data = static_cast<GetPermissionsForAppStruct*>(arg);
+
+  for (uint32_t i = 0; i < entry->GetPermissions().Length(); ++i) {
+    nsPermissionManager::PermissionEntry& permEntry = entry->GetPermissions()[i];
+
+    if (entry->GetKey()->mAppId != data->appId) {
+      continue;
+    }
+
+    data->permissions.AppendObject(new nsPermission(entry->GetKey()->mHost,
+                                                    entry->GetKey()->mAppId,
+                                                    entry->GetKey()->mIsInBrowserElement,
+                                                    gPermissionManager->mTypeArray.ElementAt(permEntry.mType),
+                                                    permEntry.mPermission,
+                                                    permEntry.mExpireType,
+                                                    permEntry.mExpireTime));
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RemovePermissionsForApp(uint32_t aAppId)
+{
+  ENSURE_NOT_CHILD_PROCESS;
+  NS_ENSURE_ARG(aAppId != nsIScriptSecurityManager::NO_APP_ID);
+
+  // We begin by removing all the permissions from the DB.
+  // This is not using a mozIStorageStatement because removing an app should be
+  // rare enough to not have to worry too much about performance.
+  // After clearing the DB, we call AddInternal() to make sure that all
+  // processes are aware of this change and the representation of the DB in
+  // memory is updated.
+  // We have to get all permissions associated with an application and then
+  // remove those because doing so in EnumerateEntries() would fail because
+  // we might happen to actually delete entries from the list.
+
+  nsCAutoString sql;
+  sql.AppendLiteral("DELETE FROM moz_hosts WHERE appId=");
+  sql.AppendInt(aAppId);
+
+  nsresult rv = mDBConn->ExecuteSimpleSQL(sql);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  GetPermissionsForAppStruct data(aAppId);
+  mPermissionTable.EnumerateEntries(GetPermissionsForApp, &data);
+
+  for (int32_t i=0; i<data.permissions.Count(); ++i) {
+    nsCAutoString host;
+    bool isInBrowserElement;
+    nsCAutoString type;
+
+    data.permissions[i]->GetHost(host);
+    data.permissions[i]->GetIsInBrowserElement(&isInBrowserElement);
+    data.permissions[i]->GetType(type);
+
+    nsCOMPtr<nsIPrincipal> principal;
+    if (NS_FAILED(GetPrincipal(host, aAppId, isInBrowserElement,
+                               getter_AddRefs(principal)))) {
+      NS_ERROR("GetPrincipal() failed!");
+      continue;
+    }
+
+    AddInternal(principal,
+                type,
+                nsIPermissionManager::UNKNOWN_ACTION,
+                0,
+                nsIPermissionManager::EXPIRE_NEVER,
+                0,
+                nsPermissionManager::eNotify,
+                nsPermissionManager::eNoDBOperation);
+  }
+
+  return NS_OK;
+}
+
 //*****************************************************************************
 //*** nsPermissionManager private methods
 //*****************************************************************************
 
 nsresult
 nsPermissionManager::RemoveAllFromMemory()
 {
   mLargestID = 0;
--- a/extensions/cookie/nsPermissionManager.h
+++ b/extensions/cookie/nsPermissionManager.h
@@ -13,16 +13,17 @@
 #include "nsCOMPtr.h"
 #include "nsIFile.h"
 #include "nsTHashtable.h"
 #include "nsTArray.h"
 #include "nsString.h"
 #include "nsPermission.h"
 #include "nsHashKeys.h"
 #include "nsAutoPtr.h"
+#include "nsCOMArray.h"
 
 class nsIPermission;
 class nsIIDNService;
 class mozIStorageConnection;
 class mozIStorageStatement;
 
 ////////////////////////////////////////////////////////////////////////////////
 
@@ -185,16 +186,24 @@ public:
                        const nsAFlatCString &aType,
                        uint32_t aPermission,
                        int64_t aID,
                        uint32_t aExpireType,
                        int64_t  aExpireTime,
                        NotifyOperationType aNotifyOperation,
                        DBOperationType aDBOperation);
 
+  /**
+   * Initialize the "webapp-uninstall" observing.
+   * Will create a nsPermissionManager instance if needed.
+   * That way, we can prevent have nsPermissionManager created at startup just
+   * to be able to clear data when an application is uninstalled.
+   */
+  static void AppUninstallObserverInit();
+
 private:
   int32_t GetTypeIndex(const char *aTypeString,
                        bool        aAdd);
 
   PermissionHashKey* GetPermissionHashKey(const nsACString& aHost,
                                           uint32_t aAppId,
                                           bool aIsInBrowserElement,
                                           uint32_t          aType,
@@ -232,16 +241,38 @@ private:
                        const nsACString     &aHost,
                        const nsACString     &aType,
                        uint32_t              aPermission,
                        uint32_t              aExpireType,
                        int64_t               aExpireTime,
                        uint32_t              aAppId,
                        bool                  aIsInBrowserElement);
 
+  /**
+   * This struct has to be passed as an argument to GetPermissionsForApp.
+   * |appId| has to be defined.
+   * |permissions| will be filed with permissions that are related to the app.
+   */
+  struct GetPermissionsForAppStruct {
+    uint32_t                  appId;
+    nsCOMArray<nsIPermission> permissions;
+
+    GetPermissionsForAppStruct() MOZ_DELETE;
+    GetPermissionsForAppStruct(uint32_t aAppId)
+      : appId(aAppId)
+    {}
+  };
+
+  /**
+   * This method will return the list of all permissions that are related to a
+   * specific app.
+   * @param arg has to be an instance of GetPermissionsForAppStruct.
+   */
+  static PLDHashOperator GetPermissionsForApp(nsPermissionManager::PermissionHashKey* entry, void* arg);
+
   nsCOMPtr<nsIObserverService> mObserverService;
   nsCOMPtr<nsIIDNService>      mIDNService;
 
   nsCOMPtr<mozIStorageConnection> mDBConn;
   nsCOMPtr<mozIStorageStatement> mStmtInsert;
   nsCOMPtr<mozIStorageStatement> mStmtDelete;
   nsCOMPtr<mozIStorageStatement> mStmtUpdate;
 
--- a/extensions/cookie/test/Makefile.in
+++ b/extensions/cookie/test/Makefile.in
@@ -49,16 +49,17 @@ MOCHITEST_FILES = \
   file_localhost_inner.html \
   test_same_base_domain_5.html \
   test_same_base_domain_6.html \
   file_loopback_inner.html \
   $(NULL)
 
 MOCHITEST_CHROME_FILES = \
   test_permissionmanager_app_isolation.html \
+  test_app_uninstall.html \
   $(NULL)
 
 MOCHITEST_BROWSER_FILES = \
   browser_test_favicon.js \
   $(NULL)
 
 XPCSHELL_TESTS = unit
 
new file mode 100644
--- /dev/null
+++ b/extensions/cookie/test/test_app_uninstall.html
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=786296
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Tests that uninstalling app removes the permissions</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=786296">Mozilla Bug 786296</a>
+<p id="display"></p>
+<div id="content">
+  
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+/** Test for Bug 786296 **/
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+SimpleTest.waitForExplicitFinish();
+
+var permManager = Cc["@mozilla.org/permissionmanager;1"]
+                    .getService(Ci.nsIPermissionManager);
+var appsService = Cc['@mozilla.org/AppsService;1']
+                    .getService(Ci.nsIAppsService);
+var secMan = Cc['@mozilla.org/scriptsecuritymanager;1']
+               .getService(Ci.nsIScriptSecurityManager);
+var ioService = Cc["@mozilla.org/network/io-service;1"]
+                  .getService(Components.interfaces.nsIIOService);
+
+function confirmNextInstall() {
+  var panel = window.top.QueryInterface(Ci.nsIInterfaceRequestor)
+                        .getInterface(Ci.nsIWebNavigation)
+                        .QueryInterface(Ci.nsIDocShell)
+                        .chromeEventHandler.ownerDocument.defaultView
+                        .PopupNotifications.panel
+
+  panel.addEventListener("popupshown", function() {
+    panel.removeEventListener("popupshown", arguments.callee, false);
+    this.childNodes[0].button.doCommand();
+  }, false);
+}
+
+// If aAppId = -1, returns permissions count, regardless of app.
+function getPermissionCountForApp(aAppId) {
+  var nbPermissions = 0;
+  var enumerator = permManager.enumerator;
+
+  while (enumerator.hasMoreElements()) {
+    var permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+
+    if (permission.appId == aAppId || aAppId == -1) {
+      nbPermissions++;
+    }
+  }
+
+  return nbPermissions;
+}
+
+var previousDryRunValue = null;
+try {
+  previousDryRunValue = SpecialPowers.getBoolPref('browser.mozApps.installer.dry_run');
+} catch(e) {
+}
+
+SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', true);
+permManager.addFromPrincipal(window.document.nodePrincipal, "webapps-manage",
+                             Ci.nsIPermissionManager.ALLOW_ACTION);
+
+var gManifestURL = "http://www.example.com:80/chrome/dom/tests/mochitest/webapps/apps/super_crazy.webapp";
+
+confirmNextInstall();
+
+navigator.mozApps.install(gManifestURL, null).onsuccess = function() {
+  var testAppId = appsService.getAppLocalIdByManifestURL(gManifestURL);
+
+  is(getPermissionCountForApp(testAppId), 0, "App should have no permission");
+
+  var currentPermissionCount = getPermissionCountForApp(-1);
+
+  var principal = secMan.getAppCodebasePrincipal(ioService.newURI("http://www.example.com", null, null),
+                                                 testAppId, false);
+
+  permManager.addFromPrincipal(principal, "foobar", Ci.nsIPermissionManager.ALLOW_ACTION);
+  permManager.addFromPrincipal(principal, "foo", Ci.nsIPermissionManager.DENY_ACTION);
+  permManager.addFromPrincipal(principal, "bar", Ci.nsIPermissionManager.ALLOW_ACTION, Ci.nsIPermissionManager.EXPIRE_SESSION, 0);
+
+  principal = secMan.getAppCodebasePrincipal(ioService.newURI("http://www.example.com", null, null),
+                                             testAppId, true);
+  permManager.addFromPrincipal(principal, "foobar", Ci.nsIPermissionManager.ALLOW_ACTION);
+
+  principal = secMan.getAppCodebasePrincipal(ioService.newURI("http://www.example.org", null, null),
+                                             testAppId, false);
+  permManager.addFromPrincipal(principal, "foobar", Ci.nsIPermissionManager.ALLOW_ACTION);
+
+  is(getPermissionCountForApp(testAppId), 5, "App should have 5 permissions");
+
+  // Not installed means not installed as native app.
+  navigator.mozApps.mgmt.getNotInstalled().onsuccess = function() {
+    for (i in this.result) {
+      var app = this.result[i];
+      if (app.manifestURL == gManifestURL) {
+        app.uninstall().onsuccess = function() {
+          is(getPermissionCountForApp(testAppId), 0, "App should have no permissions");
+
+          is(getPermissionCountForApp(-1), currentPermissionCount,
+             "Number of permissions should not have changed");
+
+          SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', previousDryRunValue);
+          permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
+                                       Ci.nsIPermissionManager.ALLOW_ACTION);
+          SimpleTest.finish();
+          return;
+        };
+      }
+    }
+  };
+};
+
+</script>
+</pre>
+</body>
+</html>
--- a/layout/build/Makefile.in
+++ b/layout/build/Makefile.in
@@ -261,16 +261,17 @@ LOCAL_INCLUDES	+= -I$(srcdir)/../base \
 		   -I$(topsrcdir)/editor/txmgr/src \
 		   -I$(topsrcdir)/editor/txtsvc/src \
 		   -I$(topsrcdir)/editor/composer/src \
 		   -I$(topsrcdir)/js/xpconnect/src \
 		   -I$(topsrcdir)/js/xpconnect/loader \
 		   -I$(topsrcdir)/caps/include \
 		   -I$(topsrcdir)/netwerk/base/src \
 		   -I$(topsrcdir)/content/svg/content/src \
+		   -I$(topsrcdir)/extensions/cookie \
 		   $(NULL)
 
 ifdef MOZ_B2G_RIL #{
 LOCAL_INCLUDES	+= -I$(topsrcdir)/dom/system/gonk
 endif #}
 
 ifdef MOZ_B2G_BT #{
 LOCAL_INCLUDES	+= -I$(topsrcdir)/dom/bluetooth
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -93,16 +93,17 @@
 #include "nsFrameMessageManager.h"
 #include "nsRefreshDriver.h"
 #include "nsDOMMutationObserver.h"
 #include "nsHyphenationManager.h"
 #include "nsEditorSpellCheck.h"
 #include "nsWindowMemoryReporter.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ipc/ProcessPriorityManager.h"
+#include "nsPermissionManager.h"
 
 extern void NS_ShutdownChainItemPool();
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::dom::ipc;
 
 nsrefcnt nsLayoutStatics::sLayoutStaticRefcnt = 0;
@@ -247,16 +248,18 @@ nsLayoutStatics::Initialize()
   NS_SealStaticAtomTable();
 
   nsWindowMemoryReporter::Init();
 
   nsSVGUtils::Init();
 
   InitProcessPriorityManager();
 
+  nsPermissionManager::AppUninstallObserverInit();
+
   return NS_OK;
 }
 
 void
 nsLayoutStatics::Shutdown()
 {
   // Don't need to shutdown nsWindowMemoryReporter, that will be done by the
   // memory reporter manager.
--- a/netwerk/base/public/nsIPermissionManager.idl
+++ b/netwerk/base/public/nsIPermissionManager.idl
@@ -30,17 +30,17 @@
 
 #include "nsISupports.idl"
 #include "nsISimpleEnumerator.idl"
 
 interface nsIURI;
 interface nsIObserver;
 interface nsIPrincipal;
 
-[scriptable, uuid(cc423aaf-f088-4ec2-86ef-7733225773f9)]
+[scriptable, uuid(da33450a-f3cb-4fdb-93ee-219644e450c2)]
 interface nsIPermissionManager : nsISupports
 {
   /**
    * Predefined return values for the testPermission method and for
    * the permission param of the add method
    * NOTE: UNKNOWN_ACTION (0) is reserved to represent the
    * default permission when no entry is found for a host, and
    * should not be used by consumers to indicate otherwise.
@@ -164,15 +164,20 @@ interface nsIPermissionManager : nsISupp
                                             in string type);
 
   /**
    * Allows enumeration of all stored permissions
    * @return an nsISimpleEnumerator interface that allows access to
    *         nsIPermission objects
    */
   readonly attribute nsISimpleEnumerator enumerator;
+
+  /**
+   * Remove all permissions associated with a given app id.
+   */
+  void removePermissionsForApp(in unsigned long appId);
 };
 
 %{ C++
 #define NS_PERMISSIONMANAGER_CONTRACTID "@mozilla.org/permissionmanager;1"
 
 #define PERM_CHANGE_NOTIFICATION "perm-changed"
 %}