Bug 681930, r=sicking.
authorBen Turner <bent.mozilla@gmail.com>
Fri, 26 Aug 2011 01:21:35 -0700
changeset 75938 6eeb384db995e17790a20c7122dc66d6eab7b92f
parent 75937 e5adec25155d6c697473762d464b03aa4879561e
child 75939 40861507b6c45809247c7ad503cb6609200b81a4
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewerssicking
bugs681930
milestone9.0a1
Bug 681930, r=sicking.
db/sqlite3/src/test_quota.c
dom/indexedDB/AsyncConnectionHelper.cpp
dom/indexedDB/CheckQuotaHelper.cpp
dom/indexedDB/IDBFactory.cpp
dom/indexedDB/IDBFactory.h
dom/indexedDB/IDBTransaction.cpp
dom/indexedDB/IndexedDatabaseManager.cpp
dom/indexedDB/IndexedDatabaseManager.h
storage/public/mozIStorageServiceQuotaManagement.idl
storage/src/mozStorageService.cpp
--- a/db/sqlite3/src/test_quota.c
+++ b/db/sqlite3/src/test_quota.c
@@ -26,16 +26,30 @@
 ** callback does enlarge the quota such that the total size of all
 ** files within the group is less than the new quota, then the write
 ** continues as if nothing had happened.
 */
 #include "sqlite3.h"
 #include <string.h>
 #include <assert.h>
 
+/*
+** For an build without mutexes, no-op the mutex calls.
+*/
+#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0
+#define sqlite3_mutex_alloc(X)    ((sqlite3_mutex*)8)
+#define sqlite3_mutex_free(X)
+#define sqlite3_mutex_enter(X)
+#define sqlite3_mutex_try(X)      SQLITE_OK
+#define sqlite3_mutex_leave(X)
+#define sqlite3_mutex_held(X)     ((void)(X),1)
+#define sqlite3_mutex_notheld(X)  ((void)(X),1)
+#endif /* SQLITE_THREADSAFE==0 */
+
+
 /************************ Object Definitions ******************************/
 
 /* Forward declaration of all object types */
 typedef struct quotaGroup quotaGroup;
 typedef struct quotaConn quotaConn;
 typedef struct quotaFile quotaFile;
 
 /*
@@ -76,16 +90,17 @@ struct quotaGroup {
 ** has a unique instance of this object and multiple open connections
 ** to the same file each point to a single instance of this object.
 */
 struct quotaFile {
   char *zFilename;                /* Name of this file */
   quotaGroup *pGroup;             /* Quota group to which this file belongs */
   sqlite3_int64 iSize;            /* Current size of this file */
   int nRef;                       /* Number of times this file is open */
+  int deleteOnClose;              /* True to delete this file when it closes */
   quotaFile *pNext, **ppPrev;     /* Linked list of files in the same group */
 };
 
 /*
 ** An instance of the following object represents each open connection
 ** to a file that participates in quota tracking.  This object is a 
 ** subclass of sqlite3_file.  The sqlite3_file object for the underlying
 ** VFS is appended to this structure.
@@ -145,22 +160,55 @@ static struct {
 /************************* Utility Routines *********************************/
 /*
 ** Acquire and release the mutex used to serialize access to the
 ** list of quotaGroups.
 */
 static void quotaEnter(void){ sqlite3_mutex_enter(gQuota.pMutex); }
 static void quotaLeave(void){ sqlite3_mutex_leave(gQuota.pMutex); }
 
+/* Count the number of open files in a quotaGroup 
+*/
+static int quotaGroupOpenFileCount(quotaGroup *pGroup){
+  int N = 0;
+  quotaFile *pFile = pGroup->pFiles;
+  while( pFile ){
+    if( pFile->nRef ) N++;
+    pFile = pFile->pNext;
+  }
+  return N;
+}
+
+/* Remove a file from a quota group.
+*/
+static void quotaRemoveFile(quotaFile *pFile){
+  quotaGroup *pGroup = pFile->pGroup;
+  pGroup->iSize -= pFile->iSize;
+  *pFile->ppPrev = pFile->pNext;
+  if( pFile->pNext ) pFile->pNext->ppPrev = pFile->ppPrev;
+  sqlite3_free(pFile);
+}
+
+/* Remove all files from a quota group.  It is always the case that
+** all files will be closed when this routine is called.
+*/
+static void quotaRemoveAllFiles(quotaGroup *pGroup){
+  while( pGroup->pFiles ){
+    assert( pGroup->pFiles->nRef==0 );
+    quotaRemoveFile(pGroup->pFiles);
+  }
+}
+
 
 /* If the reference count and threshold for a quotaGroup are both
 ** zero, then destroy the quotaGroup.
 */
 static void quotaGroupDeref(quotaGroup *pGroup){
-  if( pGroup->pFiles==0 && pGroup->iLimit==0 ){
+  if( pGroup->iLimit==0 && quotaGroupOpenFileCount(pGroup)==0 ){
+    quotaRemoveAllFiles(pGroup);
     *pGroup->ppPrev = pGroup->pNext;
     if( pGroup->pNext ) pGroup->pNext->ppPrev = pGroup->ppPrev;
     if( pGroup->xDestroy ) pGroup->xDestroy(pGroup->pArg);
     sqlite3_free(pGroup);
   }
 }
 
 /*
@@ -257,16 +305,27 @@ static quotaGroup *quotaGroupFind(const 
 /* Translate an sqlite3_file* that is really a quotaConn* into
 ** the sqlite3_file* for the underlying original VFS.
 */
 static sqlite3_file *quotaSubOpen(sqlite3_file *pConn){
   quotaConn *p = (quotaConn*)pConn;
   return (sqlite3_file*)&p[1];
 }
 
+/* Find a file in a quota group and return a pointer to that file.
+** Return NULL if the file is not in the group.
+*/
+static quotaFile *quotaFindFile(quotaGroup *pGroup, const char *zName){
+  quotaFile *pFile = pGroup->pFiles;
+  while( pFile && strcmp(pFile->zFilename, zName)!=0 ){
+    pFile = pFile->pNext;
+  }
+  return pFile;
+}
+
 /************************* VFS Method Wrappers *****************************/
 /*
 ** This is the xOpen method used for the "quota" VFS.
 **
 ** Most of the work is done by the underlying original VFS.  This method
 ** simply links the new file into the appropriate quota group if it is a
 ** file that needs to be tracked.
 */
@@ -300,69 +359,109 @@ static int quotaOpen(
     rc = pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags);
   }else{
     /* If we get to this point, it means the file needs to be quota tracked.
     */
     pQuotaOpen = (quotaConn*)pConn;
     pSubOpen = quotaSubOpen(pConn);
     rc = pOrigVfs->xOpen(pOrigVfs, zName, pSubOpen, flags, pOutFlags);
     if( rc==SQLITE_OK ){
-      for(pFile=pGroup->pFiles; pFile && strcmp(pFile->zFilename, zName);
-          pFile=pFile->pNext){}
+      pFile = quotaFindFile(pGroup, zName);
       if( pFile==0 ){
         int nName = strlen(zName);
         pFile = (quotaFile *)sqlite3_malloc( sizeof(*pFile) + nName + 1 );
         if( pFile==0 ){
           quotaLeave();
           pSubOpen->pMethods->xClose(pSubOpen);
           return SQLITE_NOMEM;
         }
         memset(pFile, 0, sizeof(*pFile));
         pFile->zFilename = (char*)&pFile[1];
         memcpy(pFile->zFilename, zName, nName+1);
         pFile->pNext = pGroup->pFiles;
         if( pGroup->pFiles ) pGroup->pFiles->ppPrev = &pFile->pNext;
         pFile->ppPrev = &pGroup->pFiles;
         pGroup->pFiles = pFile;
         pFile->pGroup = pGroup;
+        pFile->deleteOnClose = (flags & SQLITE_OPEN_DELETEONCLOSE)!=0;
       }
       pFile->nRef++;
       pQuotaOpen->pFile = pFile;
       if( pSubOpen->pMethods->iVersion==1 ){
         pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV1;
       }else{
         pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV2;
       }
     }
   }
   quotaLeave();
   return rc;
 }
 
+/*
+** This is the xDelete method used for the "quota" VFS.
+**
+** If the file being deleted is part of the quota group, then reduce
+** the size of the quota group accordingly.  And remove the file from
+** the set of files in the quota group.
+*/
+static int quotaDelete(
+  sqlite3_vfs *pVfs,          /* The quota VFS */
+  const char *zName,          /* Name of file to be deleted */
+  int syncDir                 /* Do a directory sync after deleting */
+){
+  int rc;                                    /* Result code */         
+  quotaFile *pFile;                          /* Files in the quota */
+  quotaGroup *pGroup;                        /* The group file belongs to */
+  sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs;   /* Real VFS */
+
+  /* Do the actual file delete */
+  rc = pOrigVfs->xDelete(pOrigVfs, zName, syncDir);
+
+  /* If the file just deleted is a member of a quota group, then remove
+  ** it from that quota group.
+  */
+  if( rc==SQLITE_OK ){
+    quotaEnter();
+    pGroup = quotaGroupFind(zName);
+    if( pGroup ){
+      pFile = quotaFindFile(pGroup, zName);
+      if( pFile ){
+        if( pFile->nRef ){
+          pFile->deleteOnClose = 1;
+        }else{
+          quotaRemoveFile(pFile);
+          quotaGroupDeref(pGroup);
+        }
+      }
+    }
+    quotaLeave();
+  }
+  return rc;
+}
+
+
 /************************ I/O Method Wrappers *******************************/
 
 /* xClose requests get passed through to the original VFS.  But we
 ** also have to unlink the quotaConn from the quotaFile and quotaGroup.
 ** The quotaFile and/or quotaGroup are freed if they are no longer in use.
 */
 static int quotaClose(sqlite3_file *pConn){
   quotaConn *p = (quotaConn*)pConn;
   quotaFile *pFile = p->pFile;
   sqlite3_file *pSubOpen = quotaSubOpen(pConn);
   int rc;
   rc = pSubOpen->pMethods->xClose(pSubOpen);
   quotaEnter();
   pFile->nRef--;
   if( pFile->nRef==0 ){
     quotaGroup *pGroup = pFile->pGroup;
-    pGroup->iSize -= pFile->iSize;
-    if( pFile->pNext ) pFile->pNext->ppPrev = pFile->ppPrev;
-    *pFile->ppPrev = pFile->pNext;
+    if( pFile->deleteOnClose ) quotaRemoveFile(pFile);
     quotaGroupDeref(pGroup);
-    sqlite3_free(pFile);
   }
   quotaLeave();
   return rc;
 }
 
 /* Pass xRead requests directory thru to the original VFS without
 ** further processing.
 */
@@ -567,16 +666,17 @@ int sqlite3_quota_initialize(const char 
   gQuota.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
   if( !gQuota.pMutex ){
     return SQLITE_NOMEM;
   }
   gQuota.isInitialized = 1;
   gQuota.pOrigVfs = pOrigVfs;
   gQuota.sThisVfs = *pOrigVfs;
   gQuota.sThisVfs.xOpen = quotaOpen;
+  gQuota.sThisVfs.xDelete = quotaDelete;
   gQuota.sThisVfs.szOsFile += sizeof(quotaConn);
   gQuota.sThisVfs.zName = "quota";
   gQuota.sIoMethodsV1.iVersion = 1;
   gQuota.sIoMethodsV1.xClose = quotaClose;
   gQuota.sIoMethodsV1.xRead = quotaRead;
   gQuota.sIoMethodsV1.xWrite = quotaWrite;
   gQuota.sIoMethodsV1.xTruncate = quotaTruncate;
   gQuota.sIoMethodsV1.xSync = quotaSync;
@@ -598,29 +698,30 @@ int sqlite3_quota_initialize(const char 
 }
 
 /*
 ** Shutdown the quota system.
 **
 ** All SQLite database connections must be closed before calling this
 ** routine.
 **
-** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly one while
+** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once while
 ** shutting down in order to free all remaining quota groups.
 */
 int sqlite3_quota_shutdown(void){
   quotaGroup *pGroup;
   if( gQuota.isInitialized==0 ) return SQLITE_MISUSE;
   for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){
-    if( pGroup->pFiles ) return SQLITE_MISUSE;
+    if( quotaGroupOpenFileCount(pGroup)>0 ) return SQLITE_MISUSE;
   }
   while( gQuota.pGroup ){
     pGroup = gQuota.pGroup;
     gQuota.pGroup = pGroup->pNext;
     pGroup->iLimit = 0;
+    assert( quotaGroupOpenFileCount(pGroup)==0 );
     quotaGroupDeref(pGroup);
   }
   gQuota.isInitialized = 0;
   sqlite3_mutex_free(gQuota.pMutex);
   sqlite3_vfs_unregister(&gQuota.sThisVfs);
   memset(&gQuota, 0, sizeof(gQuota));
   return SQLITE_OK;
 }
@@ -689,16 +790,54 @@ int sqlite3_quota_set(
   }
   pGroup->pArg = pArg;
   pGroup->xDestroy = xDestroy;
   quotaGroupDeref(pGroup);
   quotaLeave();
   return SQLITE_OK;
 }
 
+/*
+** Bring the named file under quota management.  Or if it is already under
+** management, update its size.
+*/
+int sqlite3_quota_file(const char *zFilename){
+  char *zFull;
+  sqlite3_file *fd;
+  int rc;
+  int outFlags = 0;
+  sqlite3_int64 iSize;
+  fd = (sqlite3_file*)
+    sqlite3_malloc(gQuota.sThisVfs.szOsFile + gQuota.sThisVfs.mxPathname+1);
+  if( fd==0 ) return SQLITE_NOMEM;
+  zFull = gQuota.sThisVfs.szOsFile + (char*)fd;
+  rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename,
+                                      gQuota.sThisVfs.mxPathname+1, zFull);
+  if( rc==SQLITE_OK ){
+    rc = quotaOpen(&gQuota.sThisVfs, zFull, fd, 
+                   SQLITE_OPEN_READONLY | SQLITE_OPEN_MAIN_DB, &outFlags);
+  }
+  if( rc==SQLITE_OK ){
+    fd->pMethods->xFileSize(fd, &iSize);
+    fd->pMethods->xClose(fd);
+  }else if( rc==SQLITE_CANTOPEN ){
+    quotaGroup *pGroup;
+    quotaFile *pFile;
+    quotaEnter();
+    pGroup = quotaGroupFind(zFull);
+    if( pGroup ){
+      pFile = quotaFindFile(pGroup, zFull);
+      if( pFile ) quotaRemoveFile(pFile);
+    }
+    quotaLeave();
+  }
+  sqlite3_free(fd);
+  return rc;
+}
+
   
 /***************************** Test Code ***********************************/
 #ifdef SQLITE_TEST
 #include <tcl.h>
 
 /*
 ** Argument passed to a TCL quota-over-limit callback.
 */
@@ -866,16 +1005,42 @@ static int test_quota_set(
   /* Invoke sqlite3_quota_set() */
   rc = sqlite3_quota_set(zPattern, iLimit, xCallback, (void*)p, xDestroy);
 
   Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
   return TCL_OK;
 }
 
 /*
+** tclcmd: sqlite3_quota_file FILENAME
+*/
+static int test_quota_file(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  const char *zFilename;          /* File pattern to configure */
+  int rc;                         /* Value returned by quota_file() */
+
+  /* Process arguments */
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
+    return TCL_ERROR;
+  }
+  zFilename = Tcl_GetString(objv[1]);
+
+  /* Invoke sqlite3_quota_file() */
+  rc = sqlite3_quota_file(zFilename);
+
+  Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
+  return TCL_OK;
+}
+
+/*
 ** tclcmd:  sqlite3_quota_dump
 */
 static int test_quota_dump(
   void * clientData,
   Tcl_Interp *interp,
   int objc,
   Tcl_Obj *CONST objv[]
 ){
@@ -898,16 +1063,18 @@ static int test_quota_dump(
     for(pFile=pGroup->pFiles; pFile; pFile=pFile->pNext){
       pFileTerm = Tcl_NewObj();
       Tcl_ListObjAppendElement(interp, pFileTerm,
             Tcl_NewStringObj(pFile->zFilename, -1));
       Tcl_ListObjAppendElement(interp, pFileTerm,
             Tcl_NewWideIntObj(pFile->iSize));
       Tcl_ListObjAppendElement(interp, pFileTerm,
             Tcl_NewWideIntObj(pFile->nRef));
+      Tcl_ListObjAppendElement(interp, pFileTerm,
+            Tcl_NewWideIntObj(pFile->deleteOnClose));
       Tcl_ListObjAppendElement(interp, pGroupTerm, pFileTerm);
     }
     Tcl_ListObjAppendElement(interp, pResult, pGroupTerm);
   }
   quotaLeave();
   Tcl_SetObjResult(interp, pResult);
   return TCL_OK;
 }
@@ -920,16 +1087,17 @@ static int test_quota_dump(
 int Sqlitequota_Init(Tcl_Interp *interp){
   static struct {
      char *zName;
      Tcl_ObjCmdProc *xProc;
   } aCmd[] = {
     { "sqlite3_quota_initialize", test_quota_initialize },
     { "sqlite3_quota_shutdown", test_quota_shutdown },
     { "sqlite3_quota_set", test_quota_set },
+    { "sqlite3_quota_file", test_quota_file },
     { "sqlite3_quota_dump", test_quota_dump },
   };
   int i;
 
   for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
     Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
   }
 
--- a/dom/indexedDB/AsyncConnectionHelper.cpp
+++ b/dom/indexedDB/AsyncConnectionHelper.cpp
@@ -41,18 +41,18 @@
 
 #include "mozilla/storage.h"
 #include "nsComponentManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsProxyRelease.h"
 #include "nsThreadUtils.h"
 
 #include "IDBEvents.h"
-#include "IDBFactory.h"
 #include "IDBTransaction.h"
+#include "IndexedDatabaseManager.h"
 #include "TransactionThreadPool.h"
 
 using mozilla::TimeStamp;
 using mozilla::TimeDuration;
 
 USING_INDEXEDDB_NAMESPACE
 
 namespace {
@@ -240,30 +240,30 @@ AsyncConnectionHelper::Run()
     if (NS_SUCCEEDED(rv)) {
       mStartTime = TimeStamp::Now();
     }
   }
 
   if (NS_SUCCEEDED(rv)) {
     bool hasSavepoint = false;
     if (mDatabase) {
-      IDBFactory::SetCurrentDatabase(mDatabase);
+      IndexedDatabaseManager::SetCurrentDatabase(mDatabase);
 
       // Make the first savepoint.
       if (mTransaction) {
         if (!(hasSavepoint = mTransaction->StartSavepoint())) {
           NS_WARNING("Failed to make savepoint!");
         }
       }
     }
 
     mResultCode = DoDatabaseWork(connection);
 
     if (mDatabase) {
-      IDBFactory::SetCurrentDatabase(nsnull);
+      IndexedDatabaseManager::SetCurrentDatabase(nsnull);
 
       // Release or roll back the savepoint depending on the error code.
       if (hasSavepoint) {
         NS_ASSERTION(mTransaction, "Huh?!");
         if (NS_SUCCEEDED(mResultCode)) {
           mTransaction->ReleaseSavepoint();
         }
         else {
--- a/dom/indexedDB/CheckQuotaHelper.cpp
+++ b/dom/indexedDB/CheckQuotaHelper.cpp
@@ -47,17 +47,17 @@
 #include "nsIURI.h"
 #include "nsXULAppAPI.h"
 
 #include "nsContentUtils.h"
 #include "nsNetUtil.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Services.h"
 
-#include "IDBFactory.h"
+#include "IndexedDatabaseManager.h"
 
 #define PERMISSION_INDEXEDDB_UNLIMITED "indexedDB-unlimited"
 
 #define TOPIC_QUOTA_PROMPT "indexedDB-quota-prompt"
 #define TOPIC_QUOTA_RESPONSE "indexedDB-quota-response"
 #define TOPIC_QUOTA_CANCEL "indexedDB-quota-cancel"
 
 USING_INDEXEDDB_NAMESPACE
@@ -197,18 +197,17 @@ CheckQuotaHelper::Run()
   
       rv = permissionManager->Add(uri, PERMISSION_INDEXEDDB_UNLIMITED,
                                   mPromptResult,
                                   nsIPermissionManager::EXPIRE_NEVER, 0);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
   else if (mPromptResult == nsIPermissionManager::UNKNOWN_ACTION) {
-    PRUint32 quota = IDBFactory::GetIndexedDBQuota();
-    NS_ASSERTION(quota, "Shouldn't get here if quota is disabled!");
+    PRUint32 quota = IndexedDatabaseManager::GetIndexedDBQuotaMB();
 
     nsString quotaString;
     quotaString.AppendInt(quota);
 
     nsCOMPtr<nsIObserverService> obs = GetObserverService();
     NS_ENSURE_STATE(obs);
 
     // We have to watch to make sure that the window doesn't go away without
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -61,71 +61,27 @@
 
 #include "AsyncConnectionHelper.h"
 #include "CheckPermissionsHelper.h"
 #include "DatabaseInfo.h"
 #include "IDBDatabase.h"
 #include "IDBKeyRange.h"
 #include "IndexedDatabaseManager.h"
 #include "LazyIdleThread.h"
-#include "nsIObserverService.h"
-#include "mozilla/Preferences.h"
 
 using namespace mozilla;
 
-#define PREF_INDEXEDDB_QUOTA "dom.indexedDB.warningQuota"
-
-// megabytes
-#define DEFAULT_QUOTA 50
-
-#define BAD_TLS_INDEX (PRUintn)-1
-
 #define DB_SCHEMA_VERSION 4
 
 USING_INDEXEDDB_NAMESPACE
 
 namespace {
 
 GeckoProcessType gAllowedProcessType = GeckoProcessType_Invalid;
 
-PRUintn gCurrentDatabaseIndex = BAD_TLS_INDEX;
-
-PRInt32 gIndexedDBQuota = DEFAULT_QUOTA;
-
-class QuotaCallback : public mozIStorageQuotaCallback
-{
-public:
-  NS_DECL_ISUPPORTS
-
-  NS_IMETHOD
-  QuotaExceeded(const nsACString& aFilename,
-                PRInt64 aCurrentSizeLimit,
-                PRInt64 aCurrentTotalSize,
-                nsISupports* aUserData,
-                PRInt64* _retval)
-  {
-    NS_ASSERTION(gCurrentDatabaseIndex != BAD_TLS_INDEX,
-                 "This should be impossible!");
-
-    IDBDatabase* database =
-      static_cast<IDBDatabase*>(PR_GetThreadPrivate(gCurrentDatabaseIndex));
-
-    if (database && database->IsQuotaDisabled()) {
-      *_retval = 0;
-      return NS_OK;
-    }
-
-    return NS_ERROR_FAILURE;
-  }
-};
-
-NS_IMPL_THREADSAFE_ISUPPORTS1(QuotaCallback, mozIStorageQuotaCallback)
-
-const PRUint32 kDefaultThreadTimeoutMS = 30000;
-
 struct ObjectStoreInfoMap
 {
   ObjectStoreInfoMap()
   : id(LL_MININT), info(nsnull) { }
 
   PRInt64 id;
   ObjectStoreInfo* info;
 };
@@ -351,86 +307,32 @@ CreateMetaData(mozIStorageConnection* aC
   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("dataVersion"),
                              JS_STRUCTURED_CLONE_VERSION);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return stmt->Execute();
 }
 
 nsresult
-CreateDatabaseConnection(const nsACString& aASCIIOrigin,
-                         const nsAString& aName,
-                         nsAString& aDatabaseFilePath,
-                         mozIStorageConnection** aConnection)
+GetDatabaseFile(const nsACString& aASCIIOrigin,
+                const nsAString& aName,
+                nsIFile** aDatabaseFile)
 {
-  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!aASCIIOrigin.IsEmpty() && !aName.IsEmpty(), "Bad arguments!");
 
-  aDatabaseFilePath.Truncate();
-
-  nsCOMPtr<nsIFile> dbDirectory;
-  nsresult rv = IDBFactory::GetDirectory(getter_AddRefs(dbDirectory));
-
-  PRBool exists;
-  rv = dbDirectory->Exists(&exists);
+  nsCOMPtr<nsIFile> dbFile;
+  nsresult rv = IDBFactory::GetDirectory(getter_AddRefs(dbFile));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (exists) {
-    PRBool isDirectory;
-    rv = dbDirectory->IsDirectory(&isDirectory);
-    NS_ENSURE_SUCCESS(rv, rv);
-    NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED);
-  }
-  else {
-    rv = dbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
   NS_ConvertASCIItoUTF16 originSanitized(aASCIIOrigin);
   originSanitized.ReplaceChar(":/", '+');
 
-  rv = dbDirectory->Append(originSanitized);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = dbDirectory->Exists(&exists);
+  rv = dbFile->Append(originSanitized);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (exists) {
-    PRBool isDirectory;
-    rv = dbDirectory->IsDirectory(&isDirectory);
-    NS_ENSURE_SUCCESS(rv, rv);
-    NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED);
-  }
-  else {
-    rv = dbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  nsCOMPtr<nsIFile> dbFile;
-  rv = dbDirectory->Clone(getter_AddRefs(dbFile));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<mozIStorageServiceQuotaManagement> ss =
-    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
-  NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE);
-
-  rv = dbDirectory->Append(NS_LITERAL_STRING("*"));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCString pattern;
-  rv = dbDirectory->GetNativePath(pattern);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (gIndexedDBQuota > 0) {
-    PRUint64 quota = PRUint64(gIndexedDBQuota) * 1024 * 1024;
-    nsRefPtr<QuotaCallback> callback(new QuotaCallback());
-    rv = ss->SetQuotaForFilenamePattern(pattern, quota, callback, nsnull);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
   nsAutoString filename;
   filename.AppendInt(HashString(aName));
 
   nsCString escapedName;
   if (!NS_Escape(NS_ConvertUTF16toUTF8(aName), escapedName, url_XPAlphas)) {
     NS_WARNING("Can't escape database name!");
     return NS_ERROR_UNEXPECTED;
   }
@@ -449,75 +351,95 @@ CreateDatabaseConnection(const nsACStrin
   }
 
   filename.Append(NS_ConvertASCIItoUTF16(substring));
   filename.AppendLiteral(".sqlite");
 
   rv = dbFile->Append(filename);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = dbFile->Exists(&exists);
+  dbFile.forget(aDatabaseFile);
+  return NS_OK;
+}
+
+nsresult
+CreateDatabaseConnection(const nsAString& aName,
+                         nsIFile* aDBFile,
+                         mozIStorageConnection** aConnection)
+{
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+  nsCOMPtr<nsIFile> dbDirectory;
+  nsresult rv = aDBFile->GetParent(getter_AddRefs(dbDirectory));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  NS_NAMED_LITERAL_CSTRING(quota, "quota");
+  PRBool exists;
+  rv = aDBFile->Exists(&exists);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  NS_NAMED_LITERAL_CSTRING(quotaVFSName, "quota");
+
+  nsCOMPtr<mozIStorageServiceQuotaManagement> ss =
+    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE);
 
   nsCOMPtr<mozIStorageConnection> connection;
-  rv = ss->OpenDatabaseWithVFS(dbFile, quota, getter_AddRefs(connection));
+  rv = ss->OpenDatabaseWithVFS(aDBFile, quotaVFSName,
+                               getter_AddRefs(connection));
   if (rv == NS_ERROR_FILE_CORRUPTED) {
     // Nuke the database file.  The web services can recreate their data.
-    rv = dbFile->Remove(PR_FALSE);
+    rv = aDBFile->Remove(PR_FALSE);
     NS_ENSURE_SUCCESS(rv, rv);
 
     exists = PR_FALSE;
 
-    rv = ss->OpenDatabaseWithVFS(dbFile, quota, getter_AddRefs(connection));
+    rv = ss->OpenDatabaseWithVFS(aDBFile, quotaVFSName,
+                                 getter_AddRefs(connection));
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Check to make sure that the database schema is correct.
   PRInt32 schemaVersion;
   rv = connection->GetSchemaVersion(&schemaVersion);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (schemaVersion != DB_SCHEMA_VERSION) {
     if (exists) {
       // If the connection is not at the right schema version, nuke it.
-      rv = dbFile->Remove(PR_FALSE);
+      rv = aDBFile->Remove(PR_FALSE);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      rv = ss->OpenDatabaseWithVFS(dbFile, quota, getter_AddRefs(connection));
+      rv = ss->OpenDatabaseWithVFS(aDBFile, quotaVFSName,
+                                   getter_AddRefs(connection));
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     rv = CreateTables(connection);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = CreateMetaData(connection, aName);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Check to make sure that the database schema is correct again.
   NS_ASSERTION(NS_SUCCEEDED(connection->GetSchemaVersion(&schemaVersion)) &&
                schemaVersion == DB_SCHEMA_VERSION,
                "CreateTables failed!");
 
-  // Turn on foreign key constraints in debug builds to catch bugs!
+  // Turn on foreign key constraints.
   rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "PRAGMA foreign_keys = ON;"
   ));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = dbFile->GetPath(aDatabaseFilePath);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   connection.forget(aConnection);
   return NS_OK;
 }
 
-} // anonyomous namespace
+} // anonymous namespace
 
 IDBFactory::IDBFactory()
 {
   IDBFactory::NoteUsedByProcessType(XRE_GetProcessType());
 }
 
 // static
 already_AddRefed<nsIIDBFactory>
@@ -526,28 +448,16 @@ IDBFactory::Create(nsPIDOMWindow* aWindo
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aWindow, "Must have a window!");
 
   if (aWindow->IsOuterWindow()) {
     aWindow = aWindow->GetCurrentInnerWindow();
   }
   NS_ENSURE_TRUE(aWindow, nsnull);
 
-  if (gCurrentDatabaseIndex == BAD_TLS_INDEX) {
-    // First time we're creating a database.
-    if (PR_NewThreadPrivateIndex(&gCurrentDatabaseIndex, NULL) != PR_SUCCESS) {
-      NS_ERROR("PR_NewThreadPrivateIndex failed!");
-      gCurrentDatabaseIndex = BAD_TLS_INDEX;
-      return nsnull;
-    }
-
-    Preferences::AddIntVarCache(&gIndexedDBQuota, PREF_INDEXEDDB_QUOTA,
-                                DEFAULT_QUOTA);
-  }
-
   nsRefPtr<IDBFactory> factory = new IDBFactory();
 
   factory->mWindow = do_GetWeakReference(aWindow);
   NS_ENSURE_TRUE(factory->mWindow, nsnull);
 
   return factory.forget();
 }
 
@@ -593,49 +503,16 @@ IDBFactory::GetConnection(const nsAStrin
     "PRAGMA foreign_keys = ON;"
   ));
   NS_ENSURE_SUCCESS(rv, nsnull);
 
   return connection.forget();
 }
 
 // static
-bool
-IDBFactory::SetCurrentDatabase(IDBDatabase* aDatabase)
-{
-  NS_ASSERTION(gCurrentDatabaseIndex != BAD_TLS_INDEX,
-               "This should have been set already!");
-
-#ifdef DEBUG
-  if (aDatabase) {
-    NS_ASSERTION(!PR_GetThreadPrivate(gCurrentDatabaseIndex),
-                 "Someone forgot to unset gCurrentDatabaseIndex!");
-  }
-  else {
-    NS_ASSERTION(PR_GetThreadPrivate(gCurrentDatabaseIndex),
-                 "Someone forgot to set gCurrentDatabaseIndex!");
-  }
-#endif
-
-  if (PR_SetThreadPrivate(gCurrentDatabaseIndex, aDatabase) != PR_SUCCESS) {
-    NS_WARNING("Failed to set gCurrentDatabaseIndex!");
-    return false;
-  }
-
-  return true;
-}
-
-// static
-PRUint32
-IDBFactory::GetIndexedDBQuota()
-{
-  return PRUint32(NS_MAX(gIndexedDBQuota, 0));
-}
-
-// static
 void
 IDBFactory::NoteUsedByProcessType(GeckoProcessType aProcessType)
 {
   if (gAllowedProcessType == GeckoProcessType_Invalid) {
     gAllowedProcessType = aProcessType;
   } else if (aProcessType != gAllowedProcessType) {
     NS_RUNTIMEABORT("More than one process type is accessing IndexedDB!");
   }
@@ -931,19 +808,50 @@ OpenDatabaseHelper::DoDatabaseWork(mozIS
   }
 #endif
   NS_ASSERTION(!aConnection, "Huh?!");
 
   if (IndexedDatabaseManager::IsShuttingDown()) {
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
+  nsCOMPtr<nsIFile> dbFile;
+  nsresult rv = GetDatabaseFile(mASCIIOrigin, mName, getter_AddRefs(dbFile));
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  rv = dbFile->GetPath(mDatabaseFilePath);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  nsCOMPtr<nsIFile> dbDirectory;
+  rv = dbFile->GetParent(getter_AddRefs(dbDirectory));
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  PRBool exists;
+  rv = dbDirectory->Exists(&exists);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (exists) {
+    PRBool isDirectory;
+    rv = dbDirectory->IsDirectory(&isDirectory);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+    NS_ENSURE_TRUE(isDirectory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  }
+  else {
+    rv = dbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  }
+
+  IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
+  NS_ASSERTION(mgr, "This should never be null!");
+
+  rv = mgr->EnsureQuotaManagementForDirectory(dbDirectory);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
   nsCOMPtr<mozIStorageConnection> connection;
-  nsresult rv = CreateDatabaseConnection(mASCIIOrigin, mName, mDatabaseFilePath,
-                                         getter_AddRefs(connection));
+  rv = CreateDatabaseConnection(mName, dbFile, getter_AddRefs(connection));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   // Get the data version.
   nsCOMPtr<mozIStorageStatement> stmt;
   rv = connection->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT dataVersion "
     "FROM database"
   ), getter_AddRefs(stmt));
--- a/dom/indexedDB/IDBFactory.h
+++ b/dom/indexedDB/IDBFactory.h
@@ -63,22 +63,16 @@ public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIIDBFACTORY
 
   static already_AddRefed<nsIIDBFactory> Create(nsPIDOMWindow* aWindow);
 
   static already_AddRefed<mozIStorageConnection>
   GetConnection(const nsAString& aDatabaseFilePath);
 
-  static bool
-  SetCurrentDatabase(IDBDatabase* aDatabase);
-
-  static PRUint32
-  GetIndexedDBQuota();
-
   // Called when a process uses an IndexedDB factory. We only allow
   // a single process type to use IndexedDB - the chrome/single process
   // in Firefox, and the child process in Fennec - so access by more
   // than one process type is a very serious error.
   static void
   NoteUsedByProcessType(GeckoProcessType aProcessType);
 
   static nsresult
--- a/dom/indexedDB/IDBTransaction.cpp
+++ b/dom/indexedDB/IDBTransaction.cpp
@@ -49,16 +49,17 @@
 #include "nsThreadUtils.h"
 
 #include "AsyncConnectionHelper.h"
 #include "DatabaseInfo.h"
 #include "IDBCursor.h"
 #include "IDBEvents.h"
 #include "IDBFactory.h"
 #include "IDBObjectStore.h"
+#include "IndexedDatabaseManager.h"
 #include "TransactionThreadPool.h"
 
 #define SAVEPOINT_NAME "savepoint"
 
 USING_INDEXEDDB_NAMESPACE
 
 namespace {
 
@@ -969,17 +970,17 @@ CommitHelper::Run()
   }
 
   IDBDatabase* database = mTransaction->Database();
   if (database->IsInvalidated()) {
     mAborted = true;
   }
 
   if (mConnection) {
-    IDBFactory::SetCurrentDatabase(database);
+    IndexedDatabaseManager::SetCurrentDatabase(database);
 
     if (!mAborted) {
       NS_NAMED_LITERAL_CSTRING(release, "END TRANSACTION");
       if (NS_FAILED(mConnection->ExecuteSimpleSQL(release))) {
         mAborted = true;
       }
     }
 
@@ -1005,13 +1006,13 @@ CommitHelper::Run()
   }
 
   mDoomedObjects.Clear();
 
   if (mConnection) {
     mConnection->Close();
     mConnection = nsnull;
 
-    IDBFactory::SetCurrentDatabase(nsnull);
+    IndexedDatabaseManager::SetCurrentDatabase(nsnull);
   }
 
   return NS_OK;
 }
--- a/dom/indexedDB/IndexedDatabaseManager.cpp
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -36,51 +36,96 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "IndexedDatabaseManager.h"
 
 #include "nsIFile.h"
 #include "nsIObserverService.h"
+#include "nsISHEntry.h"
 #include "nsISimpleEnumerator.h"
 #include "nsITimer.h"
 
+#include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
+#include "mozilla/storage.h"
 #include "nsContentUtils.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOM.h"
 #include "nsXPCOMPrivate.h"
 
 #include "AsyncConnectionHelper.h"
 #include "IDBDatabase.h"
 #include "IDBEvents.h"
 #include "IDBFactory.h"
 #include "LazyIdleThread.h"
 #include "TransactionThreadPool.h"
-#include "nsISHEntry.h"
 
 // The amount of time, in milliseconds, that our IO thread will stay alive
 // after the last event it processes.
 #define DEFAULT_THREAD_TIMEOUT_MS 30000
 
 // The amount of time, in milliseconds, that we will wait for active database
 // transactions on shutdown before aborting them.
 #define DEFAULT_SHUTDOWN_TIMER_MS 30000
 
+// Amount of space that IndexedDB databases may use by default in megabytes.
+#define DEFAULT_QUOTA_MB 50
+
+// Preference that users can set to override DEFAULT_QUOTA_MB
+#define PREF_INDEXEDDB_QUOTA "dom.indexedDB.warningQuota"
+
+// A bad TLS index number.
+#define BAD_TLS_INDEX (PRUintn)-1
+
 USING_INDEXEDDB_NAMESPACE
 using namespace mozilla::services;
+using mozilla::Preferences;
 
 namespace {
 
 PRInt32 gShutdown = 0;
 
 // Does not hold a reference.
 IndexedDatabaseManager* gInstance = nsnull;
 
+PRUintn gCurrentDatabaseIndex = BAD_TLS_INDEX;
+
+PRInt32 gIndexedDBQuotaMB = DEFAULT_QUOTA_MB;
+
+class QuotaCallback : public mozIStorageQuotaCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  NS_IMETHOD
+  QuotaExceeded(const nsACString& aFilename,
+                PRInt64 aCurrentSizeLimit,
+                PRInt64 aCurrentTotalSize,
+                nsISupports* aUserData,
+                PRInt64* _retval)
+  {
+    NS_ASSERTION(gCurrentDatabaseIndex != BAD_TLS_INDEX,
+                 "This should be impossible!");
+
+    IDBDatabase* database =
+      static_cast<IDBDatabase*>(PR_GetThreadPrivate(gCurrentDatabaseIndex));
+
+    if (database && database->IsQuotaDisabled()) {
+      *_retval = 0;
+      return NS_OK;
+    }
+
+    return NS_ERROR_FAILURE;
+  }
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(QuotaCallback, mozIStorageQuotaCallback)
+
 // Adds all databases in the hash to the given array.
 PLDHashOperator
 EnumerateToTArray(const nsACString& aKey,
                   nsTArray<IDBDatabase*>* aValue,
                   void* aUserArg)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
@@ -238,16 +283,33 @@ IndexedDatabaseManager::GetOrCreate()
   if (IsShuttingDown()) {
     NS_ERROR("Calling GetOrCreateInstance() after shutdown!");
     return nsnull;
   }
 
   nsRefPtr<IndexedDatabaseManager> instance(gInstance);
 
   if (!instance) {
+    // We need a thread-local to hold our current database.
+    if (gCurrentDatabaseIndex == BAD_TLS_INDEX) {
+      if (PR_NewThreadPrivateIndex(&gCurrentDatabaseIndex, nsnull) !=
+          PR_SUCCESS) {
+        NS_ERROR("PR_NewThreadPrivateIndex failed!");
+        gCurrentDatabaseIndex = BAD_TLS_INDEX;
+        return nsnull;
+      }
+
+      if (NS_FAILED(Preferences::AddIntVarCache(&gIndexedDBQuotaMB,
+                                                PREF_INDEXEDDB_QUOTA,
+                                                DEFAULT_QUOTA_MB))) {
+        NS_WARNING("Unable to respond to quota pref changes!");
+        gIndexedDBQuotaMB = DEFAULT_QUOTA_MB;
+      }
+    }
+
     instance = new IndexedDatabaseManager();
 
     if (!instance->mLiveDatabases.Init()) {
       NS_WARNING("Out of memory!");
       return nsnull;
     }
 
     // Make a timer here to avoid potential failures later. We don't actually
@@ -270,16 +332,19 @@ IndexedDatabaseManager::GetOrCreate()
                           PR_FALSE);
     NS_ENSURE_SUCCESS(rv, nsnull);
 
     // Make a lazy thread for any IO we need (like clearing or enumerating the
     // contents of indexedDB database directories).
     instance->mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
                                              LazyIdleThread::ManualShutdown);
 
+    // We need one quota callback object to hand to SQLite.
+    instance->mQuotaCallbackSingleton = new QuotaCallback();
+
     // The observer service will hold our last reference, don't AddRef here.
     gInstance = instance;
   }
 
   return instance.forget();
 }
 
 // static
@@ -640,16 +705,130 @@ IndexedDatabaseManager::OnDatabaseClosed
           NS_WARNING("Failed to wait for transaction to complete!");
         }
       }
       break;
     }
   }
 }
 
+// static
+bool
+IndexedDatabaseManager::SetCurrentDatabase(IDBDatabase* aDatabase)
+{
+  NS_ASSERTION(gCurrentDatabaseIndex != BAD_TLS_INDEX,
+               "This should have been set already!");
+
+#ifdef DEBUG
+  if (aDatabase) {
+    NS_ASSERTION(!PR_GetThreadPrivate(gCurrentDatabaseIndex),
+                 "Someone forgot to unset gCurrentDatabaseIndex!");
+  }
+  else {
+    NS_ASSERTION(PR_GetThreadPrivate(gCurrentDatabaseIndex),
+                 "Someone forgot to set gCurrentDatabaseIndex!");
+  }
+#endif
+
+  if (PR_SetThreadPrivate(gCurrentDatabaseIndex, aDatabase) != PR_SUCCESS) {
+    NS_WARNING("Failed to set gCurrentDatabaseIndex!");
+    return false;
+  }
+
+  return true;
+}
+
+// static
+PRUint32
+IndexedDatabaseManager::GetIndexedDBQuotaMB()
+{
+  return PRUint32(NS_MAX(gIndexedDBQuotaMB, 0));
+}
+
+nsresult
+IndexedDatabaseManager::EnsureQuotaManagementForDirectory(nsIFile* aDirectory)
+{
+#ifdef DEBUG
+  {
+    PRBool correctThread;
+    NS_ASSERTION(NS_SUCCEEDED(mIOThread->IsOnCurrentThread(&correctThread)) &&
+                 correctThread,
+                 "Running on the wrong thread!");
+  }
+#endif
+  NS_ASSERTION(aDirectory, "Null pointer!");
+
+  nsCString path;
+  nsresult rv = aDirectory->GetNativePath(path);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (mTrackedQuotaPaths.Contains(path)) {
+    return true;
+  }
+
+  // First figure out the filename pattern we'll use.
+  nsCOMPtr<nsIFile> patternFile;
+  rv = aDirectory->Clone(getter_AddRefs(patternFile));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = patternFile->Append(NS_LITERAL_STRING("*"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCString pattern;
+  rv = patternFile->GetNativePath(pattern);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Now tell SQLite to start tracking this pattern.
+  nsCOMPtr<mozIStorageServiceQuotaManagement> ss =
+    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE);
+
+  rv = ss->SetQuotaForFilenamePattern(pattern,
+                                      GetIndexedDBQuotaMB() * 1024 * 1024,
+                                      mQuotaCallbackSingleton, nsnull);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // If the directory exists then we need to see if there are any files in it
+  // already. We need to tell SQLite about all of them.
+  PRBool exists;
+  rv = aDirectory->Exists(&exists);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (exists) {
+    // Make sure this really is a directory.
+    PRBool isDirectory;
+    rv = aDirectory->IsDirectory(&isDirectory);
+    NS_ENSURE_SUCCESS(rv, rv);
+    NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED);
+
+    nsCOMPtr<nsISimpleEnumerator> entries;
+    rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    PRBool hasMore;
+    while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
+      nsCOMPtr<nsISupports> entry;
+      rv = entries->GetNext(getter_AddRefs(entry));
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
+      NS_ENSURE_TRUE(file, NS_NOINTERFACE);
+
+      rv = ss->UpdateQutoaInformationForFile(file);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  NS_ASSERTION(!mTrackedQuotaPaths.Contains(path), "What?!");
+
+  mTrackedQuotaPaths.AppendElement(path);
+  return rv;
+}
+
 NS_IMPL_ISUPPORTS2(IndexedDatabaseManager, nsIIndexedDatabaseManager,
                                            nsIObserver)
 
 NS_IMETHODIMP
 IndexedDatabaseManager::GetUsageForURI(
                                      nsIURI* aURI,
                                      nsIIndexedDatabaseUsageCallback* aCallback)
 {
--- a/dom/indexedDB/IndexedDatabaseManager.h
+++ b/dom/indexedDB/IndexedDatabaseManager.h
@@ -50,16 +50,17 @@
 #include "nsIThread.h"
 #include "nsIURI.h"
 
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
 
 #define INDEXEDDB_MANAGER_CONTRACTID "@mozilla.org/dom/indexeddb/manager;1"
 
+class mozIStorageQuotaCallback;
 class nsITimer;
 
 BEGIN_INDEXEDDB_NAMESPACE
 
 class AsyncConnectionHelper;
 
 class IndexedDatabaseManager : public nsIIndexedDatabaseManager,
                                public nsIObserver
@@ -103,16 +104,24 @@ public:
   // Called when a window is being purged from the bfcache or the user leaves
   // a page which isn't going into the bfcache. Forces any live database
   // objects to close themselves and aborts any running transactions.
   void AbortCloseDatabasesForWindow(nsPIDOMWindow* aWindow);
 
   // Used to check if there are running transactions in a given window.
   bool HasOpenTransactions(nsPIDOMWindow* aWindow);
 
+  static bool
+  SetCurrentDatabase(IDBDatabase* aDatabase);
+
+  static PRUint32
+  GetIndexedDBQuotaMB();
+
+  nsresult EnsureQuotaManagementForDirectory(nsIFile* aDirectory);
+
 private:
   IndexedDatabaseManager();
   ~IndexedDatabaseManager();
 
   // Called when a database is created.
   bool RegisterDatabase(IDBDatabase* aDatabase);
 
   // Called when a database is being unlinked or destroyed.
@@ -230,13 +239,22 @@ private:
   // Maintains a list of SetVersion calls that are in progress.
   nsAutoTArray<nsRefPtr<SetVersionRunnable>, 1> mSetVersionRunnables;
 
   // Thread on which IO is performed.
   nsCOMPtr<nsIThread> mIOThread;
 
   // A timer that gets activated at shutdown to ensure we close all databases.
   nsCOMPtr<nsITimer> mShutdownTimer;
+
+  // A single threadsafe instance of our quota callback. Created on the main
+  // thread during GetOrCreate().
+  nsCOMPtr<mozIStorageQuotaCallback> mQuotaCallbackSingleton;
+
+  // A list of all paths that are under SQLite's quota tracking system. This
+  // list isn't protected by any mutex but it is only ever touched on the IO
+  // thread.
+  nsTArray<nsCString> mTrackedQuotaPaths;
 };
 
 END_INDEXEDDB_NAMESPACE
 
 #endif /* mozilla_dom_indexeddb_indexeddatabasemanager_h__ */
--- a/storage/public/mozIStorageServiceQuotaManagement.idl
+++ b/storage/public/mozIStorageServiceQuotaManagement.idl
@@ -105,9 +105,28 @@ interface mozIStorageServiceQuotaManagem
    *
    * @param aUserData
    *        Additional information to be passed to the callback.
    */
   void setQuotaForFilenamePattern(in ACString aPattern,
                                   in long long aSizeLimit,
                                   in mozIStorageQuotaCallback aCallback,
                                   in nsISupports aUserData);
+
+  /**
+   * Adds, removes, or updates the file size information maintained by the quota
+   * system for files not opened through openDatabaseWithVFS().
+   *
+   * Use this function when you want files to be included in quota calculations
+   * that are either a) not SQLite databases, or b) SQLite databases that have
+   * not been opened.
+   *
+   * This function will have no effect on files that do not match an existing
+   * quota pattern (set previously by setQuotaForFilenamePattern()).
+   * 
+   * @param aFile
+   *        The file for which quota information should be updated. If the file
+   *        exists then its size information will be added or refreshed. If the
+   *        file does not exist then the file will be removed from tracking
+   *        under the quota system.
+   */
+  void updateQutoaInformationForFile(in nsIFile aFile);
 };
--- a/storage/src/mozStorageService.cpp
+++ b/storage/src/mozStorageService.cpp
@@ -614,10 +614,25 @@ Service::SetQuotaForFilenamePattern(cons
                                aSizeLimit, QuotaCallbackData::Callback,
                                data, QuotaCallbackData::Destroy);
   NS_ENSURE_TRUE(rc == SQLITE_OK, convertResultCode(rc));
 
   data.forget();
   return NS_OK;
 }
 
+NS_IMETHODIMP
+Service::UpdateQutoaInformationForFile(nsIFile* aFile)
+{
+  NS_ENSURE_ARG_POINTER(aFile);
+
+  nsCString path;
+  nsresult rv = aFile->GetNativePath(path);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  int rc = ::sqlite3_quota_file(PromiseFlatCString(path).get());
+  NS_ENSURE_TRUE(rc == SQLITE_OK, convertResultCode(rc));
+
+  return NS_OK;
+}
+
 } // namespace storage
 } // namespace mozilla