Bug 506446 - nsPermissionManager now reads a file with default permissions. r=bsmedberg
authorMark Hammond <mhammond@skippinet.com.au>
Fri, 12 Sep 2014 13:24:10 +1000
changeset 204994 4223912e9f76e1e6d0576fbbfde68e24915ee0fe
parent 204993 b8f71ebcfa930673e14108b611acd5b606d58b21
child 204995 2767fb9328d0caae48bb81f67c777562af5a6da4
push idunknown
push userunknown
push dateunknown
reviewersbsmedberg
bugs506446
milestone35.0a1
Bug 506446 - nsPermissionManager now reads a file with default permissions. r=bsmedberg
browser/app/default_permissions
browser/app/jar.mn
browser/app/moz.build
extensions/cookie/nsPermissionManager.cpp
extensions/cookie/nsPermissionManager.h
extensions/cookie/test/unit/test_permmanager_defaults.js
extensions/cookie/test/unit/xpcshell.ini
new file mode 100644
--- /dev/null
+++ b/browser/app/default_permissions
@@ -0,0 +1,9 @@
+# This file has default permissions for the permission manager.
+# The file-format is strict:
+# * matchtype \t type \t permission \t host
+# * Only "host" is supported for matchtype
+# * type is a string that identifies the type of permission (e.g. "cookie")
+# * permission is an integer between 1 and 15
+# See nsPermissionManager.cpp for more...
+
+# (This file is intentionally blank for the moment...)
new file mode 100644
--- /dev/null
+++ b/browser/app/jar.mn
@@ -0,0 +1,4 @@
+browser.jar:
+
+# The file that holds the default permissions (which is loaded by nsPermissionManager) for the browser.
+    default_permissions (default_permissions)
--- a/browser/app/moz.build
+++ b/browser/app/moz.build
@@ -67,8 +67,10 @@ else:
 
 DISABLE_STL_WRAPPING = True
 
 if CONFIG['MOZ_LINKER']:
     OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
 
 if CONFIG['HAVE_CLOCK_MONOTONIC']:
     OS_LIBS += CONFIG['REALTIME_LIBS']
+
+JAR_MANIFESTS += ['jar.mn']
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -15,29 +15,32 @@
 #include "nsNetUtil.h"
 #include "nsCOMArray.h"
 #include "nsArrayEnumerator.h"
 #include "nsTArray.h"
 #include "nsReadableUtils.h"
 #include "nsILineInputStream.h"
 #include "nsIIDNService.h"
 #include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.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"
 #include "nsIEffectiveTLDService.h"
 #include "nsPIDOMWindow.h"
 #include "nsIDocument.h"
 #include "mozilla/net/NeckoMessageUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsReadLine.h"
 
 static nsPermissionManager *gPermissionManager = nullptr;
 
 using mozilla::dom::ContentParent;
 using mozilla::dom::ContentChild;
 using mozilla::unused; // ha!
 
 static bool
@@ -353,16 +356,22 @@ nsPermissionManager::AppClearDataObserve
 ////////////////////////////////////////////////////////////////////////////////
 // nsPermissionManager Implementation
 
 static const char kPermissionsFileName[] = "permissions.sqlite";
 #define HOSTS_SCHEMA_VERSION 3
 
 static const char kHostpermFileName[] = "hostperm.1";
 
+// Default permissions are read from a URL - this is the preference we read
+// to find that URL.
+static const char kDefaultsUrlPrefName[] = "permissions.manager.defaultsUrl";
+// If the pref above doesn't exist, the URL we use by default.
+static const char kDefaultsUrl[] = "resource://app/chrome/browser/default_permissions";
+
 static const char kPermissionChangeNotification[] = PERM_CHANGE_NOTIFICATION;
 
 NS_IMPL_ISUPPORTS(nsPermissionManager, nsIPermissionManager, nsIObserver, nsISupportsWeakReference)
 
 nsPermissionManager::nsPermissionManager()
  : mLargestID(0)
  , mIsShuttingDown(false)
 {
@@ -584,16 +593,18 @@ nsPermissionManager::InitDB(bool aRemove
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
     "UPDATE moz_hosts "
     "SET permission = ?2, expireType= ?3, expireTime = ?4 WHERE id = ?1"),
     getter_AddRefs(mStmtUpdate));
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // Always import default permissions.
+  ImportDefaults();
   // check whether to import or just read in the db
   if (tableExists)
     return Read();
 
   return Import();
 }
 
 // sets the schema version and creates the moz_hosts table.
@@ -738,16 +749,22 @@ nsPermissionManager::AddInternal(nsIPrin
     // changed and the expire type is time, otherwise, don't modify.  There's
     // no need to modify a permission that doesn't expire with time when the
     // only thing changed is the expire time.
     if (aPermission == oldPermissionEntry.mPermission &&
         aExpireType == oldPermissionEntry.mExpireType &&
         (aExpireType == nsIPermissionManager::EXPIRE_NEVER ||
          aExpireTime == oldPermissionEntry.mExpireTime))
       op = eOperationNone;
+    else if (oldPermissionEntry.mID == cIDPermissionIsDefault)
+      // The existing permission is one added as a default and the new permission
+      // doesn't exactly match so we are replacing the default.  This is true
+      // even if the new permission is UNKNOWN_ACTION (which means a "logical
+      // remove" of the default)
+      op = eOperationReplacingDefault;
     else if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
       op = eOperationRemoving;
     else
       op = eOperationChanging;
   }
 
   // do the work for adding, deleting, or changing a permission:
   // update the in-memory list, write to the db, and notify consumers.
@@ -864,16 +881,72 @@ nsPermissionManager::AddInternal(nsIPrin
                                       aPermission,
                                       aExpireType,
                                       aExpireTime,
                                       MOZ_UTF16("changed"));
       }
 
       break;
     }
+  case eOperationReplacingDefault:
+    {
+      // this is handling the case when we have an existing permission
+      // entry that was created as a "default" (and thus isn't in the DB) with
+      // an explicit permission (that may include UNKNOWN_ACTION.)
+      // Note we will *not* get here if we are replacing an already replaced
+      // default value - that is handled as eOperationChanging.
+
+      // So this is a hybrid of eOperationAdding (as we are writing a new entry
+      // to the DB) and eOperationChanging (as we are replacing the in-memory
+      // repr and sending a "changed" notification).
+
+      // We want a new ID even if not writing to the DB, so the modified entry
+      // in memory doesn't have the magic cIDPermissionIsDefault value.
+      id = ++mLargestID;
+
+      // The default permission being replaced can't have session expiry.
+      NS_ENSURE_TRUE(entry->GetPermissions()[index].mExpireType != nsIPermissionManager::EXPIRE_SESSION,
+                     NS_ERROR_UNEXPECTED);
+      // We don't support the new entry having any expiry - supporting that would
+      // make things far more complex and none of the permissions we set as a
+      // default support that.
+      NS_ENSURE_TRUE(aExpireType == EXPIRE_NEVER, NS_ERROR_UNEXPECTED);
+
+      // update the existing entry in memory.
+      entry->GetPermissions()[index].mID = id;
+      entry->GetPermissions()[index].mPermission = aPermission;
+      entry->GetPermissions()[index].mExpireType = aExpireType;
+      entry->GetPermissions()[index].mExpireTime = aExpireTime;
+
+      // If requested, create the entry in the DB.
+      if (aDBOperation == eWriteToDB) {
+        uint32_t appId;
+        rv = aPrincipal->GetAppId(&appId);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        bool isInBrowserElement;
+        rv = aPrincipal->GetIsInBrowserElement(&isInBrowserElement);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        UpdateDB(eOperationAdding, mStmtInsert, id, host, aType, aPermission, aExpireType, aExpireTime, appId, isInBrowserElement);
+      }
+
+      if (aNotifyOperation == eNotify) {
+        NotifyObserversWithPermission(host,
+                                      entry->GetKey()->mAppId,
+                                      entry->GetKey()->mIsInBrowserElement,
+                                      mTypeArray[typeIndex],
+                                      aPermission,
+                                      aExpireType,
+                                      aExpireTime,
+                                      MOZ_UTF16("changed"));
+      }
+
+    }
+    break;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPermissionManager::Remove(const nsACString &aHost,
                             const char       *aType)
@@ -939,16 +1012,20 @@ nsPermissionManager::CloseDB(bool aRebui
 
 nsresult
 nsPermissionManager::RemoveAllInternal(bool aNotifyObservers)
 {
   // Remove from memory and notify immediately. Since the in-memory
   // database is authoritative, we do not need confirmation from the
   // on-disk database to notify observers.
   RemoveAllFromMemory();
+
+  // Re-import the defaults
+  ImportDefaults();
+
   if (aNotifyObservers) {
     NotifyObservers(nullptr, MOZ_UTF16("cleared"));
   }
 
   // clear the db
   if (mDBConn) {
     nsCOMPtr<mozIStorageAsyncStatement> removeStmt;
     nsresult rv = mDBConn->
@@ -1257,16 +1334,23 @@ struct nsGetEnumeratorData
 static PLDHashOperator
 AddPermissionsToList(nsPermissionManager::PermissionHashKey* entry, void *arg)
 {
   nsGetEnumeratorData *data = static_cast<nsGetEnumeratorData *>(arg);
 
   for (uint32_t i = 0; i < entry->GetPermissions().Length(); ++i) {
     nsPermissionManager::PermissionEntry& permEntry = entry->GetPermissions()[i];
 
+    // given how "default" permissions work and the possibility of them being
+    // overridden with UNKNOWN_ACTION, we might see this value here - but we
+    // do *not* want to return them via the enumerator.
+    if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
+      continue;
+    }
+
     nsPermission *perm = new nsPermission(entry->GetKey()->mHost,
                                           entry->GetKey()->mAppId,
                                           entry->GetKey()->mIsInBrowserElement,
                                           data->types->ElementAt(permEntry.mType),
                                           permEntry.mPermission,
                                           permEntry.mExpireType,
                                           permEntry.mExpireTime);
 
@@ -1628,60 +1712,127 @@ nsPermissionManager::Read()
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 static const char kMatchTypeHost[] = "host";
 
+// Import() will read a file from the profile directory and add them to the
+// database before deleting the file - ie, this is a one-shot operation that
+// will not succeed on subsequent runs as the file imported from is removed.
 nsresult
 nsPermissionManager::Import()
 {
-  ENSURE_NOT_CHILD_PROCESS;
-
   nsresult rv;
 
   nsCOMPtr<nsIFile> permissionsFile;
   rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(permissionsFile));
   if (NS_FAILED(rv)) return rv;
 
   rv = permissionsFile->AppendNative(NS_LITERAL_CSTRING(kHostpermFileName));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIInputStream> fileInputStream;
   rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
                                   permissionsFile);
-  if (NS_FAILED(rv)) return rv;
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = _DoImport(fileInputStream, mDBConn);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // we successfully imported and wrote to the DB - delete the old file.
+  permissionsFile->Remove(false);
+  return NS_OK;
+}
 
-  nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
+// ImportDefaults will read a URL with default permissions and add them to the
+// in-memory copy of permissions.  The database is *not* written to.
+nsresult
+nsPermissionManager::ImportDefaults()
+{
+  // We allow prefs to override the default permissions URI, mainly as a hook
+  // for testing.
+  nsCString defaultsURL;
+  if (mozilla::Preferences::HasUserValue(kDefaultsUrlPrefName)) {
+    defaultsURL = mozilla::Preferences::GetCString(kDefaultsUrlPrefName);
+  } else {
+    defaultsURL = NS_LITERAL_CSTRING(kDefaultsUrl);
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsIIOService> ioservice =
+    do_GetService("@mozilla.org/network/io-service;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIURI> defaultsURI;
+  rv = NS_NewURI(getter_AddRefs(defaultsURI), defaultsURL,
+                 nullptr, nullptr, ioservice);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  nsCOMPtr<nsIChannel> channel;
+  rv = ioservice->NewChannelFromURI(defaultsURI, getter_AddRefs(channel));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIInputStream> inputStream;
+  rv = channel->Open(getter_AddRefs(inputStream));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = _DoImport(inputStream, nullptr);
+  inputStream->Close();
+  return rv;
+}
+
+// _DoImport reads the specified stream and adds the parsed elements.  If
+// |conn| is passed, the imported data will be written to the database, but if
+// |conn| is null the data will be added only to the in-memory copy of the
+// database.
+nsresult
+nsPermissionManager::_DoImport(nsIInputStream *inputStream, mozIStorageConnection *conn)
+{
+  ENSURE_NOT_CHILD_PROCESS;
+
+  nsresult rv;
   // start a transaction on the storage db, to optimize insertions.
   // transaction will automically commit on completion
-  mozStorageTransaction transaction(mDBConn, true);
+  // (note the transaction is a no-op if a null connection is passed)
+  mozStorageTransaction transaction(conn, true);
+
+  // The DB operation - we only try and write if a connection was passed.
+  DBOperationType operation = conn ? eWriteToDB : eNoDBOperation;
+  // and if no DB connection was passed we assume this is a "default" permission,
+  // so use the special ID which indicates this.
+  int64_t id = conn ? 0 : cIDPermissionIsDefault;
 
   /* format is:
    * matchtype \t type \t permission \t host
    * Only "host" is supported for matchtype
    * type is a string that identifies the type of permission (e.g. "cookie")
    * permission is an integer between 1 and 15
    */
 
-  nsAutoCString buffer;
+  // Ideally we'd do this with nsILineInputString, but this is called with an
+  // nsIInputStream that comes from a resource:// URI, which doesn't support
+  // that interface.  So NS_ReadLine to the rescue...
+  nsLineBuffer<char> lineBuffer;
+  nsCString line;
   bool isMore = true;
-  while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
-    if (buffer.IsEmpty() || buffer.First() == '#') {
+  do {
+    rv = NS_ReadLine(inputStream, &lineBuffer, line, &isMore);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (line.IsEmpty() || line.First() == '#') {
       continue;
     }
 
     nsTArray<nsCString> lineArray;
 
     // Split the line at tabs
-    ParseString(buffer, '\t', lineArray);
+    ParseString(line, '\t', lineArray);
 
     if (lineArray[0].EqualsLiteral(kMatchTypeHost) &&
         lineArray.Length() == 4) {
 
       nsresult error;
       uint32_t permission = lineArray[2].ToInteger(&error);
       if (NS_FAILED(error))
         continue;
@@ -1692,24 +1843,22 @@ nsPermissionManager::Import()
         if (NS_FAILED(rv))
           continue;
       }
 
       nsCOMPtr<nsIPrincipal> principal;
       nsresult rv = GetPrincipal(lineArray[3], getter_AddRefs(principal));
       NS_ENSURE_SUCCESS(rv, rv);
 
-      rv = AddInternal(principal, lineArray[1], permission, 0,
-                       nsIPermissionManager::EXPIRE_NEVER, 0, eDontNotify, eWriteToDB);
+      rv = AddInternal(principal, lineArray[1], permission, id,
+                       nsIPermissionManager::EXPIRE_NEVER, 0, eDontNotify, operation);
       NS_ENSURE_SUCCESS(rv, rv);
     }
-  }
 
-  // we're done importing - delete the old file
-  permissionsFile->Remove(false);
+  } while (isMore);
 
   return NS_OK;
 }
 
 nsresult
 nsPermissionManager::NormalizeToACE(nsCString &aHost)
 {
   // lazily init the IDN service
--- a/extensions/cookie/nsPermissionManager.h
+++ b/extensions/cookie/nsPermissionManager.h
@@ -6,17 +6,17 @@
 #ifndef nsPermissionManager_h__
 #define nsPermissionManager_h__
 
 #include "nsIPermissionManager.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsWeakReference.h"
 #include "nsCOMPtr.h"
-#include "nsIFile.h"
+#include "nsIInputStream.h"
 #include "nsTHashtable.h"
 #include "nsTArray.h"
 #include "nsString.h"
 #include "nsPermission.h"
 #include "nsHashKeys.h"
 #include "nsAutoPtr.h"
 #include "nsCOMArray.h"
 #include "nsDataHashtable.h"
@@ -170,29 +170,35 @@ public:
   static nsIPermissionManager* GetXPCOMSingleton();
   nsresult Init();
 
   // enums for AddInternal()
   enum OperationType {
     eOperationNone,
     eOperationAdding,
     eOperationRemoving,
-    eOperationChanging
+    eOperationChanging,
+    eOperationReplacingDefault
   };
 
   enum DBOperationType {
     eNoDBOperation,
     eWriteToDB
   };
 
   enum NotifyOperationType {
     eDontNotify,
     eNotify
   };
 
+  // A special value for a permission ID that indicates the ID was loaded as
+  // a default value.  These will never be written to the database, but may
+  // be overridden with an explicit permission (including UNKNOWN_ACTION)
+  static const int64_t cIDPermissionIsDefault = -1;
+
   nsresult AddInternal(nsIPrincipal* aPrincipal,
                        const nsAFlatCString &aType,
                        uint32_t aPermission,
                        int64_t aID,
                        uint32_t aExpireType,
                        int64_t  aExpireTime,
                        NotifyOperationType aNotifyOperation,
                        DBOperationType aDBOperation);
@@ -221,16 +227,18 @@ private:
                                 const char *aType,
                                 uint32_t   *aPermission,
                                 bool        aExactHostMatch,
                                 bool        aIncludingSession);
 
   nsresult InitDB(bool aRemoveFile);
   nsresult CreateTable();
   nsresult Import();
+  nsresult ImportDefaults();
+  nsresult _DoImport(nsIInputStream *inputStream, mozIStorageConnection *aConn);
   nsresult Read();
   void     NotifyObserversWithPermission(const nsACString &aHost,
                                          uint32_t          aAppId,
                                          bool              aIsInBrowserElement,
                                          const nsCString  &aType,
                                          uint32_t          aPermission,
                                          uint32_t          aExpireType,
                                          int64_t           aExpireTime,
new file mode 100644
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_defaults.js
@@ -0,0 +1,177 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// The origin we use in most of the tests.
+const TEST_ORIGIN = "example.org";
+const TEST_PERMISSION = "test-permission";
+
+function run_test() {
+  run_next_test();
+}
+
+add_task(function* do_test() {
+  // setup a profile.
+  do_get_profile();
+
+  // create a file in the temp directory with the defaults.
+  let file = do_get_tempdir();
+  file.append("test_default_permissions");
+
+  // write our test data to it.
+  let ostream = Cc["@mozilla.org/network/file-output-stream;1"].
+                createInstance(Ci.nsIFileOutputStream);
+  ostream.init(file, -1, 0666, 0);
+  let conv = Cc["@mozilla.org/intl/converter-output-stream;1"].
+             createInstance(Ci.nsIConverterOutputStream);
+  conv.init(ostream, "UTF-8", 0, 0);
+
+  conv.writeString("# this is a comment\n");
+  conv.writeString("\n"); // a blank line!
+  conv.writeString("host\t" + TEST_PERMISSION + "\t1\t" + TEST_ORIGIN + "\n");
+  ostream.close();
+
+  // Set the preference used by the permission manager so the file is read.
+  Services.prefs.setCharPref("permissions.manager.defaultsUrl", "file://" + file.path);
+
+  // initialize the permission manager service - it will read that default.
+  let pm = Cc["@mozilla.org/permissionmanager;1"].
+           getService(Ci.nsIPermissionManager);
+
+  // test the default permission was applied.
+  let permURI = NetUtil.newURI("http://" + TEST_ORIGIN);
+  let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(permURI);
+
+  do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+              pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+
+  // the permission should exist in the enumerator.
+  do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION, findCapabilityViaEnum());
+  // but should not have been written to the DB
+  yield checkCapabilityViaDB(null);
+
+  // remove all should not throw and the default should remain
+  pm.removeAll();
+
+  do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+              pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+
+  // Asking for this permission to be removed should result in that permission
+  // having UNKNOWN_ACTION
+  pm.removeFromPrincipal(principal, TEST_PERMISSION);
+  do_check_eq(Ci.nsIPermissionManager.UNKNOWN_ACTION,
+              pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+  // and we should have this UNKNOWN_ACTION reflected in the DB
+  yield checkCapabilityViaDB(Ci.nsIPermissionManager.UNKNOWN_ACTION);
+  // but the permission should *not* appear in the enumerator.
+  do_check_eq(null, findCapabilityViaEnum());
+
+  // and a subsequent RemoveAll should restore the default
+  pm.removeAll();
+
+  do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+              pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+  // and allow it to again be seen in the enumerator.
+  do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION, findCapabilityViaEnum());
+
+  // now explicitly add a permission - this too should override the default.
+  pm.addFromPrincipal(principal, TEST_PERMISSION, Ci.nsIPermissionManager.DENY_ACTION);
+
+  // it should be reflected in a permission check, in the enumerator and the DB
+  do_check_eq(Ci.nsIPermissionManager.DENY_ACTION,
+              pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+  do_check_eq(Ci.nsIPermissionManager.DENY_ACTION, findCapabilityViaEnum());
+  yield checkCapabilityViaDB(Ci.nsIPermissionManager.DENY_ACTION);
+
+  // explicitly add a different permission - in this case we are no longer
+  // replacing the default, but instead replacing the replacement!
+  pm.addFromPrincipal(principal, TEST_PERMISSION, Ci.nsIPermissionManager.PROMPT_ACTION);
+
+  // it should be reflected in a permission check, in the enumerator and the DB
+  do_check_eq(Ci.nsIPermissionManager.PROMPT_ACTION,
+              pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+  do_check_eq(Ci.nsIPermissionManager.PROMPT_ACTION, findCapabilityViaEnum());
+  yield checkCapabilityViaDB(Ci.nsIPermissionManager.PROMPT_ACTION);
+
+  // remove the temp file we created.
+  file.remove(false);
+});
+
+// use an enumerator to find the requested permission.  Returns the permission
+// value (ie, the "capability" in nsIPermission parlance) or null if it can't
+// be found.
+function findCapabilityViaEnum(host = TEST_ORIGIN, type = TEST_PERMISSION) {
+  let result = undefined;
+  let e = Services.perms.enumerator;
+  while (e.hasMoreElements()) {
+    let perm = e.getNext().QueryInterface(Ci.nsIPermission);
+    if (perm.host == host &&
+        perm.type == type) {
+      if (result !== undefined) {
+        // we've already found one previously - that's bad!
+        do_throw("enumerator found multiple entries");
+      }
+      result = perm.capability;
+    }
+  }
+  return result || null;
+}
+
+// A function to check the DB has the specified capability.  As the permission
+// manager uses async DB operations without a completion callback, the
+// distinct possibility exists that our checking of the DB will happen before
+// the permission manager update has completed - so we just retry a few times.
+// Returns a promise.
+function checkCapabilityViaDB(expected, host = TEST_ORIGIN, type = TEST_PERMISSION) {
+  let deferred = Promise.defer();
+  let count = 0;
+  let max = 20;
+  let do_check = () => {
+    let got = findCapabilityViaDB(host, type);
+    if (got == expected) {
+      // the do_check_eq() below will succeed - which is what we want.
+      do_check_eq(got, expected, "The database has the expected value");
+      deferred.resolve();
+      return;
+    }
+    // value isn't correct - see if we've retried enough
+    if (count++ == max) {
+      // the do_check_eq() below will fail - which is what we want.
+      do_check_eq(got, expected, "The database wasn't updated with the expected value");
+      deferred.resolve();
+      return;
+    }
+    // we can retry...
+    do_timeout(100, do_check);
+  }
+  do_check();
+  return deferred.promise;
+}
+
+// use the DB to find the requested permission.   Returns the permission
+// value (ie, the "capability" in nsIPermission parlance) or null if it can't
+// be found.
+function findCapabilityViaDB(host = TEST_ORIGIN, type = TEST_PERMISSION) {
+  let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+  file.append("permissions.sqlite");
+
+  let storage = Cc["@mozilla.org/storage/service;1"]
+                  .getService(Ci.mozIStorageService);
+
+  let connection = storage.openDatabase(file);
+
+  let query = connection.createStatement(
+      "SELECT permission FROM moz_hosts WHERE host = :host AND type = :type");
+  query.bindByName("host", host);
+  query.bindByName("type", type);
+
+  if (!query.executeStep()) {
+    // no row
+    return null;
+  }
+  let result = query.getInt32(0);
+  if (query.executeStep()) {
+    // this is bad - we never expect more than 1 row here.
+    do_throw("More than 1 row found!")
+  }
+  return result;
+}
--- a/extensions/cookie/test/unit/xpcshell.ini
+++ b/extensions/cookie/test/unit/xpcshell.ini
@@ -13,16 +13,17 @@ support-files =
 [test_cookies_privatebrowsing.js]
 [test_cookies_profile_close.js]
 [test_cookies_read.js]
 [test_cookies_sync_failure.js]
 [test_cookies_thirdparty.js]
 [test_cookies_thirdparty_session.js]
 [test_domain_eviction.js]
 [test_eviction.js]
+[test_permmanager_defaults.js]
 [test_permmanager_expiration.js]
 [test_permmanager_getPermissionObject.js]
 [test_permmanager_notifications.js]
 [test_permmanager_removeall.js]
 [test_permmanager_load_invalid_entries.js]
 skip-if = debug == true
 [test_permmanager_idn.js]
 [test_permmanager_subdomains.js]