Bug 681930, r=sicking.
authorBen Turner <bent.mozilla@gmail.com>
Fri, 26 Aug 2011 01:21:35 -0700
changeset 77249 6eeb384db995e17790a20c7122dc66d6eab7b92f
parent 77248 e5adec25155d6c697473762d464b03aa4879561e
child 77250 40861507b6c45809247c7ad503cb6609200b81a4
push id78
push userclegnitto@mozilla.com
push dateFri, 16 Dec 2011 17:32:24 +0000
treeherdermozilla-release@79d24e644fdd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssicking
bugs681930
milestone9.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 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