Bug 866846 - Use WAL journal mode for IndexedDB databases, r=janv.
☠☠ backed out by beb016173f6f ☠ ☠
authorBen Turner <bent.mozilla@gmail.com>
Sat, 24 Jan 2015 08:16:26 -0800
changeset 266717 cbd862dd036f9190dda9ee8ed535dd5f726c5360
parent 266716 178412a2fe8be1c71354385094901374b0ace38c
child 266718 1b4ead852ae03055b1f984b8fd42c33d5b91d3fa
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjanv
bugs866846
milestone39.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 866846 - Use WAL journal mode for IndexedDB databases, r=janv.
dom/indexedDB/ActorsParent.cpp
dom/quota/QuotaObject.cpp
storage/src/TelemetryVFS.cpp
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -82,31 +82,35 @@
 #include "nsThreadUtils.h"
 #include "nsXPCOMCID.h"
 #include "PermissionRequestBase.h"
 #include "ProfilerHelpers.h"
 #include "ReportInternalError.h"
 #include "snappy/snappy.h"
 #include "TransactionThreadPool.h"
 
-namespace mozilla {
-namespace dom {
-namespace indexedDB {
-
-using namespace mozilla::dom::quota;
-using namespace mozilla::ipc;
-
 #define DISABLE_ASSERTS_FOR_FUZZING 0
 
 #if DISABLE_ASSERTS_FOR_FUZZING
 #define ASSERT_UNLESS_FUZZING(...) do { } while (0)
 #else
 #define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
 #endif
 
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+#define IDB_MOBILE
+#endif
+
+namespace mozilla {
+namespace dom {
+namespace indexedDB {
+
+using namespace mozilla::dom::quota;
+using namespace mozilla::ipc;
+
 namespace {
 
 class Cursor;
 class Database;
 struct DatabaseActorInfo;
 class DatabaseLoggingInfo;
 class DatabaseFile;
 class DatabaseOfflineStorage;
@@ -139,23 +143,54 @@ static_assert(kMajorSchemaVersion <= 0xF
 static_assert(kMinorSchemaVersion <= 0xF,
               "Minor version needs to fit in 4 bits.");
 
 const int32_t kSQLiteSchemaVersion =
   int32_t((kMajorSchemaVersion << 4) + kMinorSchemaVersion);
 
 const int32_t kStorageProgressGranularity = 1000;
 
+// Changing the value here will override the page size of new databases only.
+// A journal mode change and VACUUM are needed to change existing databases, so
+// the best way to do that is to use the schema version upgrade mechanism.
+const uint32_t kSQLitePageSizeOverride =
+#ifdef IDB_MOBILE
+  2048;
+#else
+  4096;
+#endif
+
+static_assert(kSQLitePageSizeOverride == /* mozStorage default */ 0 ||
+              (kSQLitePageSizeOverride % 2 == 0 &&
+               kSQLitePageSizeOverride >= 512  &&
+               kSQLitePageSizeOverride <= 65536),
+              "Must be 0 (disabled) or a power of 2 between 512 and 65536!");
+
+// Set to -1 to use SQLite's default, 0 to disable, or some positive number to
+// enforce a custom limit.
+const int32_t kMaxWALPages = 5000; // 20MB on desktop, 10MB on mobile.
+
+// Set to some multiple of the page size to grow the database in larger chunks.
+const uint32_t kSQLiteGrowthIncrement = kSQLitePageSizeOverride * 2;
+
+static_assert(kSQLiteGrowthIncrement >= 0 &&
+              kSQLiteGrowthIncrement % kSQLitePageSizeOverride == 0 &&
+              kSQLiteGrowthIncrement < uint32_t(INT32_MAX),
+              "Must be 0 (disabled) or a positive multiple of the page size!");
+
 const char kSavepointClause[] = "SAVEPOINT sp;";
 
 const uint32_t kFileCopyBufferSize = 32768;
 
 const char kJournalDirectoryName[] = "journals";
 
 const char kFileManagerDirectoryNameSuffix[] = ".files";
+const char kSQLiteJournalSuffix[] = ".sqlite-journal";
+const char kSQLiteSHMSuffix[] = ".sqlite-shm";
+const char kSQLiteWALSuffix[] = ".sqlite-wal";
 
 const char kPrefIndexedDBEnabled[] = "dom.indexedDB.enabled";
 
 #define IDB_PREFIX "indexedDB"
 
 #ifdef MOZ_CHILD_PERMISSIONS
 const char kPermissionString[] = IDB_PREFIX;
 #endif // MOZ_CHILD_PERMISSIONS
@@ -173,16 +208,26 @@ enum AppId {
 
 #ifdef DEBUG
 
 const int32_t kDEBUGThreadPriority = nsISupportsPriority::PRIORITY_NORMAL;
 const uint32_t kDEBUGThreadSleepMS = 0;
 
 #endif
 
+template <size_t N>
+MOZ_CONSTEXPR size_t
+LiteralStringLength(const char (&aArr)[N])
+{
+  static_assert(N, "Zero-length string literal?!");
+
+  // Don't include the null terminator.
+  return ArrayLength<const char, N>(aArr) - 1;
+}
+
 /*******************************************************************************
  * Metadata classes
  ******************************************************************************/
 
 struct FullIndexMetadata
 {
   IndexMetadata mCommonMetadata;
 
@@ -2058,17 +2103,17 @@ UpgradeSchemaFrom12_0To13_0(mozIStorageC
   MOZ_ASSERT(aConnection);
 
   PROFILER_LABEL("IndexedDB",
                  "UpgradeSchemaFrom12_0To13_0",
                  js::ProfileEntry::Category::STORAGE);
 
   nsresult rv;
 
-#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+#ifdef IDB_MOBILE
   int32_t defaultPageSize;
   rv = aConnection->GetDefaultPageSize(&defaultPageSize);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Enable auto_vacuum mode and update the page size to the platform default.
   nsAutoCString upgradeQuery("PRAGMA auto_vacuum = FULL; PRAGMA page_size = ");
@@ -2176,72 +2221,254 @@ GetDatabaseFileURL(nsIFile* aDatabaseFil
 
   fileUrl.forget(aResult);
   return NS_OK;
 }
 
 nsresult
 SetDefaultPragmas(mozIStorageConnection* aConnection)
 {
+  MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aConnection);
 
-  static const char query[] =
-#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
-    // Switch the journaling mode to TRUNCATE to avoid changing the directory
-    // structure at the conclusion of every transaction for devices with slower
-    // file systems.
-    "PRAGMA journal_mode = TRUNCATE; "
-#endif
+  static const char kBuiltInPragmas[] =
     // We use foreign keys in lots of places.
-    "PRAGMA foreign_keys = ON; "
+    "PRAGMA foreign_keys = ON;"
+
     // The "INSERT OR REPLACE" statement doesn't fire the update trigger,
     // instead it fires only the insert trigger. This confuses the update
     // refcount function. This behavior changes with enabled recursive triggers,
     // so the statement fires the delete trigger first and then the insert
     // trigger.
     "PRAGMA recursive_triggers = ON;"
-    // We don't need SQLite's table locks because we manage transaction ordering
-    // ourselves and we know we will never allow a write transaction to modify
-    // an object store that a read transaction is in the process of using.
-    "PRAGMA read_uncommitted = TRUE;"
-    // No more PRAGMAs.
-    ;
-
-  nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(query));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
+
+    // We aggressively truncate the database file when idle so don't bother
+    // overwriting the WAL with 0 during active periods.
+    "PRAGMA secure_delete = OFF;"
+  ;
+
+  nsresult rv =
+    aConnection->ExecuteSimpleSQL(
+      nsDependentCString(kBuiltInPragmas,
+                         LiteralStringLength(kBuiltInPragmas)));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsAutoCString pragmaStmt;
+  pragmaStmt.AssignLiteral("PRAGMA synchronous = ");
 
   if (IndexedDatabaseManager::FullSynchronous()) {
-    rv = aConnection->ExecuteSimpleSQL(
-                             NS_LITERAL_CSTRING("PRAGMA synchronous = FULL;"));
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
-  return NS_OK;
-}
-
-nsresult
-CreateDatabaseConnection(nsIFile* aDBFile,
-                         nsIFile* aFMDirectory,
-                         const nsAString& aName,
-                         PersistenceType aPersistenceType,
-                         const nsACString& aGroup,
-                         const nsACString& aOrigin,
-                         mozIStorageConnection** aConnection)
+    pragmaStmt.AppendLiteral("FULL");
+  } else {
+    pragmaStmt.AppendLiteral("NORMAL");
+  }
+  pragmaStmt.Append(';');
+
+  rv = aConnection->ExecuteSimpleSQL(pragmaStmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+#ifndef IDB_MOBILE
+  if (kSQLiteGrowthIncrement) {
+    rv = aConnection->SetGrowthIncrement(kSQLiteGrowthIncrement,
+                                         EmptyCString());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+#endif // IDB_MOBILE
+
+  return NS_OK;
+}
+
+nsresult
+SetJournalMode(mozIStorageConnection* aConnection)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConnection);
+
+  // Try enabling WAL mode. This can fail in various circumstances so we have to
+  // check the results here.
+  NS_NAMED_LITERAL_CSTRING(journalModeQueryStart, "PRAGMA journal_mode = ");
+  NS_NAMED_LITERAL_CSTRING(journalModeWAL, "wal");
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv =
+    aConnection->CreateStatement(journalModeQueryStart + journalModeWAL,
+                                 getter_AddRefs(stmt));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  bool hasResult;
+  rv = stmt->ExecuteStep(&hasResult);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  MOZ_ASSERT(hasResult);
+
+  nsCString journalMode;
+  rv = stmt->GetUTF8String(0, journalMode);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (journalMode.Equals(journalModeWAL)) {
+    // WAL mode successfully enabled. Maybe set limits on its size here.
+    if (kMaxWALPages >= 0) {
+      nsAutoCString pageCount;
+      pageCount.AppendInt(kMaxWALPages);
+
+      rv = aConnection->ExecuteSimpleSQL(
+        NS_LITERAL_CSTRING("PRAGMA wal_autocheckpoint = ") + pageCount);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+  } else {
+    NS_WARNING("Failed to set WAL mode, falling back to normal journal mode.");
+#ifdef IDB_MOBILE
+    rv = aConnection->ExecuteSimpleSQL(journalModeQueryStart +
+                                       NS_LITERAL_CSTRING("truncate"));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+#endif
+  }
+
+  return NS_OK;
+}
+
+template <class FileOrURLType>
+struct StorageOpenTraits;
+
+template <>
+struct StorageOpenTraits<nsIFileURL*>
+{
+  static nsresult
+  Open(mozIStorageService* aStorageService,
+       nsIFileURL* aFileURL,
+       mozIStorageConnection** aConnection)
+  {
+    return aStorageService->OpenDatabaseWithFileURL(aFileURL, aConnection);
+  }
+
+#ifdef DEBUG
+  static void
+  GetPath(nsIFileURL* aFileURL, nsCString& aPath)
+  {
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aFileURL->GetFileName(aPath)));
+  }
+#endif
+};
+
+template <>
+struct StorageOpenTraits<nsIFile*>
+{
+  static nsresult
+  Open(mozIStorageService* aStorageService,
+       nsIFile* aFile,
+       mozIStorageConnection** aConnection)
+  {
+    return aStorageService->OpenUnsharedDatabase(aFile, aConnection);
+  }
+
+#ifdef DEBUG
+  static void
+  GetPath(nsIFile* aFile, nsCString& aPath)
+  {
+    nsString path;
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aFile->GetPath(path)));
+
+    aPath.AssignWithConversion(path);
+  }
+#endif
+};
+
+template <template <class> class SmartPtr, class FileOrURLType>
+struct StorageOpenTraits<SmartPtr<FileOrURLType>>
+  : public StorageOpenTraits<FileOrURLType*>
+{ };
+
+template <class FileOrURLType>
+nsresult
+OpenDatabaseAndHandleBusy(mozIStorageService* aStorageService,
+                          FileOrURLType aFileOrURL,
+                          mozIStorageConnection** aConnection)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aStorageService);
+  MOZ_ASSERT(aFileOrURL);
+  MOZ_ASSERT(aConnection);
+
+  nsCOMPtr<mozIStorageConnection> connection;
+  nsresult rv =
+    StorageOpenTraits<FileOrURLType>::Open(aStorageService,
+                                           aFileOrURL,
+                                           getter_AddRefs(connection));
+
+  if (rv == NS_ERROR_STORAGE_BUSY) {
+#ifdef DEBUG
+    {
+      nsCString path;
+      StorageOpenTraits<FileOrURLType>::GetPath(aFileOrURL, path);
+
+      nsPrintfCString message("Received NS_ERROR_STORAGE_BUSY when attempting "
+                              "to open database '%s', retrying for up to 10 "
+                              "seconds",
+                              path.get());
+      NS_WARNING(message.get());
+    }
+#endif
+
+    // Another thread must be checkpointing the WAL. Wait up to 10 seconds for
+    // that to complete.
+    TimeStamp start = TimeStamp::NowLoRes();
+
+    while (true) {
+      PR_Sleep(PR_MillisecondsToInterval(100));
+
+      rv = StorageOpenTraits<FileOrURLType>::Open(aStorageService,
+                                                  aFileOrURL,
+                                                  getter_AddRefs(connection));
+      if (rv != NS_ERROR_STORAGE_BUSY ||
+          TimeStamp::NowLoRes() - start > TimeDuration::FromSeconds(10)) {
+        break;
+      }
+    }
+  }
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  connection.forget(aConnection);
+  return NS_OK;
+}
+
+nsresult
+CreateStorageConnection(nsIFile* aDBFile,
+                        nsIFile* aFMDirectory,
+                        const nsAString& aName,
+                        PersistenceType aPersistenceType,
+                        const nsACString& aGroup,
+                        const nsACString& aOrigin,
+                        mozIStorageConnection** aConnection)
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(aDBFile);
   MOZ_ASSERT(aFMDirectory);
   MOZ_ASSERT(aConnection);
 
   PROFILER_LABEL("IndexedDB",
-                 "CreateDatabaseConnection",
+                 "CreateStorageConnection",
                  js::ProfileEntry::Category::STORAGE);
 
   nsresult rv;
   bool exists;
 
   if (IndexedDatabaseManager::InLowDiskSpaceMode()) {
     rv = aDBFile->Exists(&exists);
     if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -2263,17 +2490,17 @@ CreateDatabaseConnection(nsIFile* aDBFil
 
   nsCOMPtr<mozIStorageService> ss =
     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCOMPtr<mozIStorageConnection> connection;
-  rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(connection));
+  rv = OpenDatabaseAndHandleBusy(ss, dbFileUrl, getter_AddRefs(connection));
   if (rv == NS_ERROR_FILE_CORRUPTED) {
     // If we're just opening the database during origin initialization, then
     // we don't want to erase any files. The failure here will fail origin
     // initialization too.
     if (aName.IsVoid()) {
       return rv;
     }
 
@@ -2300,18 +2527,19 @@ CreateDatabaseConnection(nsIFile* aDBFil
       }
 
       rv = aFMDirectory->Remove(true);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
 
-    rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(connection));
-  }
+    rv = OpenDatabaseAndHandleBusy(ss, dbFileUrl, getter_AddRefs(connection));
+  }
+
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = SetDefaultPragmas(connection);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -2334,37 +2562,48 @@ CreateDatabaseConnection(nsIFile* aDBFil
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   if (schemaVersion > kSQLiteSchemaVersion) {
     IDB_WARNING("Unable to open IndexedDB database, schema is too high!");
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
-  bool vacuumNeeded = false;
+  bool journalModeSet = false;
 
   if (schemaVersion != kSQLiteSchemaVersion) {
-#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
     if (!schemaVersion) {
+      // Brand new file.
+
+#ifdef IDB_MOBILE
       // Have to do this before opening a transaction.
       rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
         // Turn on auto_vacuum mode to reclaim disk space on mobile devices.
         "PRAGMA auto_vacuum = FULL; "
       ));
       if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
         // mozstorage translates SQLITE_FULL to NS_ERROR_FILE_NO_DEVICE_SPACE,
         // which we know better as NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR.
         rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
       }
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
-    }
 #endif
 
+      rv = SetJournalMode(connection);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      journalModeSet = true;
+    }
+
+    bool vacuumNeeded = false;
+
     mozStorageTransaction transaction(connection, false,
                                   mozIStorageConnection::TRANSACTION_IMMEDIATE);
 
     if (!schemaVersion) {
       // Brand new file, initialize our tables.
       rv = CreateTables(connection);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
@@ -2447,20 +2686,27 @@ CreateDatabaseConnection(nsIFile* aDBFil
     if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
       // mozstorage translates SQLITE_FULL to NS_ERROR_FILE_NO_DEVICE_SPACE,
       // which we know better as NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR.
       rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
     }
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
-  }
-
-  if (vacuumNeeded) {
-    rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM;"));
+
+    if (vacuumNeeded) {
+      rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM"));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+  }
+
+  if (!journalModeSet) {
+    rv = SetJournalMode(connection);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   connection.forget(aConnection);
   return NS_OK;
 }
@@ -2478,30 +2724,30 @@ GetFileForPath(const nsAString& aPath)
   if (NS_WARN_IF(NS_FAILED(file->InitWithPath(aPath)))) {
     return nullptr;
   }
 
   return file.forget();
 }
 
 nsresult
-GetDatabaseConnection(const nsAString& aDatabaseFilePath,
-                      PersistenceType aPersistenceType,
-                      const nsACString& aGroup,
-                      const nsACString& aOrigin,
-                      mozIStorageConnection** aConnection)
+GetStorageConnection(const nsAString& aDatabaseFilePath,
+                     PersistenceType aPersistenceType,
+                     const nsACString& aGroup,
+                     const nsACString& aOrigin,
+                     mozIStorageConnection** aConnection)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(!IsOnBackgroundThread());
   MOZ_ASSERT(!aDatabaseFilePath.IsEmpty());
   MOZ_ASSERT(StringEndsWith(aDatabaseFilePath, NS_LITERAL_STRING(".sqlite")));
   MOZ_ASSERT(aConnection);
 
   PROFILER_LABEL("IndexedDB",
-                 "GetDatabaseConnection",
+                 "GetStorageConnection",
                  js::ProfileEntry::Category::STORAGE);
 
   nsCOMPtr<nsIFile> dbFile = GetFileForPath(aDatabaseFilePath);
   if (NS_WARN_IF(!dbFile)) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
@@ -2525,26 +2771,31 @@ GetDatabaseConnection(const nsAString& a
 
   nsCOMPtr<mozIStorageService> ss =
     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCOMPtr<mozIStorageConnection> connection;
-  rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(connection));
+  rv = OpenDatabaseAndHandleBusy(ss, dbFileUrl, getter_AddRefs(connection));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = SetDefaultPragmas(connection);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  rv = SetJournalMode(connection);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   connection.forget(aConnection);
   return NS_OK;
 }
 
 /*******************************************************************************
  * Actor class declarations
  ******************************************************************************/
 
@@ -4276,16 +4527,21 @@ private:
   RunOnMainThread();
 
   nsresult
   RunOnIOThread();
 
   void
   RunOnOwningThread();
 
+  nsresult
+  DeleteFile(nsIFile* aDirectory,
+             const nsAString& aFilename,
+             QuotaManager* aQuotaManager);
+
   NS_DECL_NSIRUNNABLE
 };
 
 class VersionChangeTransactionOp
   : public TransactionDatabaseOperationBase
 {
 public:
   virtual void
@@ -6685,50 +6941,48 @@ TransactionBase::EnsureConnection()
 #endif
 
   AssertIsOnTransactionThread();
 
   PROFILER_LABEL("IndexedDB",
                  "TransactionBase::EnsureConnection",
                  js::ProfileEntry::Category::STORAGE);
 
+  const bool readOnly = mMode == IDBTransaction::READ_ONLY;
+
   if (!mConnection) {
     nsCOMPtr<mozIStorageConnection> connection;
     nsresult rv =
-      GetDatabaseConnection(mDatabase->FilePath(), mDatabase->Type(),
-                            mDatabase->Group(), mDatabase->Origin(),
-                            getter_AddRefs(connection));
+      GetStorageConnection(mDatabase->FilePath(),
+                           mDatabase->Type(),
+                           mDatabase->Group(),
+                           mDatabase->Origin(),
+                           getter_AddRefs(connection));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     nsRefPtr<UpdateRefcountFunction> function;
-    nsCString beginTransaction;
-
-    if (mMode == IDBTransaction::READ_ONLY) {
-      beginTransaction.AssignLiteral("BEGIN TRANSACTION;");
-    } else {
+
+    nsAutoCString beginTransaction;
+    beginTransaction.AssignLiteral("BEGIN");
+
+    if (!readOnly) {
       function = new UpdateRefcountFunction(mDatabase->GetFileManager());
 
       rv = connection->CreateFunction(NS_LITERAL_CSTRING("update_refcount"), 2,
                                       function);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
-      beginTransaction.AssignLiteral("BEGIN IMMEDIATE TRANSACTION;");
-    }
-
-    nsCOMPtr<mozIStorageStatement> stmt;
-    rv = connection->CreateStatement(beginTransaction, getter_AddRefs(stmt));
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = stmt->Execute();
+      beginTransaction.AppendLiteral(" IMMEDIATE");
+    }
+
+    rv = connection->ExecuteSimpleSQL(beginTransaction);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     function.swap(mUpdateFileRefcountFunction);
     connection.swap(mConnection);
   }
 
@@ -9064,23 +9318,23 @@ FileManager::InitDirectory(nsIFile* aDir
     bool hasElements;
     rv = entries->HasMoreElements(&hasElements);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     if (hasElements) {
       nsCOMPtr<mozIStorageConnection> connection;
-      rv = CreateDatabaseConnection(aDatabaseFile,
-                                    aDirectory,
-                                    NullString(),
-                                    aPersistenceType,
-                                    aGroup,
-                                    aOrigin,
-                                    getter_AddRefs(connection));
+      rv = CreateStorageConnection(aDatabaseFile,
+                                   aDirectory,
+                                   NullString(),
+                                   aPersistenceType,
+                                   aGroup,
+                                   aOrigin,
+                                   getter_AddRefs(connection));
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       mozStorageTransaction transaction(connection, false);
 
       rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
         "CREATE VIRTUAL TABLE fs USING filesystem;"
@@ -9274,16 +9528,17 @@ QuotaClient::GetType()
 {
   return QuotaClient::IDB;
 }
 
 struct FileManagerInitInfo
 {
   nsCOMPtr<nsIFile> mDirectory;
   nsCOMPtr<nsIFile> mDatabaseFile;
+  nsCOMPtr<nsIFile> mDatabaseWALFile;
 };
 
 nsresult
 QuotaClient::InitOrigin(PersistenceType aPersistenceType,
                         const nsACString& aGroup,
                         const nsACString& aOrigin,
                         UsageInfo* aUsageInfo)
 {
@@ -9306,17 +9561,27 @@ QuotaClient::InitOrigin(PersistenceType 
   nsAutoTArray<FileManagerInitInfo, 20> initInfos;
 
   nsCOMPtr<nsISimpleEnumerator> entries;
   rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  const NS_ConvertASCIItoUTF16 filesSuffix(kFileManagerDirectoryNameSuffix);
+  const NS_ConvertASCIItoUTF16 filesSuffix(
+    kFileManagerDirectoryNameSuffix,
+    LiteralStringLength(kFileManagerDirectoryNameSuffix));
+
+  const NS_ConvertASCIItoUTF16 journalSuffix(
+    kSQLiteJournalSuffix,
+    LiteralStringLength(kSQLiteJournalSuffix));
+  const NS_ConvertASCIItoUTF16 shmSuffix(kSQLiteSHMSuffix,
+                                         LiteralStringLength(kSQLiteSHMSuffix));
+  const NS_ConvertASCIItoUTF16 walSuffix(kSQLiteWALSuffix,
+                                         LiteralStringLength(kSQLiteWALSuffix));
 
   bool hasMore;
   while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
          hasMore &&
          (!aUsageInfo || !aUsageInfo->Canceled())) {
     nsCOMPtr<nsISupports> entry;
     rv = entries->GetNext(getter_AddRefs(entry));
     if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -9327,62 +9592,87 @@ QuotaClient::InitOrigin(PersistenceType 
     MOZ_ASSERT(file);
 
     nsString leafName;
     rv = file->GetLeafName(leafName);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-
     bool isDirectory;
     rv = file->IsDirectory(&isDirectory);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     if (isDirectory) {
       if (!StringEndsWith(leafName, filesSuffix) ||
           !validSubdirs.GetEntry(leafName)) {
         subdirsToProcess.AppendElement(leafName);
       }
       continue;
     }
 
-    // Skip SQLite and Desktop Service Store (.DS_Store) files.
-    // Desktop Service Store file is only used on Mac OS X, but the profile
-    // can be shared across different operating systems, so we check it on
-    // all platforms.
-    if (StringEndsWith(leafName, NS_LITERAL_STRING(".sqlite-journal")) ||
-        leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
+    // Skip Desktop Service Store (.DS_Store) files. These files are only used
+    // on Mac OS X, but the profile can be shared across different operating
+    // systems, so we check it on all platforms.
+    if (leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
+      continue;
+    }
+
+    // Skip SQLite temporary files. These files take up space on disk but will
+    // be deleted as soon as the database is opened, so we don't count them
+    // towards quota.
+    if (StringEndsWith(leafName, journalSuffix) ||
+        StringEndsWith(leafName, shmSuffix)) {
+      continue;
+    }
+
+    // The SQLite WAL file does count towards quota, but it is handled below
+    // once we find the actual database file.
+    if (StringEndsWith(leafName, walSuffix)) {
       continue;
     }
 
     nsDependentSubstring dbBaseFilename;
     if (!GetDatabaseBaseFilename(leafName, dbBaseFilename)) {
       unknownFiles.AppendElement(file);
       continue;
     }
 
-    nsString fmDirectoryBaseName = dbBaseFilename + filesSuffix;
-
     nsCOMPtr<nsIFile> fmDirectory;
     rv = directory->Clone(getter_AddRefs(fmDirectory));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
+    nsString fmDirectoryBaseName = dbBaseFilename + filesSuffix;
+
     rv = fmDirectory->Append(fmDirectoryBaseName);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
+    nsCOMPtr<nsIFile> walFile;
+    if (aUsageInfo) {
+      rv = directory->Clone(getter_AddRefs(walFile));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      rv = walFile->Append(dbBaseFilename + walSuffix);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+
     FileManagerInitInfo* initInfo = initInfos.AppendElement();
     initInfo->mDirectory.swap(fmDirectory);
     initInfo->mDatabaseFile.swap(file);
+    initInfo->mDatabaseWALFile.swap(walFile);
 
     validSubdirs.PutEntry(fmDirectoryBaseName);
   }
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
@@ -9457,16 +9747,17 @@ QuotaClient::InitOrigin(PersistenceType 
       return rv;
     }
   }
 
   for (uint32_t count = initInfos.Length(), i = 0; i < count; i++) {
     FileManagerInitInfo& initInfo = initInfos[i];
     MOZ_ASSERT(initInfo.mDirectory);
     MOZ_ASSERT(initInfo.mDatabaseFile);
+    MOZ_ASSERT_IF(aUsageInfo, initInfo.mDatabaseWALFile);
 
     rv = FileManager::InitDirectory(initInfo.mDirectory,
                                     initInfo.mDatabaseFile,
                                     aPersistenceType,
                                     aGroup,
                                     aOrigin);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
@@ -9478,41 +9769,61 @@ QuotaClient::InitOrigin(PersistenceType 
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       MOZ_ASSERT(fileSize >= 0);
 
       aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
 
+      rv = initInfo.mDatabaseWALFile->GetFileSize(&fileSize);
+      if (NS_SUCCEEDED(rv)) {
+        MOZ_ASSERT(fileSize >= 0);
+        aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
+      } else if (NS_WARN_IF(rv != NS_ERROR_FILE_NOT_FOUND &&
+                            rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)) {
+        return rv;
+      }
+
       uint64_t usage;
       rv = FileManager::GetUsage(initInfo.mDirectory, &usage);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       aUsageInfo->AppendToFileUsage(usage);
     }
   }
 
   // We have to do this after file manager initialization.
-  for (uint32_t count = unknownFiles.Length(), i = 0; i < count; i++) {
-    nsCOMPtr<nsIFile>& unknownFile = unknownFiles[i];
-
-    // Some temporary SQLite files could disappear during file manager
-    // initialization, so we have to check if the unknown file still exists.
-    bool exists;
-    rv = unknownFile->Exists(&exists);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    if (exists) {
-      return NS_ERROR_UNEXPECTED;
-    }
+  if (!unknownFiles.IsEmpty()) {
+#ifdef DEBUG
+    for (uint32_t count = unknownFiles.Length(), i = 0; i < count; i++) {
+      nsCOMPtr<nsIFile>& unknownFile = unknownFiles[i];
+
+      nsString leafName;
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(unknownFile->GetLeafName(leafName)));
+
+      MOZ_ASSERT(!StringEndsWith(leafName, journalSuffix));
+      MOZ_ASSERT(!StringEndsWith(leafName, shmSuffix));
+      MOZ_ASSERT(!StringEndsWith(leafName, walSuffix));
+
+      nsString path;
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(unknownFile->GetPath(path)));
+      MOZ_ASSERT(!path.IsEmpty());
+
+      nsPrintfCString warning("Refusing to open databases for \"%s\" because "
+                              "an unexpected file exists in the storage "
+                              "area: \"%s\"",
+                              PromiseFlatCString(aOrigin).get(),
+                              NS_ConvertUTF16toUTF8(path).get());
+      NS_WARNING(warning.get());
+    }
+#endif
+    return NS_ERROR_UNEXPECTED;
   }
 
   return NS_OK;
 }
 
 nsresult
 QuotaClient::GetUsageForOrigin(PersistenceType aPersistenceType,
                                const nsACString& aGroup,
@@ -9691,29 +10002,42 @@ QuotaClient::GetUsageForDirectoryInterna
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (!entries) {
     return NS_OK;
   }
 
+  const NS_ConvertASCIItoUTF16 shmSuffix(kSQLiteSHMSuffix,
+                                         LiteralStringLength(kSQLiteSHMSuffix));
+
   bool hasMore;
   while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
          hasMore &&
          !aUsageInfo->Canceled()) {
     nsCOMPtr<nsISupports> entry;
     rv = entries->GetNext(getter_AddRefs(entry));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
     MOZ_ASSERT(file);
 
+    nsString leafName;
+    rv = file->GetLeafName(leafName);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (StringEndsWith(leafName, shmSuffix)) {
+      continue;
+    }
+
     bool isDirectory;
     rv = file->IsDirectory(&isDirectory);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     if (isDirectory) {
       if (aDatabaseFiles) {
@@ -10840,17 +11164,17 @@ FactoryOp::CheckPermission(ContentParent
   nsCString group;
   nsCString origin;
   bool isApp;
   rv = QuotaManager::GetInfoFromPrincipal(principal, &group, &origin, &isApp);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+#ifdef IDB_MOBILE
   if (persistenceType == PERSISTENCE_TYPE_PERSISTENT &&
       !QuotaManager::IsOriginWhitelistedForPersistentStorage(origin) &&
       !isApp) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 #endif
 
   PermissionRequestBase::PermissionValue permission;
@@ -11337,23 +11661,23 @@ OpenDatabaseOp::DoDatabaseWork()
   const NS_ConvertASCIItoUTF16 filesSuffix(kFileManagerDirectoryNameSuffix);
 
   rv = fmDirectory->Append(filename + filesSuffix);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCOMPtr<mozIStorageConnection> connection;
-  rv = CreateDatabaseConnection(dbFile,
-                                fmDirectory,
-                                databaseName,
-                                persistenceType,
-                                mGroup,
-                                mOrigin,
-                                getter_AddRefs(connection));
+  rv = CreateStorageConnection(dbFile,
+                               fmDirectory,
+                               databaseName,
+                               persistenceType,
+                               mGroup,
+                               mOrigin,
+                               getter_AddRefs(connection));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   AutoSetProgressHandler asph;
   rv = asph.Register(this, connection);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -12406,17 +12730,17 @@ DeleteDatabaseOp::LoadPreviousVersion(ns
 
   nsCOMPtr<mozIStorageService> ss =
     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
   nsCOMPtr<mozIStorageConnection> connection;
-  rv = ss->OpenDatabase(aDatabaseFile, getter_AddRefs(connection));
+  rv = OpenDatabaseAndHandleBusy(ss, aDatabaseFile, getter_AddRefs(connection));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
 #ifdef DEBUG
   {
     nsCOMPtr<mozIStorageStatement> stmt;
     rv = connection->CreateStatement(NS_LITERAL_CSTRING(
@@ -12725,16 +13049,84 @@ VersionChangeOp::RunOnMainThread()
   if (NS_WARN_IF(NS_FAILED(rv))) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   return NS_OK;
 }
 
+
+nsresult
+DeleteDatabaseOp::
+VersionChangeOp::DeleteFile(nsIFile* aDirectory,
+                            const nsAString& aFilename,
+                            QuotaManager* aQuotaManager)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aDirectory);
+  MOZ_ASSERT(!aFilename.IsEmpty());
+  MOZ_ASSERT_IF(aQuotaManager, mDeleteDatabaseOp->mEnforcingQuota);
+
+  MOZ_ASSERT(mDeleteDatabaseOp->mState == State_DatabaseWorkVersionChange);
+
+  PROFILER_LABEL("IndexedDB",
+                 "DeleteDatabaseOp::VersionChangeOp::DeleteFile",
+                 js::ProfileEntry::Category::STORAGE);
+
+  nsCOMPtr<nsIFile> file;
+  nsresult rv = aDirectory->Clone(getter_AddRefs(file));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = file->Append(aFilename);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  int64_t fileSize;
+
+  if (aQuotaManager) {
+    rv = file->GetFileSize(&fileSize);
+    if (rv == NS_ERROR_FILE_NOT_FOUND ||
+        rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+      return NS_OK;
+    }
+
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    MOZ_ASSERT(fileSize >= 0);
+  }
+
+  rv = file->Remove(false);
+  if (rv == NS_ERROR_FILE_NOT_FOUND ||
+      rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+    return NS_OK;
+  }
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (aQuotaManager && fileSize > 0) {
+    const PersistenceType& persistenceType =
+      mDeleteDatabaseOp->mCommonParams.metadata().persistenceType();
+
+    aQuotaManager->DecreaseUsageForOrigin(persistenceType,
+                                          mDeleteDatabaseOp->mGroup,
+                                          mDeleteDatabaseOp->mOrigin,
+                                          fileSize);
+  }
+
+  return NS_OK;
+}
+
 nsresult
 DeleteDatabaseOp::
 VersionChangeOp::RunOnIOThread()
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(mDeleteDatabaseOp->mState == State_DatabaseWorkVersionChange);
 
   PROFILER_LABEL("IndexedDB",
@@ -12742,110 +13134,94 @@ VersionChangeOp::RunOnIOThread()
                  js::ProfileEntry::Category::STORAGE);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread()) ||
       !OperationMayProceed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
+  const PersistenceType& persistenceType =
+    mDeleteDatabaseOp->mCommonParams.metadata().persistenceType();
+
+  QuotaManager* quotaManager =
+    mDeleteDatabaseOp->mEnforcingQuota ?
+    QuotaManager::Get() :
+    nullptr;
+
+  MOZ_ASSERT_IF(mDeleteDatabaseOp->mEnforcingQuota, quotaManager);
+
   nsCOMPtr<nsIFile> directory =
     GetFileForPath(mDeleteDatabaseOp->mDatabaseDirectoryPath);
   if (NS_WARN_IF(!directory)) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
-  nsCOMPtr<nsIFile> dbFile;
-  nsresult rv = directory->Clone(getter_AddRefs(dbFile));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = dbFile->Append(mDeleteDatabaseOp->mDatabaseFilenameBase +
-                      NS_LITERAL_STRING(".sqlite"));
+  // The database file counts towards quota.
+  nsAutoString filename =
+    mDeleteDatabaseOp->mDatabaseFilenameBase + NS_LITERAL_STRING(".sqlite");
+
+  nsresult rv = DeleteFile(directory, filename, quotaManager);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // .sqlite-journal files don't count towards quota.
+  const NS_ConvertASCIItoUTF16 journalSuffix(
+    kSQLiteJournalSuffix,
+    LiteralStringLength(kSQLiteJournalSuffix));
+
+  filename = mDeleteDatabaseOp->mDatabaseFilenameBase + journalSuffix;
+
+  rv = DeleteFile(directory, filename, /* doesn't count */ nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // .sqlite-shm files don't count towards quota.
+  const NS_ConvertASCIItoUTF16 shmSuffix(kSQLiteSHMSuffix,
+                                         LiteralStringLength(kSQLiteSHMSuffix));
+
+  filename = mDeleteDatabaseOp->mDatabaseFilenameBase + shmSuffix;
+
+  rv = DeleteFile(directory, filename, /* doesn't count */ nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // .sqlite-wal files do count towards quota.
+  const NS_ConvertASCIItoUTF16 walSuffix(kSQLiteWALSuffix,
+                                         LiteralStringLength(kSQLiteWALSuffix));
+
+  filename = mDeleteDatabaseOp->mDatabaseFilenameBase + walSuffix;
+
+  rv = DeleteFile(directory, filename, quotaManager);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIFile> fmDirectory;
+  rv = directory->Clone(getter_AddRefs(fmDirectory));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // The files directory counts towards quota.
+  const NS_ConvertASCIItoUTF16 filesSuffix(
+    kFileManagerDirectoryNameSuffix,
+    LiteralStringLength(kFileManagerDirectoryNameSuffix));
+
+  rv = fmDirectory->Append(mDeleteDatabaseOp->mDatabaseFilenameBase +
+                           filesSuffix);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   bool exists;
-  rv = dbFile->Exists(&exists);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  const nsString& databaseName =
-    mDeleteDatabaseOp->mCommonParams.metadata().name();
-  PersistenceType persistenceType =
-    mDeleteDatabaseOp->mCommonParams.metadata().persistenceType();
-
-  QuotaManager* quotaManager = QuotaManager::Get();
-  MOZ_ASSERT(quotaManager);
-
-  if (exists) {
-    int64_t fileSize;
-
-    if (mDeleteDatabaseOp->mEnforcingQuota) {
-      rv = dbFile->GetFileSize(&fileSize);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-    }
-
-    rv = dbFile->Remove(false);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    if (mDeleteDatabaseOp->mEnforcingQuota) {
-      quotaManager->DecreaseUsageForOrigin(persistenceType,
-                                           mDeleteDatabaseOp->mGroup,
-                                           mDeleteDatabaseOp->mOrigin,
-                                           fileSize);
-    }
-  }
-
-  nsCOMPtr<nsIFile> dbJournalFile;
-  rv = directory->Clone(getter_AddRefs(dbJournalFile));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = dbJournalFile->Append(mDeleteDatabaseOp->mDatabaseFilenameBase +
-                             NS_LITERAL_STRING(".sqlite-journal"));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = dbJournalFile->Exists(&exists);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  if (exists) {
-    rv = dbJournalFile->Remove(false);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
-  nsCOMPtr<nsIFile> fmDirectory;
-  rv = directory->Clone(getter_AddRefs(fmDirectory));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  const NS_ConvertASCIItoUTF16 filesSuffix(kFileManagerDirectoryNameSuffix);
-
-  rv = fmDirectory->Append(mDeleteDatabaseOp->mDatabaseFilenameBase +
-                           filesSuffix);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
   rv = fmDirectory->Exists(&exists);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (exists) {
     bool isDirectory;
     rv = fmDirectory->IsDirectory(&isDirectory);
@@ -12864,30 +13240,45 @@ VersionChangeOp::RunOnIOThread()
       rv = FileManager::GetUsage(fmDirectory, &usage);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
 
     rv = fmDirectory->Remove(true);
     if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    if (mDeleteDatabaseOp->mEnforcingQuota) {
+      // We may have deleted some files, check if we can and update quota
+      // information before returning the error.
+      if (mDeleteDatabaseOp->mEnforcingQuota) {
+        uint64_t newUsage;
+        if (NS_SUCCEEDED(FileManager::GetUsage(fmDirectory, &newUsage))) {
+          MOZ_ASSERT(newUsage <= usage);
+          usage = usage - newUsage;
+        }
+      }
+    }
+
+    if (mDeleteDatabaseOp->mEnforcingQuota && usage) {
       quotaManager->DecreaseUsageForOrigin(persistenceType,
                                            mDeleteDatabaseOp->mGroup,
                                            mDeleteDatabaseOp->mOrigin,
                                            usage);
     }
+
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
   }
 
   IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
   MOZ_ASSERT(mgr);
 
+  const nsString& databaseName =
+    mDeleteDatabaseOp->mCommonParams.metadata().name();
+
   mgr->InvalidateFileManager(persistenceType,
                              mDeleteDatabaseOp->mOrigin,
                              databaseName);
 
   rv = mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -17208,8 +17599,12 @@ DEBUGThreadSlower::AfterProcessNextEvent
   return NS_OK;
 }
 
 #endif // DEBUG
 
 } // namespace indexedDB
 } // namespace dom
 } // namespace mozilla
+
+#undef IDB_MOBILE
+#undef ASSERT_UNLESS_FUZZING
+#undef DISABLE_ASSERTS_FOR_FUZZING
--- a/dom/quota/QuotaObject.cpp
+++ b/dom/quota/QuotaObject.cpp
@@ -1,21 +1,72 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "QuotaObject.h"
 
+#include "mozilla/TypeTraits.h"
 #include "QuotaManager.h"
 #include "Utilities.h"
 
+#ifdef DEBUG
+#include "nsComponentManagerUtils.h"
+#include "nsIFile.h"
+#include "nsXPCOMCID.h"
+#endif
+
 USING_QUOTA_NAMESPACE
 
+namespace {
+
+template <typename T, typename U>
+void
+AssertPositiveIntegers(T aOne, U aTwo)
+{
+  static_assert(mozilla::IsIntegral<T>::value, "Not an integer!");
+  static_assert(mozilla::IsIntegral<U>::value, "Not an integer!");
+  MOZ_ASSERT(aOne >= 0);
+  MOZ_ASSERT(aTwo >= 0);
+}
+
+template <typename T, typename U>
+void
+AssertNoOverflow(T aOne, U aTwo)
+{
+  AssertPositiveIntegers(aOne, aTwo);
+  AssertNoOverflow(uint64_t(aOne), uint64_t(aTwo));
+}
+
+template <>
+void
+AssertNoOverflow<uint64_t, uint64_t>(uint64_t aOne, uint64_t aTwo)
+{
+  MOZ_ASSERT(UINT64_MAX - aOne >= aTwo);
+}
+
+template <typename T, typename U>
+void
+AssertNoUnderflow(T aOne, U aTwo)
+{
+  AssertPositiveIntegers(aOne, aTwo);
+  AssertNoUnderflow(uint64_t(aOne), uint64_t(aTwo));
+}
+
+template <>
+void
+AssertNoUnderflow<uint64_t, uint64_t>(uint64_t aOne, uint64_t aTwo)
+{
+  MOZ_ASSERT(aOne >= aTwo);
+}
+
+} // anonymous namespace
+
 void
 QuotaObject::AddRef()
 {
   QuotaManager* quotaManager = QuotaManager::Get();
   if (!quotaManager) {
     NS_ERROR("Null quota manager, this shouldn't happen, possible leak!");
 
     ++mRefCnt;
@@ -59,41 +110,75 @@ QuotaObject::Release()
   }
 
   delete this;
 }
 
 void
 QuotaObject::UpdateSize(int64_t aSize)
 {
+  MOZ_ASSERT(aSize >= 0);
+
+#ifdef DEBUG
+  {
+    nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+    MOZ_ASSERT(file);
+
+    MOZ_ASSERT(NS_SUCCEEDED(file->InitWithPath(mPath)));
+
+    bool exists;
+    MOZ_ASSERT(NS_SUCCEEDED(file->Exists(&exists)));
+
+    if (exists) {
+      int64_t fileSize;
+      MOZ_ASSERT(NS_SUCCEEDED(file->GetFileSize(&fileSize)));
+
+      MOZ_ASSERT(aSize == fileSize);
+    } else {
+      MOZ_ASSERT(!aSize);
+    }
+  }
+#endif
+
   QuotaManager* quotaManager = QuotaManager::Get();
   NS_ASSERTION(quotaManager, "Shouldn't be null!");
 
   MutexAutoLock lock(quotaManager->mQuotaMutex);
 
-  if (!mOriginInfo) {
+  if (!mOriginInfo || mSize == aSize) {
     return;
   }
 
+  AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, mSize);
+  quotaManager->mTemporaryStorageUsage -= mSize;
+
   GroupInfo* groupInfo = mOriginInfo->mGroupInfo;
 
-  quotaManager->mTemporaryStorageUsage -= mSize;
+  AssertNoUnderflow(groupInfo->mUsage, mSize);
   groupInfo->mUsage -= mSize;
+
+  AssertNoUnderflow(mOriginInfo->mUsage, mSize);
   mOriginInfo->mUsage -= mSize;
 
   mSize = aSize;
 
+  AssertNoOverflow(mOriginInfo->mUsage, mSize);
   mOriginInfo->mUsage += mSize;
+
+  AssertNoOverflow(groupInfo->mUsage, mSize);
   groupInfo->mUsage += mSize;
+
+  AssertNoOverflow(quotaManager->mTemporaryStorageUsage, mSize);
   quotaManager->mTemporaryStorageUsage += mSize;
 }
 
 bool
 QuotaObject::MaybeAllocateMoreSpace(int64_t aOffset, int32_t aCount)
 {
+  AssertNoOverflow(aOffset, aCount);
   int64_t end = aOffset + aCount;
 
   QuotaManager* quotaManager = QuotaManager::Get();
   NS_ASSERTION(quotaManager, "Shouldn't be null!");
 
   MutexAutoLock lock(quotaManager->mQuotaMutex);
 
   if (mSize >= end || !mOriginInfo) {
@@ -101,36 +186,42 @@ QuotaObject::MaybeAllocateMoreSpace(int6
   }
 
   GroupInfo* groupInfo = mOriginInfo->mGroupInfo;
 
   nsRefPtr<GroupInfo> complementaryGroupInfo =
     groupInfo->mGroupInfoPair->LockedGetGroupInfo(
       ComplementaryPersistenceType(groupInfo->mPersistenceType));
 
+  AssertNoUnderflow(end, mSize);
   uint64_t delta = end - mSize;
 
+  AssertNoOverflow(mOriginInfo->mUsage, delta);
   uint64_t newUsage = mOriginInfo->mUsage + delta;
 
   // Temporary storage has no limit for origin usage (there's a group and the
   // global limit though).
 
+  AssertNoOverflow(groupInfo->mUsage, delta);
   uint64_t newGroupUsage = groupInfo->mUsage + delta;
 
   uint64_t groupUsage = groupInfo->mUsage;
   if (complementaryGroupInfo) {
+    AssertNoOverflow(groupUsage, complementaryGroupInfo->mUsage);
     groupUsage += complementaryGroupInfo->mUsage;
   }
 
   // Temporary storage has a hard limit for group usage (20 % of the global
   // limit).
+  AssertNoOverflow(groupUsage, delta);
   if (groupUsage + delta > quotaManager->GetGroupLimit()) {
     return false;
   }
 
+  AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta);
   uint64_t newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage +
                                       delta;
 
   if (newTemporaryStorageUsage > quotaManager->mTemporaryStorageLimit) {
     // This will block the thread without holding the lock while waitting.
 
     nsAutoTArray<OriginInfo*, 10> originInfos;
     uint64_t sizeToBeFreed =
@@ -176,54 +267,59 @@ QuotaObject::MaybeAllocateMoreSpace(int6
 #endif
 
       origins.AppendElement(OriginParams(persistenceType, origin, isApp));
     }
 
     // We unlocked and relocked several times so we need to recompute all the
     // essential variables and recheck the group limit.
 
+    AssertNoUnderflow(end, mSize);
     delta = end - mSize;
 
+    AssertNoOverflow(mOriginInfo->mUsage, delta);
     newUsage = mOriginInfo->mUsage + delta;
 
+    AssertNoOverflow(groupInfo->mUsage, delta);
     newGroupUsage = groupInfo->mUsage + delta;
 
     groupUsage = groupInfo->mUsage;
     if (complementaryGroupInfo) {
+      AssertNoOverflow(groupUsage, complementaryGroupInfo->mUsage);
       groupUsage += complementaryGroupInfo->mUsage;
     }
 
+    AssertNoOverflow(groupUsage, delta);
     if (groupUsage + delta > quotaManager->GetGroupLimit()) {
       // Unfortunately some other thread increased the group usage in the
       // meantime and we are not below the group limit anymore.
 
       // However, the origin eviction must be finalized in this case too.
       MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
 
       quotaManager->FinalizeOriginEviction(origins);
 
       return false;
     }
 
+    AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta);
     newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage + delta;
 
     NS_ASSERTION(newTemporaryStorageUsage <=
                  quotaManager->mTemporaryStorageLimit, "How come?!");
 
     // Ok, we successfully freed enough space and the operation can continue
     // without throwing the quota error.
-
     mOriginInfo->mUsage = newUsage;
     groupInfo->mUsage = newGroupUsage;
     quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage;;
 
     // Some other thread could increase the size in the meantime, but no more
     // than this one.
-    NS_ASSERTION(mSize < end, "This shouldn't happen!");
+    MOZ_ASSERT(mSize < end);
     mSize = end;
 
     // Finally, release IO thread only objects and allow next synchronized
     // ops for the evicted origins.
     MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
 
     quotaManager->FinalizeOriginEviction(origins);
 
@@ -239,23 +335,26 @@ QuotaObject::MaybeAllocateMoreSpace(int6
   return true;
 }
 
 void
 OriginInfo::LockedDecreaseUsage(int64_t aSize)
 {
   AssertCurrentThreadOwnsQuotaMutex();
 
+  AssertNoUnderflow(mUsage, aSize);
   mUsage -= aSize;
 
+  AssertNoUnderflow(mGroupInfo->mUsage, aSize);
   mGroupInfo->mUsage -= aSize;
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
+  AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, aSize);
   quotaManager->mTemporaryStorageUsage -= aSize;
 }
 
 // static
 PLDHashOperator
 OriginInfo::ClearOriginInfoCallback(const nsAString& aKey,
                                     QuotaObject* aValue,
                                     void* aUserArg)
@@ -289,39 +388,41 @@ void
 GroupInfo::LockedAddOriginInfo(OriginInfo* aOriginInfo)
 {
   AssertCurrentThreadOwnsQuotaMutex();
 
   NS_ASSERTION(!mOriginInfos.Contains(aOriginInfo),
                "Replacing an existing entry!");
   mOriginInfos.AppendElement(aOriginInfo);
 
+  AssertNoOverflow(mUsage, aOriginInfo->mUsage);
   mUsage += aOriginInfo->mUsage;
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
+  AssertNoOverflow(quotaManager->mTemporaryStorageUsage, aOriginInfo->mUsage);
   quotaManager->mTemporaryStorageUsage += aOriginInfo->mUsage;
 }
 
 void
 GroupInfo::LockedRemoveOriginInfo(const nsACString& aOrigin)
 {
   AssertCurrentThreadOwnsQuotaMutex();
 
   for (uint32_t index = 0; index < mOriginInfos.Length(); index++) {
     if (mOriginInfos[index]->mOrigin == aOrigin) {
-      MOZ_ASSERT(mUsage >= mOriginInfos[index]->mUsage);
+      AssertNoUnderflow(mUsage, mOriginInfos[index]->mUsage);
       mUsage -= mOriginInfos[index]->mUsage;
 
       QuotaManager* quotaManager = QuotaManager::Get();
       MOZ_ASSERT(quotaManager);
 
-      MOZ_ASSERT(quotaManager->mTemporaryStorageUsage >=
-                 mOriginInfos[index]->mUsage);
+      AssertNoUnderflow(quotaManager->mTemporaryStorageUsage,
+                        mOriginInfos[index]->mUsage);
       quotaManager->mTemporaryStorageUsage -= mOriginInfos[index]->mUsage;
 
       mOriginInfos.RemoveElementAt(index);
 
       return;
     }
   }
 }
@@ -332,20 +433,20 @@ GroupInfo::LockedRemoveOriginInfos()
   AssertCurrentThreadOwnsQuotaMutex();
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
   for (uint32_t index = mOriginInfos.Length(); index > 0; index--) {
     OriginInfo* originInfo = mOriginInfos[index - 1];
 
-    MOZ_ASSERT(mUsage >= originInfo->mUsage);
+    AssertNoUnderflow(mUsage, originInfo->mUsage);
     mUsage -= originInfo->mUsage;
 
-    MOZ_ASSERT(quotaManager->mTemporaryStorageUsage >= originInfo->mUsage);
+    AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, originInfo->mUsage);
     quotaManager->mTemporaryStorageUsage -= originInfo->mUsage;
 
     mOriginInfos.RemoveElementAt(index - 1);
   }
 }
 
 nsRefPtr<GroupInfo>&
 GroupInfoPair::GetGroupInfoForPersistenceType(PersistenceType aPersistenceType)
--- a/storage/src/TelemetryVFS.cpp
+++ b/storage/src/TelemetryVFS.cpp
@@ -139,16 +139,208 @@ struct telemetry_file {
 
   // quota object for this file
   nsRefPtr<QuotaObject> quotaObject;
 
   // This contains the vfs that actually does work
   sqlite3_file pReal[1];
 };
 
+const char*
+DatabasePathFromWALPath(const char *zWALName)
+{
+  /**
+   * Do some sketchy pointer arithmetic to find the parameter key. The WAL
+   * filename is in the middle of a big allocated block that contains:
+   *
+   *   - Random Values
+   *   - Main Database Path
+   *   - \0
+   *   - Multiple URI components consisting of:
+   *     - Key
+   *     - \0
+   *     - Value
+   *     - \0
+   *   - \0
+   *   - Journal Path
+   *   - \0
+   *   - WAL Path (zWALName)
+   *   - \0
+   *
+   * Because the main database path is preceded by a random value we have to be
+   * careful when trying to figure out when we should terminate this loop.
+   */
+  MOZ_ASSERT(zWALName);
+
+  nsDependentCSubstring dbPath(zWALName, strlen(zWALName));
+
+  // Chop off the "-wal" suffix.
+  NS_NAMED_LITERAL_CSTRING(kWALSuffix, "-wal");
+  MOZ_ASSERT(StringEndsWith(dbPath, kWALSuffix));
+
+  dbPath.Rebind(zWALName, dbPath.Length() - kWALSuffix.Length());
+  MOZ_ASSERT(!dbPath.IsEmpty());
+
+  // We want to scan to the end of the key/value URI pairs. Skip the preceding
+  // null and go to the last char of the journal path.
+  const char* cursor = zWALName - 2;
+
+  // Make sure we just skipped a null.
+  MOZ_ASSERT(!*(cursor + 1));
+
+  // Walk backwards over the journal path.
+  while (*cursor) {
+    cursor--;
+  }
+
+  // There should be another null here.
+  cursor--;
+  MOZ_ASSERT(!*cursor);
+
+  // Back up one more char to the last char of the previous string. It may be
+  // the database path or it may be a key/value URI pair.
+  cursor--;
+
+#ifdef DEBUG
+  {
+    // Verify that we just walked over the journal path. Account for the two
+    // nulls we just skipped.
+    const char *journalStart = cursor + 3;
+
+    nsDependentCSubstring journalPath(journalStart,
+                                      strlen(journalStart));
+
+    // Chop off the "-journal" suffix.
+    NS_NAMED_LITERAL_CSTRING(kJournalSuffix, "-journal");
+    MOZ_ASSERT(StringEndsWith(journalPath, kJournalSuffix));
+
+    journalPath.Rebind(journalStart,
+                        journalPath.Length() - kJournalSuffix.Length());
+    MOZ_ASSERT(!journalPath.IsEmpty());
+
+    // Make sure that the database name is a substring of the journal name.
+    MOZ_ASSERT(journalPath == dbPath);
+  }
+#endif
+
+  // Now we're either at the end of the key/value URI pairs or we're at the
+  // end of the database path. Carefully walk backwards one character at a
+  // time to do this safely without running past the beginning of the database
+  // path.
+  const char *const dbPathStart = dbPath.BeginReading();
+  const char *dbPathCursor = dbPath.EndReading() - 1;
+  bool isDBPath = true;
+
+  while (true) {
+    MOZ_ASSERT(*dbPathCursor, "dbPathCursor should never see a null char!");
+
+    if (isDBPath) {
+      isDBPath = dbPathStart <= dbPathCursor &&
+                 *dbPathCursor == *cursor &&
+                 *cursor;
+    }
+
+    if (!isDBPath) {
+      // This isn't the database path so it must be a value. Scan past it and
+      // the key also.
+      for (size_t stringCount = 0; stringCount < 2; stringCount++) {
+        // Scan past the string to the preceding null character.
+        while (*cursor) {
+          cursor--;
+        }
+
+        // Back up one more char to the last char of preceding string.
+        cursor--;
+      }
+
+      // Reset and start again.
+      dbPathCursor = dbPath.EndReading() - 1;
+      isDBPath = true;
+
+      continue;
+    }
+
+    MOZ_ASSERT(isDBPath);
+    MOZ_ASSERT(*cursor);
+
+    if (dbPathStart == dbPathCursor) {
+      // Found the full database path, we're all done.
+      MOZ_ASSERT(nsDependentCString(cursor) == dbPath);
+      return cursor;
+    }
+
+    // Change the cursors and go through the loop again.
+    cursor--;
+    dbPathCursor--;
+  }
+
+  MOZ_CRASH("Should never get here!");
+}
+
+already_AddRefed<QuotaObject>
+GetQuotaObjectFromNameAndParameters(const char *zName,
+                                    const char *zURIParameterKey)
+{
+  MOZ_ASSERT(zName);
+  MOZ_ASSERT(zURIParameterKey);
+
+  const char *persistenceType =
+    persistenceType = sqlite3_uri_parameter(zURIParameterKey,
+                                            "persistenceType");
+  if (!persistenceType) {
+    return nullptr;
+  }
+
+  const char *group = sqlite3_uri_parameter(zURIParameterKey, "group");
+  if (!group) {
+    NS_WARNING("SQLite URI had 'persistenceType' but not 'group'?!");
+    return nullptr;
+  }
+
+  const char *origin = sqlite3_uri_parameter(zURIParameterKey, "origin");
+  if (!origin) {
+    NS_WARNING("SQLite URI had 'persistenceType' and 'group' but not "
+               "'origin'?!");
+    return nullptr;
+  }
+
+  QuotaManager *quotaManager = QuotaManager::Get();
+  MOZ_ASSERT(quotaManager);
+
+  return quotaManager->GetQuotaObject(
+    PersistenceTypeFromText(nsDependentCString(persistenceType)),
+    nsDependentCString(group),
+    nsDependentCString(origin),
+    NS_ConvertUTF8toUTF16(zName));
+}
+
+void
+MaybeEstablishQuotaControl(const char *zName,
+                           telemetry_file *pFile,
+                           int flags)
+{
+  MOZ_ASSERT(pFile);
+  MOZ_ASSERT(!pFile->quotaObject);
+
+  if (!(flags & (SQLITE_OPEN_URI | SQLITE_OPEN_WAL))) {
+    return;
+  }
+
+  MOZ_ASSERT(zName);
+
+  const char *zURIParameterKey = (flags & SQLITE_OPEN_WAL) ?
+                                 DatabasePathFromWALPath(zName) :
+                                 zName;
+
+  MOZ_ASSERT(zURIParameterKey);
+
+  pFile->quotaObject =
+    GetQuotaObjectFromNameAndParameters(zName, zURIParameterKey);
+}
+
 /*
 ** Close a telemetry_file.
 */
 int
 xClose(sqlite3_file *pFile)
 {
   telemetry_file *p = (telemetry_file *)pFile;
   int rc;
@@ -193,57 +385,65 @@ xWrite(sqlite3_file *pFile, const void *
   IOThreadAutoTimer ioTimer(p->histograms->writeMS, IOInterposeObserver::OpWrite);
   int rc;
   rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst);
   Telemetry::Accumulate(p->histograms->writeB, rc == SQLITE_OK ? iAmt : 0);
   return rc;
 }
 
 /*
+** Return the current file-size of a telemetry_file.
+*/
+int
+xFileSize(sqlite3_file *pFile, sqlite_int64 *pSize)
+{
+  IOThreadAutoTimer ioTimer(IOInterposeObserver::OpStat);
+  telemetry_file *p = (telemetry_file *)pFile;
+  int rc;
+  rc = p->pReal->pMethods->xFileSize(p->pReal, pSize);
+  return rc;
+}
+
+/*
 ** Truncate a telemetry_file.
 */
 int
 xTruncate(sqlite3_file *pFile, sqlite_int64 size)
 {
   IOThreadAutoTimer ioTimer(Telemetry::MOZ_SQLITE_TRUNCATE_MS);
   telemetry_file *p = (telemetry_file *)pFile;
   int rc;
   Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_TRUNCATE_MS> timer;
   rc = p->pReal->pMethods->xTruncate(p->pReal, size);
   if (rc == SQLITE_OK && p->quotaObject) {
-    p->quotaObject->UpdateSize(size);
+    // xTruncate doesn't always set the size of the file to the exact size
+    // requested (e.g. if a growth increment has been specified it will round up
+    // to the next multiple of the chunk size). Use xFileSize to see what the
+    // real size is.
+    sqlite_int64 newSize;
+    rc = xFileSize(pFile, &newSize);
+    if (rc == SQLITE_OK) {
+      p->quotaObject->UpdateSize(newSize);
+    }
   }
   return rc;
 }
 
 /*
 ** Sync a telemetry_file.
 */
 int
 xSync(sqlite3_file *pFile, int flags)
 {
   telemetry_file *p = (telemetry_file *)pFile;
   IOThreadAutoTimer ioTimer(p->histograms->syncMS, IOInterposeObserver::OpFSync);
   return p->pReal->pMethods->xSync(p->pReal, flags);
 }
 
 /*
-** Return the current file-size of a telemetry_file.
-*/
-int
-xFileSize(sqlite3_file *pFile, sqlite_int64 *pSize)
-{
-  IOThreadAutoTimer ioTimer(IOInterposeObserver::OpStat);
-  telemetry_file *p = (telemetry_file *)pFile;
-  int rc;
-  rc = p->pReal->pMethods->xFileSize(p->pReal, pSize);
-  return rc;
-}
-
-/*
 ** Lock a telemetry_file.
 */
 int
 xLock(sqlite3_file *pFile, int eLock)
 {
   telemetry_file *p = (telemetry_file *)pFile;
   int rc;
   rc = p->pReal->pMethods->xLock(p->pReal, eLock);
@@ -381,30 +581,17 @@ xOpen(sqlite3_vfs* vfs, const char *zNam
       continue;
     char c = match[strlen(h->name)];
     // include -wal/-journal too
     if (!c || c == '-')
       break;
   }
   p->histograms = h;
 
-  const char* persistenceType;
-  const char* group;
-  const char* origin;
-  if ((flags & SQLITE_OPEN_URI) &&
-      (persistenceType = sqlite3_uri_parameter(zName, "persistenceType")) &&
-      (group = sqlite3_uri_parameter(zName, "group")) &&
-      (origin = sqlite3_uri_parameter(zName, "origin"))) {
-    QuotaManager* quotaManager = QuotaManager::Get();
-    MOZ_ASSERT(quotaManager);
-
-    p->quotaObject = quotaManager->GetQuotaObject(PersistenceTypeFromText(
-      nsDependentCString(persistenceType)), nsDependentCString(group),
-      nsDependentCString(origin), NS_ConvertUTF8toUTF16(zName));
-  }
+  MaybeEstablishQuotaControl(zName, p, flags);
 
   rc = orig_vfs->xOpen(orig_vfs, zName, p->pReal, flags, pOutFlags);
   if( rc != SQLITE_OK )
     return rc;
   if( p->pReal->pMethods ){
     sqlite3_io_methods *pNew = new sqlite3_io_methods;
     const sqlite3_io_methods *pSub = p->pReal->pMethods;
     memset(pNew, 0, sizeof(*pNew));
@@ -446,17 +633,32 @@ xOpen(sqlite3_vfs* vfs, const char *zNam
   }
   return rc;
 }
 
 int
 xDelete(sqlite3_vfs* vfs, const char *zName, int syncDir)
 {
   sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
-  return orig_vfs->xDelete(orig_vfs, zName, syncDir);
+  int rc;
+  nsRefPtr<QuotaObject> quotaObject;
+
+  if (StringEndsWith(nsDependentCString(zName), NS_LITERAL_CSTRING("-wal"))) {
+    const char *zURIParameterKey = DatabasePathFromWALPath(zName);
+    MOZ_ASSERT(zURIParameterKey);
+
+    quotaObject = GetQuotaObjectFromNameAndParameters(zName, zURIParameterKey);
+  }
+
+  rc = orig_vfs->xDelete(orig_vfs, zName, syncDir);
+  if (rc == SQLITE_OK && quotaObject) {
+    quotaObject->UpdateSize(0);
+  }
+
+  return rc;
 }
 
 int
 xAccess(sqlite3_vfs *vfs, const char *zName, int flags, int *pResOut)
 {
   sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
   return orig_vfs->xAccess(orig_vfs, zName, flags, pResOut);
 }