Backed out changeset 14b9b4827805 (bug 1131776)
authorWes Kocher <wkocher@mozilla.com>
Mon, 30 Mar 2015 19:45:55 -0700
changeset 236627 28a053ae1f19ca070559fcd2777e69288e59942d
parent 236626 93bab937c624fa8c203139e819440333ef751697
child 236628 0b12fc331918ad340145eec08f9b784d70291dd0
push id57725
push userkwierso@gmail.com
push dateTue, 31 Mar 2015 02:46:25 +0000
treeherdermozilla-inbound@23912a439df3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1131776
milestone40.0a1
backs out14b9b48278051e7e43d62c9c5ef1516ae3853cd8
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
Backed out changeset 14b9b4827805 (bug 1131776)
dom/asmjscache/AsmJSCache.cpp
dom/indexedDB/ActorsParent.cpp
dom/indexedDB/Key.cpp
dom/indexedDB/Key.h
dom/indexedDB/test/unit/schema18upgrade_profile.zip
dom/indexedDB/test/unit/test_schema18upgrade.js
dom/indexedDB/test/unit/xpcshell-parent-process.ini
dom/quota/FileStreams.cpp
dom/quota/QuotaObject.cpp
dom/quota/QuotaObject.h
storage/src/TelemetryVFS.cpp
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/IndexedDB/key_valid.html
--- a/dom/asmjscache/AsmJSCache.cpp
+++ b/dom/asmjscache/AsmJSCache.cpp
@@ -828,25 +828,23 @@ MainProcessRunnable::OpenCacheFileForWri
 
   if (mEnforcingQuota) {
     // Create the QuotaObject before all file IO and keep it alive until caching
     // completes to get maximum assertion coverage in QuotaManager against
     // concurrent removal, etc.
     mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file);
     NS_ENSURE_STATE(mQuotaObject);
 
-    if (!mQuotaObject->MaybeUpdateSize(mWriteParams.mSize,
-                                       /* aTruncate */ false)) {
+    if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
       // If the request fails, it might be because mOrigin is using too much
-      // space (MaybeUpdateSize will not evict our own origin since it is
+      // space (MaybeAllocateMoreSpace will not evict our own origin since it is
       // active). Try to make some space by evicting LRU entries until there is
       // enough space.
       EvictEntries(mDirectory, mGroup, mOrigin, mWriteParams.mSize, mMetadata);
-      if (!mQuotaObject->MaybeUpdateSize(mWriteParams.mSize,
-                                         /* aTruncate */ false)) {
+      if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
         mResult = JS::AsmJSCache_QuotaExceeded;
         return NS_ERROR_FAILURE;
       }
     }
   }
 
   int32_t openFlags = PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE;
   rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -46,17 +46,16 @@
 #include "mozilla/dom/quota/OriginOrPatternString.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/quota/UsageInfo.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/InputStreamParams.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/ipc/PBackground.h"
-#include "mozilla/storage/Variant.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsClassHashtable.h"
 #include "nsCOMPtr.h"
 #include "nsDataHashtable.h"
 #include "nsEscape.h"
 #include "nsHashKeys.h"
 #include "nsNetUtil.h"
 #include "nsIAppsService.h"
@@ -133,17 +132,17 @@ class VersionChangeTransaction;
  ******************************************************************************/
 
 // If JS_STRUCTURED_CLONE_VERSION changes then we need to update our major
 // schema version.
 static_assert(JS_STRUCTURED_CLONE_VERSION == 5,
               "Need to update the major schema version.");
 
 // Major schema version. Bump for almost everything.
-const uint32_t kMajorSchemaVersion = 18;
+const uint32_t kMajorSchemaVersion = 17;
 
 // Minor schema version. Should almost always be 0 (maybe bump on release
 // branches if we have to).
 const uint32_t kMinorSchemaVersion = 0;
 
 // The schema version we store in the SQLite database is a (signed) 32-bit
 // integer. The major version is left-shifted 4 bits so the max value is
 // 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF.
@@ -245,28 +244,16 @@ const int32_t kDEBUGThreadPriority = nsI
 const uint32_t kDEBUGThreadSleepMS = 0;
 
 const int32_t kDEBUGTransactionThreadPriority =
   nsISupportsPriority::PRIORITY_NORMAL;
 const uint32_t kDEBUGTransactionThreadSleepMS = 0;
 
 #endif
 
-struct MozFreeDeleter
-{
-  void
-  operator()(void* aPtr) const
-  {
-    moz_free(aPtr);
-  }
-};
-
-template <typename T>
-using UniqueMozFreePtr = UniquePtr<T, MozFreeDeleter>;
-
 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 N - 1;
@@ -321,19 +308,16 @@ public:
   {
     // This can happen either on the QuotaManager IO thread or on a
     // versionchange transaction thread. These threads can never race so this is
     // totally safe.
   }
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullObjectStoreMetadata);
 
-  bool
-  HasLiveIndexes() const;
-
 private:
   ~FullObjectStoreMetadata()
   { }
 };
 
 typedef nsRefPtrHashtable<nsUint64HashKey, FullObjectStoreMetadata>
   ObjectStoreTable;
 
@@ -440,73 +424,16 @@ private:
       closure->mMetadata = aValue;
       return PL_DHASH_STOP;
     }
 
     return PL_DHASH_NEXT;
   }
 };
 
-struct IndexDataValue final
-{
-  int64_t mIndexId;
-  Key mKey;
-  bool mUnique;
-
-  IndexDataValue()
-    : mIndexId(0)
-    , mUnique(false)
-  {
-    MOZ_COUNT_CTOR(IndexDataValue);
-  }
-
-  explicit
-  IndexDataValue(const IndexDataValue& aOther)
-    : mIndexId(aOther.mIndexId)
-    , mKey(aOther.mKey)
-    , mUnique(aOther.mUnique)
-  {
-    MOZ_ASSERT(!aOther.mKey.IsUnset());
-
-    MOZ_COUNT_CTOR(IndexDataValue);
-  }
-
-  IndexDataValue(int64_t aIndexId, bool aUnique, const Key& aKey)
-    : mIndexId(aIndexId)
-    , mKey(aKey)
-    , mUnique(aUnique)
-  {
-    MOZ_ASSERT(!aKey.IsUnset());
-
-    MOZ_COUNT_CTOR(IndexDataValue);
-  }
-
-  ~IndexDataValue()
-  {
-    MOZ_COUNT_DTOR(IndexDataValue);
-  }
-
-  bool
-  operator==(const IndexDataValue& aOther) const
-  {
-    return mIndexId == aOther.mIndexId &&
-           mKey == aOther.mKey;
-  }
-
-  bool
-  operator<(const IndexDataValue& aOther) const
-  {
-    if (mIndexId == aOther.mIndexId) {
-      return mKey < aOther.mKey;
-    }
-
-    return mIndexId < aOther.mIndexId;
-  }
-};
-
 /*******************************************************************************
  * SQLite functions
  ******************************************************************************/
 
 int32_t
 MakeSchemaVersion(uint32_t aMajorSchemaVersion,
                   uint32_t aMinorSchemaVersion)
 {
@@ -590,345 +517,16 @@ GetDatabaseFilename(const nsAString& aNa
     } else {
       substring.Append(*forwardIter++);
     }
   }
 
   aDatabaseFilename.AppendASCII(substring.get(), substring.Length());
 }
 
-uint32_t
-CompressedByteCountForNumber(uint64_t aNumber)
-{
-  MOZ_ASSERT(aNumber);
-
-  // All bytes have 7 bits available.
-  uint32_t count = 1;
-  while ((aNumber >>= 7)) {
-    count++;
-  }
-
-  return count;
-}
-
-uint32_t
-CompressedByteCountForIndexId(int64_t aIndexId)
-{
-  MOZ_ASSERT(aIndexId);
-  MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
-              "Overflow!");
-
-  return CompressedByteCountForNumber(uint64_t(aIndexId * 2));
-}
-
-void
-WriteCompressedNumber(uint64_t aNumber, uint8_t** aIterator)
-{
-  MOZ_ASSERT(aIterator);
-  MOZ_ASSERT(*aIterator);
-
-  uint8_t*& buffer = *aIterator;
-
-#ifdef DEBUG
-  const uint8_t* bufferStart = buffer;
-  const uint64_t originalNumber = aNumber;
-#endif
-
-  while (true) {
-    uint64_t shiftedNumber = aNumber >> 7;
-    if (shiftedNumber) {
-      *buffer++ = uint8_t(0x80 | (aNumber & 0x7f));
-      aNumber = shiftedNumber;
-    } else {
-      *buffer++ = uint8_t(aNumber);
-      break;
-    }
-  }
-
-  MOZ_ASSERT(buffer > bufferStart);
-  MOZ_ASSERT(uint32_t(buffer - bufferStart) ==
-               CompressedByteCountForNumber(originalNumber));
-}
-
-uint64_t
-ReadCompressedNumber(const uint8_t** aIterator, const uint8_t* aEnd)
-{
-  MOZ_ASSERT(aIterator);
-  MOZ_ASSERT(*aIterator);
-  MOZ_ASSERT(aEnd);
-  MOZ_ASSERT(*aIterator < aEnd);
-
-  const uint8_t*& buffer = *aIterator;
-
-  uint8_t shiftCounter = 0;
-  uint64_t result = 0;
-
-  while (true) {
-    MOZ_ASSERT(shiftCounter <= 56, "Shifted too many bits!");
-
-    result += (uint64_t(*buffer & 0x7f) << shiftCounter);
-    shiftCounter += 7;
-
-    if (!(*buffer++ & 0x80)) {
-      break;
-    }
-
-    if (NS_WARN_IF(buffer == aEnd)) {
-      MOZ_ASSERT(false);
-      break;
-    }
-  }
-
-  return result;
-}
-
-void
-WriteCompressedIndexId(int64_t aIndexId, bool aUnique, uint8_t** aIterator)
-{
-  MOZ_ASSERT(aIndexId);
-  MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
-             "Overflow!");
-  MOZ_ASSERT(aIterator);
-  MOZ_ASSERT(*aIterator);
-
-  const uint64_t indexId = (uint64_t(aIndexId * 2) | (aUnique ? 1 : 0));
-  WriteCompressedNumber(indexId, aIterator);
-}
-
-void
-ReadCompressedIndexId(const uint8_t** aIterator,
-                      const uint8_t* aEnd,
-                      int64_t* aIndexId,
-                      bool* aUnique)
-{
-  MOZ_ASSERT(aIterator);
-  MOZ_ASSERT(*aIterator);
-  MOZ_ASSERT(aIndexId);
-  MOZ_ASSERT(aUnique);
-
-  uint64_t indexId = ReadCompressedNumber(aIterator, aEnd);
-
-  if (indexId % 2) {
-    *aUnique = true;
-    indexId--;
-  } else {
-    *aUnique = false;
-  }
-
-  MOZ_ASSERT(UINT64_MAX / 2 >= uint64_t(indexId), "Bad index id!");
-
-  *aIndexId = int64_t(indexId / 2);
-}
-
-// static
-nsresult
-MakeCompressedIndexDataValues(
-                          const FallibleTArray<IndexDataValue>& aIndexValues,
-                          UniqueMozFreePtr<uint8_t>& aCompressedIndexDataValues,
-                          uint32_t* aCompressedIndexDataValuesLength)
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-  MOZ_ASSERT(!IsOnBackgroundThread());
-  MOZ_ASSERT(!aCompressedIndexDataValues);
-  MOZ_ASSERT(aCompressedIndexDataValuesLength);
-
-  PROFILER_LABEL("IndexedDB",
-                 "MakeCompressedIndexDataValues",
-                 js::ProfileEntry::Category::STORAGE);
-
-  const uint32_t arrayLength = aIndexValues.Length();
-  if (!arrayLength) {
-    *aCompressedIndexDataValuesLength = 0;
-    return NS_OK;
-  }
-
-  // First calculate the size of the final buffer.
-  uint32_t blobDataLength = 0;
-
-  for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
-    const IndexDataValue& info = aIndexValues[arrayIndex];
-    const nsCString& keyBuffer = info.mKey.GetBuffer();
-    const uint32_t keyBufferLength = keyBuffer.Length();
-
-    MOZ_ASSERT(!keyBuffer.IsEmpty());
-
-    // Don't let |infoLength| overflow.
-    if (NS_WARN_IF(UINT32_MAX - keyBuffer.Length() <
-                   CompressedByteCountForIndexId(info.mIndexId) +
-                   CompressedByteCountForNumber(keyBufferLength))) {
-      IDB_REPORT_INTERNAL_ERR();
-      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-    }
-
-    const uint32_t infoLength =
-      CompressedByteCountForIndexId(info.mIndexId) +
-      CompressedByteCountForNumber(keyBufferLength) +
-      keyBufferLength;
-
-    // Don't let |blobDataLength| overflow.
-    if (NS_WARN_IF(UINT32_MAX - infoLength < blobDataLength)) {
-      IDB_REPORT_INTERNAL_ERR();
-      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-    }
-
-    blobDataLength += infoLength;
-  }
-
-  UniqueMozFreePtr<uint8_t> blobData(
-    static_cast<uint8_t*>(moz_malloc(blobDataLength)));
-  if (NS_WARN_IF(!blobData)) {
-    IDB_REPORT_INTERNAL_ERR();
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  uint8_t* blobDataIter = blobData.get();
-
-  for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
-    const IndexDataValue& info = aIndexValues[arrayIndex];
-    const nsCString& keyBuffer = info.mKey.GetBuffer();
-    const uint32_t keyBufferLength = keyBuffer.Length();
-
-    WriteCompressedIndexId(info.mIndexId, info.mUnique, &blobDataIter);
-    WriteCompressedNumber(keyBuffer.Length(), &blobDataIter);
-
-    memcpy(blobDataIter, keyBuffer.get(), keyBufferLength);
-    blobDataIter += keyBufferLength;
-  }
-
-  MOZ_ASSERT(blobDataIter == blobData.get() + blobDataLength);
-
-  aCompressedIndexDataValues.swap(blobData);
-  *aCompressedIndexDataValuesLength = uint32_t(blobDataLength);
-
-  return NS_OK;
-}
-
-nsresult
-ReadCompressedIndexDataValuesFromBlob(
-                                   const uint8_t* aBlobData,
-                                   uint32_t aBlobDataLength,
-                                   FallibleTArray<IndexDataValue>& aIndexValues)
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-  MOZ_ASSERT(!IsOnBackgroundThread());
-  MOZ_ASSERT(aBlobData);
-  MOZ_ASSERT(aBlobDataLength);
-  MOZ_ASSERT(aIndexValues.IsEmpty());
-
-  PROFILER_LABEL("IndexedDB",
-                 "ReadCompressedIndexDataValuesFromBlob",
-                 js::ProfileEntry::Category::STORAGE);
-
-  const uint8_t* blobDataIter = aBlobData;
-  const uint8_t* blobDataEnd = aBlobData + aBlobDataLength;
-
-  while (blobDataIter < blobDataEnd) {
-    int64_t indexId;
-    bool unique;
-    ReadCompressedIndexId(&blobDataIter, blobDataEnd, &indexId, &unique);
-
-    if (NS_WARN_IF(blobDataIter == blobDataEnd)) {
-      IDB_REPORT_INTERNAL_ERR();
-      return NS_ERROR_FILE_CORRUPTED;
-    }
-
-    // Read key buffer length.
-    const uint64_t keyBufferLength =
-      ReadCompressedNumber(&blobDataIter, blobDataEnd);
-
-    if (NS_WARN_IF(blobDataIter == blobDataEnd) ||
-        NS_WARN_IF(keyBufferLength > uint64_t(UINT32_MAX)) ||
-        NS_WARN_IF(blobDataIter + keyBufferLength > blobDataEnd)) {
-      IDB_REPORT_INTERNAL_ERR();
-      return NS_ERROR_FILE_CORRUPTED;
-    }
-
-    nsCString keyBuffer(reinterpret_cast<const char*>(blobDataIter),
-                        uint32_t(keyBufferLength));
-    blobDataIter += keyBufferLength;
-
-    if (NS_WARN_IF(!aIndexValues.InsertElementSorted(
-                      IndexDataValue(indexId, unique, Key(keyBuffer))))) {
-      IDB_REPORT_INTERNAL_ERR();
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-  }
-
-  MOZ_ASSERT(blobDataIter == blobDataEnd);
-
-  return NS_OK;
-}
-
-// static
-template <typename T>
-nsresult
-ReadCompressedIndexDataValuesFromSource(
-                                   T* aSource,
-                                   uint32_t aColumnIndex,
-                                   FallibleTArray<IndexDataValue>& aIndexValues)
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-  MOZ_ASSERT(!IsOnBackgroundThread());
-  MOZ_ASSERT(aSource);
-  MOZ_ASSERT(aIndexValues.IsEmpty());
-
-  int32_t columnType;
-  nsresult rv = aSource->GetTypeOfIndex(aColumnIndex, &columnType);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  if (columnType == mozIStorageStatement::VALUE_TYPE_NULL) {
-    return NS_OK;
-  }
-
-  MOZ_ASSERT(columnType == mozIStorageStatement::VALUE_TYPE_BLOB);
-
-  const uint8_t* blobData;
-  uint32_t blobDataLength;
-  rv = aSource->GetSharedBlob(aColumnIndex, &blobDataLength, &blobData);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  if (NS_WARN_IF(!blobDataLength)) {
-    IDB_REPORT_INTERNAL_ERR();
-    return NS_ERROR_FILE_CORRUPTED;
-  }
-
-  rv = ReadCompressedIndexDataValuesFromBlob(blobData,
-                                             blobDataLength,
-                                             aIndexValues);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
-}
-
-nsresult
-ReadCompressedIndexDataValues(mozIStorageStatement* aStatement,
-                              uint32_t aColumnIndex,
-                              FallibleTArray<IndexDataValue>& aIndexValues)
-{
-  return ReadCompressedIndexDataValuesFromSource(aStatement,
-                                                 aColumnIndex,
-                                                 aIndexValues);
-}
-
-nsresult
-ReadCompressedIndexDataValues(mozIStorageValueArray* aValues,
-                              uint32_t aColumnIndex,
-                              FallibleTArray<IndexDataValue>& aIndexValues)
-{
-  return ReadCompressedIndexDataValuesFromSource(aValues,
-                                                 aColumnIndex,
-                                                 aIndexValues);
-}
-
 nsresult
 CreateFileTables(mozIStorageConnection* aConnection)
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(aConnection);
 
   PROFILER_LABEL("IndexedDB",
                  "CreateFileTables",
@@ -1005,107 +603,124 @@ CreateTables(mozIStorageConnection* aCon
   MOZ_ASSERT(aConnection);
 
   PROFILER_LABEL("IndexedDB",
                  "CreateTables",
                  js::ProfileEntry::Category::STORAGE);
 
   // Table `database`
   nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE TABLE database"
-      "( name TEXT PRIMARY KEY"
-      ", origin TEXT NOT NULL"
-      ", version INTEGER NOT NULL DEFAULT 0"
-      ", last_vacuum_time INTEGER NOT NULL DEFAULT 0"
-      ", last_analyze_time INTEGER NOT NULL DEFAULT 0"
-      ", last_vacuum_size INTEGER NOT NULL DEFAULT 0"
-      ") WITHOUT ROWID;"
+    "CREATE TABLE database ("
+      "name TEXT NOT NULL, "
+      "version INTEGER NOT NULL DEFAULT 0"
+    ");"
   ));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Table `object_store`
   rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE TABLE object_store"
-      "( id INTEGER PRIMARY KEY"
-      ", auto_increment INTEGER NOT NULL DEFAULT 0"
-      ", name TEXT NOT NULL"
-      ", key_path TEXT"
-      ");"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Table `index`
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE TABLE object_store_index"
-      "( id INTEGER PRIMARY KEY"
-      ", object_store_id INTEGER NOT NULL"
-      ", name TEXT NOT NULL"
-      ", key_path TEXT NOT NULL"
-      ", unique_index INTEGER NOT NULL"
-      ", multientry INTEGER NOT NULL"
-      ", FOREIGN KEY (object_store_id) "
-          "REFERENCES object_store(id) "
-      ");"
+    "CREATE TABLE object_store ("
+      "id INTEGER PRIMARY KEY, "
+      "auto_increment INTEGER NOT NULL DEFAULT 0, "
+      "name TEXT NOT NULL, "
+      "key_path TEXT, "
+      "UNIQUE (name)"
+    ");"
   ));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Table `object_data`
   rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE TABLE object_data"
-      "( object_store_id INTEGER NOT NULL"
-      ", key BLOB NOT NULL"
-      ", index_data_values BLOB DEFAULT NULL"
-      ", file_ids TEXT"
-      ", data BLOB NOT NULL"
-      ", PRIMARY KEY (object_store_id, key)"
-      ", FOREIGN KEY (object_store_id) "
-          "REFERENCES object_store(id) "
-      ") WITHOUT ROWID;"
+    "CREATE TABLE object_data ("
+      "id INTEGER PRIMARY KEY, "
+      "object_store_id INTEGER NOT NULL, "
+      "key_value BLOB DEFAULT NULL, "
+      "file_ids TEXT, "
+      "data BLOB NOT NULL, "
+      "UNIQUE (object_store_id, key_value), "
+      "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
+        "CASCADE"
+    ");"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Table `index`
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TABLE object_store_index ("
+      "id INTEGER PRIMARY KEY, "
+      "object_store_id INTEGER NOT NULL, "
+      "name TEXT NOT NULL, "
+      "key_path TEXT NOT NULL, "
+      "unique_index INTEGER NOT NULL, "
+      "multientry INTEGER NOT NULL, "
+      "UNIQUE (object_store_id, name), "
+      "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
+        "CASCADE"
+    ");"
   ));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Table `index_data`
   rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE TABLE index_data"
-      "( index_id INTEGER NOT NULL"
-      ", value BLOB NOT NULL"
-      ", object_data_key BLOB NOT NULL"
-      ", object_store_id INTEGER NOT NULL"
-      ", PRIMARY KEY (index_id, value, object_data_key)"
-      ", FOREIGN KEY (index_id) "
-          "REFERENCES object_store_index(id) "
-      ", FOREIGN KEY (object_store_id, object_data_key) "
-          "REFERENCES object_data(object_store_id, key) "
-      ") WITHOUT ROWID;"
+    "CREATE TABLE index_data ("
+      "index_id INTEGER NOT NULL, "
+      "value BLOB NOT NULL, "
+      "object_data_key BLOB NOT NULL, "
+      "object_data_id INTEGER NOT NULL, "
+      "PRIMARY KEY (index_id, value, object_data_key), "
+      "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
+        "CASCADE, "
+      "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
+        "CASCADE"
+    ");"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Need this to make cascading deletes from object_data and object_store fast.
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE INDEX index_data_object_data_id_index "
+    "ON index_data (object_data_id);"
   ));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Table `unique_index_data`
   rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE TABLE unique_index_data"
-      "( index_id INTEGER NOT NULL"
-      ", value BLOB NOT NULL"
-      ", object_store_id INTEGER NOT NULL"
-      ", object_data_key BLOB NOT NULL"
-      ", PRIMARY KEY (index_id, value)"
-      ", FOREIGN KEY (index_id) "
-          "REFERENCES object_store_index(id) "
-      ", FOREIGN KEY (object_store_id, object_data_key) "
-          "REFERENCES object_data(object_store_id, key) "
-      ") WITHOUT ROWID;"
+    "CREATE TABLE unique_index_data ("
+      "index_id INTEGER NOT NULL, "
+      "value BLOB NOT NULL, "
+      "object_data_key BLOB NOT NULL, "
+      "object_data_id INTEGER NOT NULL, "
+      "PRIMARY KEY (index_id, value, object_data_key), "
+      "UNIQUE (index_id, value), "
+      "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
+        "CASCADE "
+      "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
+        "CASCADE"
+    ");"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Need this to make cascading deletes from object_data and object_store fast.
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE INDEX unique_index_data_object_data_id_index "
+    "ON unique_index_data (object_data_id);"
   ));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = CreateFileTables(aConnection);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -2606,935 +2221,16 @@ UpgradeSchemaFrom16_0To17_0(mozIStorageC
   nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(17, 0));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
-class UpgradeSchemaFrom17_0To18_0Helper final
-{
-  class InsertIndexDataValuesFunction;
-  class UpgradeKeyFunction;
-
-public:
-  static nsresult
-  DoUpgrade(mozIStorageConnection* aConnection, const nsACString& aOrigin);
-
-private:
-  static nsresult
-  DoUpgradeInternal(mozIStorageConnection* aConnection,
-                    const nsACString& aOrigin);
-
-  UpgradeSchemaFrom17_0To18_0Helper()
-  {
-    MOZ_ASSERT_UNREACHABLE("Don't create instances of this class!");
-  }
-
-  ~UpgradeSchemaFrom17_0To18_0Helper()
-  {
-    MOZ_ASSERT_UNREACHABLE("Don't create instances of this class!");
-  }
-};
-
-class UpgradeSchemaFrom17_0To18_0Helper::InsertIndexDataValuesFunction final
-  : public mozIStorageFunction
-{
-public:
-  InsertIndexDataValuesFunction()
-  { }
-
-  NS_DECL_ISUPPORTS
-
-private:
-  ~InsertIndexDataValuesFunction()
-  { }
-
-  NS_DECL_MOZISTORAGEFUNCTION
-};
-
-NS_IMPL_ISUPPORTS(UpgradeSchemaFrom17_0To18_0Helper::
-                    InsertIndexDataValuesFunction,
-                  mozIStorageFunction);
-
-NS_IMETHODIMP
-UpgradeSchemaFrom17_0To18_0Helper::
-InsertIndexDataValuesFunction::OnFunctionCall(mozIStorageValueArray* aValues,
-                                              nsIVariant** _retval)
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-  MOZ_ASSERT(!IsOnBackgroundThread());
-  MOZ_ASSERT(aValues);
-  MOZ_ASSERT(_retval);
-
-#ifdef DEBUG
-  {
-    uint32_t argCount;
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetNumEntries(&argCount)));
-    MOZ_ASSERT(argCount == 4);
-
-    int32_t valueType;
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(0, &valueType)));
-    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
-               valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(1, &valueType)));
-    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER);
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(2, &valueType)));
-    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER);
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(3, &valueType)));
-    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
-  }
-#endif
-
-  // Read out the previous value. It may be NULL, in which case we'll just end
-  // up with an empty array.
-  AutoFallibleTArray<IndexDataValue, 32> indexValues;
-  nsresult rv = ReadCompressedIndexDataValues(aValues, 0, indexValues);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  int64_t indexId;
-  rv = aValues->GetInt64(1, &indexId);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  int32_t unique;
-  rv = aValues->GetInt32(2, &unique);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  Key value;
-  rv = value.SetFromValueArray(aValues, 3);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Update the array with the new addition.
-  if (NS_WARN_IF(!indexValues.SetCapacity(indexValues.Length() + 1))) {
-    IDB_REPORT_INTERNAL_ERR();
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  MOZ_ALWAYS_TRUE(
-    indexValues.InsertElementSorted(IndexDataValue(indexId, !!unique, value)));
-
-  // Compress the array.
-  UniqueMozFreePtr<uint8_t> indexValuesBlob;
-  uint32_t indexValuesBlobLength;
-  rv = MakeCompressedIndexDataValues(indexValues,
-                                     indexValuesBlob,
-                                     &indexValuesBlobLength);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // The compressed blob is the result of this function.
-  std::pair<uint8_t *, int> indexValuesBlobPair(indexValuesBlob.release(),
-                                                indexValuesBlobLength);
-
-  nsCOMPtr<nsIVariant> result =
-    new storage::AdoptedBlobVariant(indexValuesBlobPair);
-
-  result.forget(_retval);
-  return NS_OK;
-}
-
-class UpgradeSchemaFrom17_0To18_0Helper::UpgradeKeyFunction final
-  : public mozIStorageFunction
-{
-public:
-  UpgradeKeyFunction()
-  { }
-
-  static nsresult
-  CopyAndUpgradeKeyBuffer(const uint8_t* aSource,
-                          const uint8_t* aSourceEnd,
-                          uint8_t* aDestination)
-  {
-    return CopyAndUpgradeKeyBufferInternal(aSource,
-                                           aSourceEnd,
-                                           aDestination,
-                                           0 /* aTagOffset */,
-                                           0 /* aRecursionDepth */);
-  }
-
-  NS_DECL_ISUPPORTS
-
-private:
-  ~UpgradeKeyFunction()
-  { }
-
-  static nsresult
-  CopyAndUpgradeKeyBufferInternal(const uint8_t*& aSource,
-                                  const uint8_t* aSourceEnd,
-                                  uint8_t*& aDestination,
-                                  uint8_t aTagOffset,
-                                  uint8_t aRecursionDepth);
-
-  static uint32_t
-  AdjustedSize(uint32_t aMaxSize,
-               const uint8_t* aSource,
-               const uint8_t* aSourceEnd)
-  {
-    MOZ_ASSERT(aMaxSize);
-    MOZ_ASSERT(aSource);
-    MOZ_ASSERT(aSourceEnd);
-    MOZ_ASSERT(aSource <= aSourceEnd);
-
-    return std::min(aMaxSize, uint32_t(aSourceEnd - aSource));
-  }
-
-  NS_DECL_MOZISTORAGEFUNCTION
-};
-
-// static
-nsresult
-UpgradeSchemaFrom17_0To18_0Helper::
-UpgradeKeyFunction::CopyAndUpgradeKeyBufferInternal(const uint8_t*& aSource,
-                                                    const uint8_t* aSourceEnd,
-                                                    uint8_t*& aDestination,
-                                                    uint8_t aTagOffset,
-                                                    uint8_t aRecursionDepth)
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-  MOZ_ASSERT(!IsOnBackgroundThread());
-  MOZ_ASSERT(aSource);
-  MOZ_ASSERT(*aSource);
-  MOZ_ASSERT(aSourceEnd);
-  MOZ_ASSERT(aSource < aSourceEnd);
-  MOZ_ASSERT(aDestination);
-  MOZ_ASSERT(aTagOffset <=  Key::kMaxArrayCollapse);
-
-  static MOZ_CONSTEXPR_VAR uint8_t kOldNumberTag = 0x1;
-  static MOZ_CONSTEXPR_VAR uint8_t kOldDateTag = 0x2;
-  static MOZ_CONSTEXPR_VAR uint8_t kOldStringTag = 0x3;
-  static MOZ_CONSTEXPR_VAR uint8_t kOldArrayTag = 0x4;
-  static MOZ_CONSTEXPR_VAR uint8_t kOldMaxType = kOldArrayTag;
-
-  if (NS_WARN_IF(aRecursionDepth > Key::kMaxRecursionDepth)) {
-    IDB_REPORT_INTERNAL_ERR();
-    return NS_ERROR_FILE_CORRUPTED;
-  }
-
-  const uint8_t sourceTag = *aSource - (aTagOffset * kOldMaxType);
-  MOZ_ASSERT(sourceTag);
-
-  if (NS_WARN_IF(sourceTag > kOldMaxType * Key::kMaxArrayCollapse)) {
-    IDB_REPORT_INTERNAL_ERR();
-    return NS_ERROR_FILE_CORRUPTED;
-  }
-
-  if (sourceTag == kOldNumberTag || sourceTag == kOldDateTag) {
-    // Write the new tag.
-    *aDestination++ =
-      (sourceTag == kOldNumberTag ? Key::eFloat : Key::eDate) +
-      (aTagOffset * Key::eMaxType);
-    aSource++;
-
-    // Numbers and Dates are encoded as 64-bit integers, but trailing 0
-    // bytes have been removed.
-    const uint32_t byteCount =
-      AdjustedSize(sizeof(uint64_t), aSource, aSourceEnd);
-
-    for (uint32_t count = 0; count < byteCount; count++) {
-      *aDestination++ = *aSource++;
-    }
-
-    return NS_OK;
-  }
-
-  if (sourceTag == kOldStringTag) {
-    // Write the new tag.
-    *aDestination++ = Key::eString + (aTagOffset * Key::eMaxType);
-    aSource++;
-
-    while (aSource < aSourceEnd) {
-      const uint8_t byte = *aSource++;
-      *aDestination++ = byte;
-
-      if (!byte) {
-        // Just copied the terminator.
-        break;
-      }
-
-      // Maybe copy one or two extra bytes if the byte is tagged and we have
-      // enough source space.
-      if (byte & 0x80) {
-        const uint32_t byteCount =
-          AdjustedSize((byte & 0x40) ? 2 : 1, aSource, aSourceEnd);
-
-        for (uint32_t count = 0; count < byteCount; count++) {
-          *aDestination++ = *aSource++;
-        }
-      }
-    }
-
-    return NS_OK;
-  }
-
-  if (NS_WARN_IF(sourceTag < kOldArrayTag)) {
-    IDB_REPORT_INTERNAL_ERR();
-    return NS_ERROR_FILE_CORRUPTED;
-  }
-
-  aTagOffset++;
-
-  if (aTagOffset == Key::kMaxArrayCollapse) {
-    MOZ_ASSERT(sourceTag == kOldArrayTag);
-
-    *aDestination++ = (aTagOffset * Key::eMaxType);
-    aSource++;
-
-    aTagOffset = 0;
-  }
-
-  while (aSource < aSourceEnd &&
-         (*aSource - (aTagOffset * kOldMaxType)) != Key::eTerminator) {
-    nsresult rv = CopyAndUpgradeKeyBufferInternal(aSource,
-                                                  aSourceEnd,
-                                                  aDestination,
-                                                  aTagOffset,
-                                                  aRecursionDepth + 1);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    aTagOffset = 0;
-  }
-
-  if (aSource < aSourceEnd) {
-    MOZ_ASSERT((*aSource - (aTagOffset * kOldMaxType)) == Key::eTerminator);
-    *aDestination++ = Key::eTerminator + (aTagOffset * Key::eMaxType);
-    aSource++;
-  }
-
-  return NS_OK;
-}
-
-NS_IMPL_ISUPPORTS(UpgradeSchemaFrom17_0To18_0Helper::UpgradeKeyFunction,
-                  mozIStorageFunction);
-
-NS_IMETHODIMP
-UpgradeSchemaFrom17_0To18_0Helper::
-UpgradeKeyFunction::OnFunctionCall(mozIStorageValueArray* aValues,
-                                   nsIVariant** _retval)
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-  MOZ_ASSERT(!IsOnBackgroundThread());
-  MOZ_ASSERT(aValues);
-  MOZ_ASSERT(_retval);
-
-#ifdef DEBUG
-  {
-    uint32_t argCount;
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetNumEntries(&argCount)));
-    MOZ_ASSERT(argCount == 1);
-
-    int32_t valueType;
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(0, &valueType)));
-    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
-  }
-#endif
-
-  // Dig the old key out of the values.
-  const uint8_t* blobData;
-  uint32_t blobDataLength;
-  nsresult rv = aValues->GetSharedBlob(0, &blobDataLength, &blobData);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Upgrading the key doesn't change the amount of space needed to hold it.
-  UniqueMozFreePtr<uint8_t> upgradedBlobData(
-    static_cast<uint8_t*>(moz_malloc(blobDataLength)));
-  if (NS_WARN_IF(!upgradedBlobData)) {
-    IDB_REPORT_INTERNAL_ERR();
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  rv = CopyAndUpgradeKeyBuffer(blobData,
-                               blobData + blobDataLength,
-                               upgradedBlobData.get());
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // The upgraded key is the result of this function.
-  std::pair<uint8_t*, int> data(upgradedBlobData.release(),
-                                int(blobDataLength));
-
-  nsCOMPtr<nsIVariant> result = new mozilla::storage::AdoptedBlobVariant(data);
-
-  upgradedBlobData.release();
-
-  result.forget(_retval);
-  return NS_OK;
-}
-
-// static
-nsresult
-UpgradeSchemaFrom17_0To18_0Helper::DoUpgrade(mozIStorageConnection* aConnection,
-                                             const nsACString& aOrigin)
-{
-  MOZ_ASSERT(aConnection);
-  MOZ_ASSERT(!aOrigin.IsEmpty());
-
-  // Register the |upgrade_key| function.
-  nsRefPtr<UpgradeKeyFunction> updateFunction = new UpgradeKeyFunction();
-
-  NS_NAMED_LITERAL_CSTRING(upgradeKeyFunctionName, "upgrade_key");
-
-  nsresult rv =
-    aConnection->CreateFunction(upgradeKeyFunctionName, 1, updateFunction);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Register the |insert_idv| function.
-  nsRefPtr<InsertIndexDataValuesFunction> insertIDVFunction =
-    new InsertIndexDataValuesFunction();
-
-  NS_NAMED_LITERAL_CSTRING(insertIDVFunctionName, "insert_idv");
-
-  rv = aConnection->CreateFunction(insertIDVFunctionName, 4, insertIDVFunction);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-      aConnection->RemoveFunction(upgradeKeyFunctionName)));
-    return rv;
-  }
-
-  rv = DoUpgradeInternal(aConnection, aOrigin);
-
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-    aConnection->RemoveFunction(upgradeKeyFunctionName)));
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-    aConnection->RemoveFunction(insertIDVFunctionName)));
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
-}
-
-// static
-nsresult
-UpgradeSchemaFrom17_0To18_0Helper::DoUpgradeInternal(
-                                             mozIStorageConnection* aConnection,
-                                             const nsACString& aOrigin)
-{
-  MOZ_ASSERT(aConnection);
-  MOZ_ASSERT(!aOrigin.IsEmpty());
-
-  nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // Drop these triggers to avoid unnecessary work during the upgrade process.
-    "DROP TRIGGER object_data_insert_trigger;"
-    "DROP TRIGGER object_data_update_trigger;"
-    "DROP TRIGGER object_data_delete_trigger;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // Drop these indexes before we do anything else to free disk space.
-    "DROP INDEX index_data_object_data_id_index;"
-    "DROP INDEX unique_index_data_object_data_id_index;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Create the new tables and triggers first.
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // This will eventually become the |database| table.
-    "CREATE TABLE database_upgrade "
-      "( name TEXT PRIMARY KEY"
-      ", origin TEXT NOT NULL"
-      ", version INTEGER NOT NULL DEFAULT 0"
-      ", last_vacuum_time INTEGER NOT NULL DEFAULT 0"
-      ", last_analyze_time INTEGER NOT NULL DEFAULT 0"
-      ", last_vacuum_size INTEGER NOT NULL DEFAULT 0"
-      ") WITHOUT ROWID;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-     // This will eventually become the |object_store| table.
-    "CREATE TABLE object_store_upgrade"
-      "( id INTEGER PRIMARY KEY"
-      ", auto_increment INTEGER NOT NULL DEFAULT 0"
-      ", name TEXT NOT NULL"
-      ", key_path TEXT"
-      ");"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // This will eventually become the |object_store_index| table.
-    "CREATE TABLE object_store_index_upgrade"
-      "( id INTEGER PRIMARY KEY"
-      ", object_store_id INTEGER NOT NULL"
-      ", name TEXT NOT NULL"
-      ", key_path TEXT NOT NULL"
-      ", unique_index INTEGER NOT NULL"
-      ", multientry INTEGER NOT NULL"
-      ", FOREIGN KEY (object_store_id) "
-          "REFERENCES object_store(id) "
-      ");"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // This will eventually become the |object_data| table.
-    "CREATE TABLE object_data_upgrade"
-      "( object_store_id INTEGER NOT NULL"
-      ", key BLOB NOT NULL"
-      ", index_data_values BLOB DEFAULT NULL"
-      ", file_ids TEXT"
-      ", data BLOB NOT NULL"
-      ", PRIMARY KEY (object_store_id, key)"
-      ", FOREIGN KEY (object_store_id) "
-          "REFERENCES object_store(id) "
-      ") WITHOUT ROWID;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // This will eventually become the |index_data| table.
-    "CREATE TABLE index_data_upgrade"
-      "( index_id INTEGER NOT NULL"
-      ", value BLOB NOT NULL"
-      ", object_data_key BLOB NOT NULL"
-      ", object_store_id INTEGER NOT NULL"
-      ", PRIMARY KEY (index_id, value, object_data_key)"
-      ", FOREIGN KEY (index_id) "
-          "REFERENCES object_store_index(id) "
-      ", FOREIGN KEY (object_store_id, object_data_key) "
-          "REFERENCES object_data(object_store_id, key) "
-      ") WITHOUT ROWID;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // This will eventually become the |unique_index_data| table.
-    "CREATE TABLE unique_index_data_upgrade"
-      "( index_id INTEGER NOT NULL"
-      ", value BLOB NOT NULL"
-      ", object_store_id INTEGER NOT NULL"
-      ", object_data_key BLOB NOT NULL"
-      ", PRIMARY KEY (index_id, value)"
-      ", FOREIGN KEY (index_id) "
-          "REFERENCES object_store_index(id) "
-      ", FOREIGN KEY (object_store_id, object_data_key) "
-          "REFERENCES object_data(object_store_id, key) "
-      ") WITHOUT ROWID;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // Temporarily store |index_data_values| that we build during the upgrade of
-    // the index tables. We will later move this to the |object_data| table.
-    "CREATE TEMPORARY TABLE temp_index_data_values "
-      "( object_store_id INTEGER NOT NULL"
-      ", key BLOB NOT NULL"
-      ", index_data_values BLOB DEFAULT NULL"
-      ", PRIMARY KEY (object_store_id, key)"
-      ") WITHOUT ROWID;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // These two triggers help build the |index_data_values| blobs. The nested
-    // SELECT statements help us achieve an "INSERT OR UPDATE"-like behavior.
-    "CREATE TEMPORARY TRIGGER unique_index_data_upgrade_insert_trigger "
-      "AFTER INSERT ON unique_index_data_upgrade "
-      "BEGIN "
-        "INSERT OR REPLACE INTO temp_index_data_values "
-          "VALUES "
-          "( NEW.object_store_id"
-          ", NEW.object_data_key"
-          ", insert_idv("
-              "( SELECT index_data_values "
-                  "FROM temp_index_data_values "
-                  "WHERE object_store_id = NEW.object_store_id "
-                  "AND key = NEW.object_data_key "
-              "), NEW.index_id"
-               ", 1" /* unique */
-               ", NEW.value"
-            ")"
-          ");"
-      "END;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE TEMPORARY TRIGGER index_data_upgrade_insert_trigger "
-      "AFTER INSERT ON index_data_upgrade "
-      "BEGIN "
-        "INSERT OR REPLACE INTO temp_index_data_values "
-          "VALUES "
-          "( NEW.object_store_id"
-          ", NEW.object_data_key"
-          ", insert_idv("
-              "("
-                "SELECT index_data_values "
-                  "FROM temp_index_data_values "
-                  "WHERE object_store_id = NEW.object_store_id "
-                  "AND key = NEW.object_data_key "
-              "), NEW.index_id"
-               ", 0" /* not unique */
-               ", NEW.value"
-            ")"
-          ");"
-      "END;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Update the |unique_index_data| table to change the column order, remove the
-  // ON DELETE CASCADE clauses, and to apply the WITHOUT ROWID optimization.
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // Insert all the data.
-    "INSERT INTO unique_index_data_upgrade "
-      "SELECT "
-        "unique_index_data.index_id, "
-        "upgrade_key(unique_index_data.value), "
-        "object_data.object_store_id, "
-        "upgrade_key(unique_index_data.object_data_key) "
-        "FROM unique_index_data "
-        "JOIN object_data "
-        "ON unique_index_data.object_data_id = object_data.id;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // The trigger is no longer needed.
-    "DROP TRIGGER unique_index_data_upgrade_insert_trigger;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // The old table is no longer needed.
-    "DROP TABLE unique_index_data;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // Rename the table.
-    "ALTER TABLE unique_index_data_upgrade "
-      "RENAME TO unique_index_data;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Update the |index_data| table to change the column order, remove the ON
-  // DELETE CASCADE clauses, and to apply the WITHOUT ROWID optimization.
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // Insert all the data.
-    "INSERT INTO index_data_upgrade "
-      "SELECT "
-        "index_data.index_id, "
-        "upgrade_key(index_data.value), "
-        "upgrade_key(index_data.object_data_key), "
-        "object_data.object_store_id "
-        "FROM index_data "
-        "JOIN object_data "
-        "ON index_data.object_data_id = object_data.id;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // The trigger is no longer needed.
-    "DROP TRIGGER index_data_upgrade_insert_trigger;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // The old table is no longer needed.
-    "DROP TABLE index_data;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // Rename the table.
-    "ALTER TABLE index_data_upgrade "
-      "RENAME TO index_data;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Update the |object_data| table to add the |index_data_values| column,
-  // remove the ON DELETE CASCADE clause, and apply the WITHOUT ROWID
-  // optimization.
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // Insert all the data.
-    "INSERT INTO object_data_upgrade "
-      "SELECT "
-        "object_data.object_store_id, "
-        "upgrade_key(object_data.key_value), "
-        "temp_index_data_values.index_data_values, "
-        "object_data.file_ids, "
-        "object_data.data "
-        "FROM object_data "
-        "LEFT JOIN temp_index_data_values "
-        "ON object_data.object_store_id = "
-          "temp_index_data_values.object_store_id "
-        "AND upgrade_key(object_data.key_value) = "
-          "temp_index_data_values.key;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // The temporary table is no longer needed.
-    "DROP TABLE temp_index_data_values;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // The old table is no longer needed.
-    "DROP TABLE object_data;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    // Rename the table.
-    "ALTER TABLE object_data_upgrade "
-      "RENAME TO object_data;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Update the |object_store_index| table to remove the UNIQUE constraint and
-  // the ON DELETE CASCADE clause.
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "INSERT INTO object_store_index_upgrade "
-      "SELECT * "
-        "FROM object_store_index;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "DROP TABLE object_store_index;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "ALTER TABLE object_store_index_upgrade "
-      "RENAME TO object_store_index;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Update the |object_store| table to remove the UNIQUE constraint.
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "INSERT INTO object_store_upgrade "
-      "SELECT * "
-        "FROM object_store;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "DROP TABLE object_store;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "ALTER TABLE object_store_upgrade "
-      "RENAME TO object_store;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Update the |database| table to include the origin, vacuum information, and
-  // apply the WITHOUT ROWID optimization.
-  nsCOMPtr<mozIStorageStatement> stmt;
-  rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
-    "INSERT INTO database_upgrade "
-      "SELECT name, :origin, version, 0, 0, 0 "
-        "FROM database;"
-  ), getter_AddRefs(stmt));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), aOrigin);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = stmt->Execute();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "DROP TABLE database;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "ALTER TABLE database_upgrade "
-      "RENAME TO database;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-#ifdef DEBUG
-  {
-    // Make sure there's only one entry in the |database| table.
-    nsCOMPtr<mozIStorageStatement> stmt;
-    MOZ_ASSERT(NS_SUCCEEDED(
-      aConnection->CreateStatement(
-        NS_LITERAL_CSTRING("SELECT COUNT(*) "
-                             "FROM database;"),
-        getter_AddRefs(stmt))));
-
-    bool hasResult;
-    MOZ_ASSERT(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)));
-
-    int64_t count;
-    MOZ_ASSERT(NS_SUCCEEDED(stmt->GetInt64(0, &count)));
-
-    MOZ_ASSERT(count == 1);
-  }
-#endif
-
-  // Recreate file table triggers.
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE TRIGGER object_data_insert_trigger "
-      "AFTER INSERT ON object_data "
-      "WHEN NEW.file_ids IS NOT NULL "
-      "BEGIN "
-        "SELECT update_refcount(NULL, NEW.file_ids);"
-      "END;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE TRIGGER object_data_update_trigger "
-      "AFTER UPDATE OF file_ids ON object_data "
-      "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
-      "BEGIN "
-        "SELECT update_refcount(OLD.file_ids, NEW.file_ids);"
-      "END;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE TRIGGER object_data_delete_trigger "
-      "AFTER DELETE ON object_data "
-      "WHEN OLD.file_ids IS NOT NULL "
-      "BEGIN "
-        "SELECT update_refcount(OLD.file_ids, NULL);"
-      "END;"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Finally, turn on auto_vacuum mode. We use full auto_vacuum mode to reclaim
-  // disk space on mobile devices (at the cost of some COMMIT speed), and
-  // incremental auto_vacuum mode on desktop builds.
-  rv = aConnection->ExecuteSimpleSQL(
-#ifdef IDB_MOBILE
-    NS_LITERAL_CSTRING("PRAGMA auto_vacuum = FULL;")
-#else
-    NS_LITERAL_CSTRING("PRAGMA auto_vacuum = INCREMENTAL;")
-#endif
-  );
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aConnection->SetSchemaVersion(MakeSchemaVersion(18, 0));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
-}
-
-nsresult
-UpgradeSchemaFrom17_0To18_0(mozIStorageConnection* aConnection,
-                            const nsACString& aOrigin)
-{
-  MOZ_ASSERT(aConnection);
-  MOZ_ASSERT(!aOrigin.IsEmpty());
-
-  PROFILER_LABEL("IndexedDB",
-                 "UpgradeSchemaFrom17_0To18_0",
-                 js::ProfileEntry::Category::STORAGE);
-
-  return UpgradeSchemaFrom17_0To18_0Helper::DoUpgrade(aConnection, aOrigin);
-}
-
 nsresult
 GetDatabaseFileURL(nsIFile* aDatabaseFile,
                    PersistenceType aPersistenceType,
                    const nsACString& aGroup,
                    const nsACString& aOrigin,
                    nsIFileURL** aResult)
 {
   MOZ_ASSERT(aDatabaseFile);
@@ -3566,25 +2262,18 @@ GetDatabaseFileURL(nsIFile* aDatabaseFil
 
 nsresult
 SetDefaultPragmas(mozIStorageConnection* aConnection)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aConnection);
 
   static const char kBuiltInPragmas[] =
-    // We use foreign keys in DEBUG builds only because there is a performance
-    // cost to using them.
-   "PRAGMA foreign_keys = "
-#ifdef DEBUG
-     "ON"
-#else
-     "OFF"
-#endif
-     ";"
+    // We use foreign keys in lots of places.
+    "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;"
 
@@ -3913,105 +2602,79 @@ CreateStorageConnection(nsIFile* aDBFile
   if (schemaVersion > kSQLiteSchemaVersion) {
     IDB_WARNING("Unable to open IndexedDB database, schema is too high!");
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   bool journalModeSet = false;
 
   if (schemaVersion != kSQLiteSchemaVersion) {
-    const bool newDatabase = !schemaVersion;
-
-    if (newDatabase) {
-      // Set the page size first.
-      if (kSQLitePageSizeOverride) {
-        rv = connection->ExecuteSimpleSQL(
-          nsPrintfCString("PRAGMA page_size = %lu;", kSQLitePageSizeOverride)
-        );
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          return rv;
-        }
-      }
-
-      // We have to set the auto_vacuum mode before opening a transaction.
-      rv = connection->ExecuteSimpleSQL(
+    if (!schemaVersion) {
+      // Brand new file.
+
 #ifdef IDB_MOBILE
-        // Turn on full auto_vacuum mode to reclaim disk space on mobile
-        // devices (at the cost of some COMMIT speed).
-        NS_LITERAL_CSTRING("PRAGMA auto_vacuum = FULL;")
-#else
-        // Turn on incremental auto_vacuum mode on desktop builds.
-        NS_LITERAL_CSTRING("PRAGMA auto_vacuum = INCREMENTAL;")
-#endif
-      );
+      // 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;
-    } else {
-#ifdef DEBUG
-    // Disable foreign key support while upgrading. This has to be done before
-    // starting a transaction.
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-      connection->ExecuteSimpleSQL(
-        NS_LITERAL_CSTRING("PRAGMA foreign_keys = OFF;"))));
-#endif
     }
 
     bool vacuumNeeded = false;
 
     mozStorageTransaction transaction(connection, false,
                                   mozIStorageConnection::TRANSACTION_IMMEDIATE);
 
-    if (newDatabase) {
+    if (!schemaVersion) {
+      // Brand new file, initialize our tables.
       rv = CreateTables(connection);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       MOZ_ASSERT(NS_SUCCEEDED(connection->GetSchemaVersion(&schemaVersion)));
       MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion);
 
       nsCOMPtr<mozIStorageStatement> stmt;
       nsresult rv = connection->CreateStatement(NS_LITERAL_CSTRING(
-        "INSERT INTO database (name, origin) "
-        "VALUES (:name, :origin)"
+        "INSERT INTO database (name) "
+        "VALUES (:name)"
       ), getter_AddRefs(stmt));
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), aName);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
-      rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), aOrigin);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
       rv = stmt->Execute();
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     } else  {
       // This logic needs to change next time we change the schema!
-      static_assert(kSQLiteSchemaVersion == int32_t((18 << 4) + 0),
+      static_assert(kSQLiteSchemaVersion == int32_t((17 << 4) + 0),
                     "Upgrade function needed due to schema version increase.");
 
       while (schemaVersion != kSQLiteSchemaVersion) {
         if (schemaVersion == 4) {
           rv = UpgradeSchemaFrom4To5(connection);
         } else if (schemaVersion == 5) {
           rv = UpgradeSchemaFrom5To6(connection);
         } else if (schemaVersion == 6) {
@@ -4032,19 +2695,16 @@ CreateStorageConnection(nsIFile* aDBFile
         } else if (schemaVersion == MakeSchemaVersion(13, 0)) {
           rv = UpgradeSchemaFrom13_0To14_0(connection);
         } else if (schemaVersion == MakeSchemaVersion(14, 0)) {
           rv = UpgradeSchemaFrom14_0To15_0(connection);
         } else if (schemaVersion == MakeSchemaVersion(15, 0)) {
           rv = UpgradeSchemaFrom15_0To16_0(connection);
         } else if (schemaVersion == MakeSchemaVersion(16, 0)) {
           rv = UpgradeSchemaFrom16_0To17_0(connection);
-        } else if (schemaVersion == MakeSchemaVersion(17, 0)) {
-          rv = UpgradeSchemaFrom17_0To18_0(connection, aOrigin);
-          vacuumNeeded = true;
         } else {
           IDB_WARNING("Unable to open IndexedDB database, no upgrade path is "
                       "available!");
           return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
         }
 
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return rv;
@@ -4064,160 +2724,18 @@ CreateStorageConnection(nsIFile* aDBFile
       // 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;
     }
 
-#ifdef DEBUG
-    if (!newDatabase) {
-      // Re-enable foreign key support after doing a foreign key check.
-      nsCOMPtr<mozIStorageStatement> checkStmt;
-      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-        connection->CreateStatement(
-          NS_LITERAL_CSTRING("PRAGMA foreign_key_check;"),
-          getter_AddRefs(checkStmt))));
-
-      bool hasResult;
-      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(checkStmt->ExecuteStep(&hasResult)));
-      MOZ_ASSERT(!hasResult, "Database has inconsisistent foreign keys!");
-
-      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-        connection->ExecuteSimpleSQL(
-          NS_LITERAL_CSTRING("PRAGMA foreign_keys = OFF;"))));
-    }
-#endif
-
-    if (kSQLitePageSizeOverride && !newDatabase) {
-      nsCOMPtr<mozIStorageStatement> stmt;
-      rv = connection->CreateStatement(NS_LITERAL_CSTRING(
-        "PRAGMA page_size;"
-      ), 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);
-
-      int32_t pageSize;
-      rv = stmt->GetInt32(0, &pageSize);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      MOZ_ASSERT(pageSize >= 512 && pageSize <= 65536);
-
-      if (kSQLitePageSizeOverride != uint32_t(pageSize)) {
-        // We must not be in WAL journal mode to change the page size.
-        rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-          "PRAGMA journal_mode = DELETE;"
-        ));
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          return rv;
-        }
-
-        rv = connection->CreateStatement(NS_LITERAL_CSTRING(
-          "PRAGMA journal_mode;"
-        ), getter_AddRefs(stmt));
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          return rv;
-        }
-
-        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.EqualsLiteral("delete")) {
-          // Successfully set to rollback journal mode so changing the page size
-          // is possible with a VACUUM.
-          rv = connection->ExecuteSimpleSQL(
-            nsPrintfCString("PRAGMA page_size = %lu;", kSQLitePageSizeOverride)
-          );
-          if (NS_WARN_IF(NS_FAILED(rv))) {
-            return rv;
-          }
-
-          // We will need to VACUUM in order to change the page size.
-          vacuumNeeded = true;
-        } else {
-          NS_WARNING("Failed to set journal_mode for database, unable to "
-                     "change the page size!");
-        }
-      }
-    }
-
     if (vacuumNeeded) {
-      rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM;"));
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-    }
-
-    if (newDatabase || vacuumNeeded) {
-      if (journalModeSet) {
-        // Make sure we checkpoint to get an accurate file size.
-        rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-          "PRAGMA wal_checkpoint(FULL);"
-        ));
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          return rv;
-        }
-      }
-
-      int64_t fileSize;
-      rv = aDBFile->GetFileSize(&fileSize);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      MOZ_ASSERT(fileSize > 0);
-
-      PRTime vacuumTime = PR_Now();
-      MOZ_ASSERT(vacuumTime);
-
-      nsCOMPtr<mozIStorageStatement> vacuumTimeStmt;
-      rv = connection->CreateStatement(NS_LITERAL_CSTRING(
-        "UPDATE database "
-          "SET last_vacuum_time = :time"
-            ", last_vacuum_size = :size;"
-      ), getter_AddRefs(vacuumTimeStmt));
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      rv = vacuumTimeStmt->BindInt64ByName(NS_LITERAL_CSTRING("time"),
-                                           vacuumTime);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      rv = vacuumTimeStmt->BindInt64ByName(NS_LITERAL_CSTRING("size"),
-                                           fileSize);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      rv = vacuumTimeStmt->Execute();
+      rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM"));
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
   }
 
   if (!journalModeSet) {
     rv = SetJournalMode(connection);
@@ -5188,106 +3706,38 @@ protected:
   static uint64_t
   ReinterpretDoubleAsUInt64(double aDouble);
 
   static nsresult
   GetStructuredCloneReadInfoFromStatement(mozIStorageStatement* aStatement,
                                           uint32_t aDataIndex,
                                           uint32_t aFileIdsIndex,
                                           FileManager* aFileManager,
-                                          StructuredCloneReadInfo* aInfo)
-  {
-    return GetStructuredCloneReadInfoFromSource(aStatement,
-                                                aDataIndex,
-                                                aFileIdsIndex,
-                                                aFileManager,
-                                                aInfo);
-  }
-
-  static nsresult
-  GetStructuredCloneReadInfoFromValueArray(mozIStorageValueArray* aValues,
-                                           uint32_t aDataIndex,
-                                           uint32_t aFileIdsIndex,
-                                           FileManager* aFileManager,
-                                           StructuredCloneReadInfo* aInfo)
-  {
-    return GetStructuredCloneReadInfoFromSource(aValues,
-                                                aDataIndex,
-                                                aFileIdsIndex,
-                                                aFileManager,
-                                                aInfo);
-  }
+                                          StructuredCloneReadInfo* aInfo);
 
   static nsresult
   BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange,
                           mozIStorageStatement* aStatement);
 
   static void
   AppendConditionClause(const nsACString& aColumnName,
                         const nsACString& aArgName,
                         bool aLessThan,
                         bool aEquals,
                         nsAutoCString& aResult);
 
   static nsresult
-  GetUniqueIndexTableForObjectStore(
-                               TransactionBase* aTransaction,
-                               int64_t aObjectStoreId,
-                               Maybe<UniqueIndexTable>& aMaybeUniqueIndexTable);
-
-  static nsresult
-  IndexDataValuesFromUpdateInfos(
-                              const nsTArray<IndexUpdateInfo>& aUpdateInfos,
-                              const UniqueIndexTable& aUniqueIndexTable,
-                              FallibleTArray<IndexDataValue>& aIndexValues);
-
-  static nsresult
-  InsertIndexTableRows(DatabaseConnection* aConnection,
-                       const int64_t aObjectStoreId,
-                       const Key& aObjectStoreKey,
-                       const FallibleTArray<IndexDataValue>& aIndexValues);
-
-  static nsresult
-  DeleteIndexDataTableRows(DatabaseConnection* aConnection,
-                           const Key& aObjectStoreKey,
-                           const FallibleTArray<IndexDataValue>& aIndexValues);
-
-  static nsresult
-  DeleteObjectStoreDataTableRowsWithIndexes(DatabaseConnection* aConnection,
-                                            const int64_t aObjectStoreId,
-                                            const OptionalKeyRange& aKeyRange);
-
-  static nsresult
-  UpdateIndexValues(DatabaseConnection* aConnection,
-                    const int64_t aObjectStoreId,
-                    const Key& aObjectStoreKey,
-                    const FallibleTArray<IndexDataValue>& aIndexValues);
-
-#ifdef DEBUG
-  static bool
-  ObjectStoreHasIndexes(DatabaseConnection* aConnection,
-                        const int64_t aObjectStoreId);
-#endif
-
-private:
-  template <typename T>
-  static nsresult
-  GetStructuredCloneReadInfoFromSource(T* aSource,
-                                       uint32_t aDataIndex,
-                                       uint32_t aFileIdsIndex,
-                                       FileManager* aFileManager,
-                                       StructuredCloneReadInfo* aInfo);
-
-  static nsresult
-  GetStructuredCloneReadInfoFromBlob(const uint8_t* aBlobData,
-                                     uint32_t aBlobDataLength,
-                                     const nsAString& aFileIds,
-                                     FileManager* aFileManager,
-                                     StructuredCloneReadInfo* aInfo);
-
+  UpdateIndexes(DatabaseConnection* aConnection,
+                const UniqueIndexTable& aUniqueIndexTable,
+                const Key& aObjectStoreKey,
+                bool aOverwrite,
+                int64_t aObjectDataId,
+                const nsTArray<IndexUpdateInfo>& aUpdateInfoArray);
+
+private:
   // Not to be overridden by subclasses.
   NS_DECL_MOZISTORAGEPROGRESSHANDLER
 };
 
 class MOZ_STACK_CLASS DatabaseOperationBase::AutoSetProgressHandler final
 {
   mozIStorageConnection* mConnection;
   DebugOnly<DatabaseOperationBase*> mDEBUGDatabaseOp;
@@ -6098,24 +4548,16 @@ private:
   // Writes new autoIncrement counts to database.
   nsresult
   WriteAutoIncrementCounts();
 
   // Updates counts after a database activity has finished.
   void
   CommitOrRollbackAutoIncrementCounts();
 
-  void
-  AssertForeignKeyConsistency(DatabaseConnection* aConnection)
-#ifdef DEBUG
-  ;
-#else
-  { }
-#endif
-
   NS_DECL_NSIRUNNABLE
 
   virtual void
   TransactionFinishedBeforeUnblock() override;
 
   virtual void
   TransactionFinishedAfterUnblock() override;
 
@@ -6763,28 +5205,23 @@ private:
 };
 
 class DeleteObjectStoreOp final
   : public VersionChangeTransactionOp
 {
   friend class VersionChangeTransaction;
 
   const nsRefPtr<FullObjectStoreMetadata> mMetadata;
-  const bool mIsLastObjectStore;
-  const bool mObjectStoreHasIndexes;
 
 private:
   // Only created by VersionChangeTransaction.
   DeleteObjectStoreOp(VersionChangeTransaction* aTransaction,
-                      FullObjectStoreMetadata* const aMetadata,
-                      const bool aIsLastObjectStore)
+                      FullObjectStoreMetadata* const aMetadata)
     : VersionChangeTransactionOp(aTransaction)
     , mMetadata(aMetadata)
-    , mIsLastObjectStore(aIsLastObjectStore)
-    , mObjectStoreHasIndexes(aMetadata->HasLiveIndexes())
   {
     MOZ_ASSERT(aMetadata->mCommonMetadata.id());
   }
 
   ~DeleteObjectStoreOp()
   { }
 
   virtual nsresult
@@ -6792,17 +5229,17 @@ private:
 };
 
 class CreateIndexOp final
   : public VersionChangeTransactionOp
 {
   friend class VersionChangeTransaction;
 
   class ThreadLocalJSRuntime;
-  class UpdateIndexDataValuesFunction;
+  friend class ThreadLocalJSRuntime;
 
   static const unsigned int kBadThreadLocalIndex =
     static_cast<unsigned int>(-1);
 
   static unsigned int sThreadLocalIndex;
 
   const IndexMetadata mMetadata;
   Maybe<UniqueIndexTable> mMaybeUniqueIndexTable;
@@ -6814,22 +5251,22 @@ private:
   // Only created by VersionChangeTransaction.
   CreateIndexOp(VersionChangeTransaction* aTransaction,
                 const int64_t aObjectStoreId,
                 const IndexMetadata& aMetadata);
 
   ~CreateIndexOp()
   { }
 
+  static void
+  InitThreadLocals();
+
   nsresult
   InsertDataFromObjectStore(DatabaseConnection* aConnection);
 
-  nsresult
-  InsertDataFromObjectStoreInternal(DatabaseConnection* aConnection);
-
   virtual bool
   Init(TransactionBase* aTransaction) override;
 
   virtual nsresult
   DoDatabaseWork(DatabaseConnection* aConnection) override;
 };
 
 class CreateIndexOp::ThreadLocalJSRuntime final
@@ -6881,72 +5318,36 @@ private:
       JS_DestroyRuntime(mRuntime);
     }
   }
 
   bool
   Init();
 };
 
-class CreateIndexOp::UpdateIndexDataValuesFunction final
-  : public mozIStorageFunction
-{
-  nsRefPtr<CreateIndexOp> mOp;
-  nsRefPtr<DatabaseConnection> mConnection;
-  JSContext* mCx;
-
-public:
-  UpdateIndexDataValuesFunction(CreateIndexOp* aOp,
-                                DatabaseConnection* aConnection,
-                                JSContext* aCx)
-    : mOp(aOp)
-    , mConnection(aConnection)
-    , mCx(aCx)
-  {
-    MOZ_ASSERT(aOp);
-    MOZ_ASSERT(aConnection);
-    aConnection->AssertIsOnConnectionThread();
-    MOZ_ASSERT(aCx);
-  }
-
-  NS_DECL_ISUPPORTS
-
-private:
-  ~UpdateIndexDataValuesFunction()
-  { }
-
-  NS_DECL_MOZISTORAGEFUNCTION
-};
-
 class DeleteIndexOp final
   : public VersionChangeTransactionOp
 {
   friend class VersionChangeTransaction;
 
-  const int64_t mObjectStoreId;
   const int64_t mIndexId;
-  const bool mUnique;
-  const bool mIsLastIndex;
 
 private:
   // Only created by VersionChangeTransaction.
   DeleteIndexOp(VersionChangeTransaction* aTransaction,
-                const int64_t aObjectStoreId,
-                const int64_t aIndexId,
-                const bool aUnique,
-                const bool aIsLastIndex);
+                const int64_t aIndexId)
+    : VersionChangeTransactionOp(aTransaction)
+    , mIndexId(aIndexId)
+  {
+    MOZ_ASSERT(aIndexId);
+  }
 
   ~DeleteIndexOp()
   { }
 
-  nsresult
-  RemoveReferencesToIndex(DatabaseConnection* aConnection,
-                          const Key& aObjectDataKey,
-                          FallibleTArray<IndexDataValue>& aIndexValues);
-
   virtual nsresult
   DoDatabaseWork(DatabaseConnection* aConnection) override;
 };
 
 class NormalTransactionOp
   : public TransactionDatabaseOperationBase
   , public PBackgroundIDBRequestParent
 {
@@ -7002,30 +5403,26 @@ class ObjectStoreAddOrPutRequestOp final
 
   nsRefPtr<FileManager> mFileManager;
 
   Key mResponse;
   const nsCString mGroup;
   const nsCString mOrigin;
   const PersistenceType mPersistenceType;
   const bool mOverwrite;
-  const bool mObjectStoreHasIndexes;
 
 private:
   // Only created by TransactionBase.
   ObjectStoreAddOrPutRequestOp(TransactionBase* aTransaction,
                                const RequestParams& aParams);
 
   ~ObjectStoreAddOrPutRequestOp()
   { }
 
   nsresult
-  RemoveOldIndexDataValues(DatabaseConnection* aConnection);
-
-  nsresult
   CopyFileData(nsIInputStream* aInputStream, nsIOutputStream* aOutputStream);
 
   virtual bool
   Init(TransactionBase* aTransaction) override;
 
   virtual nsresult
   DoDatabaseWork(DatabaseConnection* aConnection) override;
 
@@ -7120,21 +5517,23 @@ private:
 
 class ObjectStoreDeleteRequestOp final
   : public NormalTransactionOp
 {
   friend class TransactionBase;
 
   const ObjectStoreDeleteParams mParams;
   ObjectStoreDeleteResponse mResponse;
-  const bool mObjectStoreHasIndexes;
 
 private:
   ObjectStoreDeleteRequestOp(TransactionBase* aTransaction,
-                             const ObjectStoreDeleteParams& aParams);
+                             const ObjectStoreDeleteParams& aParams)
+    : NormalTransactionOp(aTransaction)
+    , mParams(aParams)
+  { }
 
   ~ObjectStoreDeleteRequestOp()
   { }
 
   virtual nsresult
   DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   virtual void
@@ -7146,21 +5545,23 @@ private:
 
 class ObjectStoreClearRequestOp final
   : public NormalTransactionOp
 {
   friend class TransactionBase;
 
   const ObjectStoreClearParams mParams;
   ObjectStoreClearResponse mResponse;
-  const bool mObjectStoreHasIndexes;
 
 private:
   ObjectStoreClearRequestOp(TransactionBase* aTransaction,
-                            const ObjectStoreClearParams& aParams);
+                            const ObjectStoreClearParams& aParams)
+    : NormalTransactionOp(aTransaction)
+    , mParams(aParams)
+  { }
 
   ~ObjectStoreClearRequestOp()
   { }
 
   virtual nsresult
   DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   virtual void
@@ -10807,55 +9208,16 @@ TransactionInfoPair::~TransactionInfoPai
 
   MOZ_COUNT_DTOR(ConnectionPool::TransactionInfoPair);
 }
 
 /*******************************************************************************
  * Metadata classes
  ******************************************************************************/
 
-bool
-FullObjectStoreMetadata::HasLiveIndexes() const
-{
-  AssertIsOnBackgroundThread();
-
-  class MOZ_STACK_CLASS Helper final
-  {
-  public:
-    static bool
-    HasLiveIndexes(const FullObjectStoreMetadata* aMetadata)
-    {
-      AssertIsOnBackgroundThread();
-      MOZ_ASSERT(aMetadata);
-
-      bool hasLiveIndexes = false;
-      aMetadata->mIndexes.EnumerateRead(&Enumerate, &hasLiveIndexes);
-
-      return hasLiveIndexes;
-    }
-
-  private:
-    static PLDHashOperator
-    Enumerate(const uint64_t& aKey, FullIndexMetadata* aValue, void* aClosure)
-    {
-      auto* result = static_cast<bool*>(aClosure);
-      MOZ_ASSERT(result);
-
-      if (!aValue->mDeleted) {
-        *result = true;
-        return PL_DHASH_STOP;
-      }
-
-      return PL_DHASH_NEXT;
-    }
-  };
-
-  return Helper::HasLiveIndexes(this);
-}
-
 already_AddRefed<FullDatabaseMetadata>
 FullDatabaseMetadata::Duplicate() const
 {
   AssertIsOnBackgroundThread();
 
   class MOZ_STACK_CLASS IndexClosure final
   {
     FullObjectStoreMetadata& mNew;
@@ -12489,18 +10851,16 @@ TransactionBase::VerifyRequestParams(con
       ASSERT_UNLESS_FUZZING();
       return false;
     }
 
     if (NS_WARN_IF(updates[index].value().IsUnset())) {
       ASSERT_UNLESS_FUZZING();
       return false;
     }
-
-    MOZ_ASSERT(!updates[index].value().GetBuffer().IsEmpty());
   }
 
   const nsTArray<DatabaseFileOrMutableFileId>& files = aParams.files();
 
   for (uint32_t index = 0; index < files.Length(); index++) {
     const DatabaseFileOrMutableFileId& fileOrFileId = files[index];
 
     MOZ_ASSERT(fileOrFileId.type() != DatabaseFileOrMutableFileId::T__None);
@@ -13243,66 +11603,16 @@ VersionChangeTransaction::RecvCreateObje
   return true;
 }
 
 bool
 VersionChangeTransaction::RecvDeleteObjectStore(const int64_t& aObjectStoreId)
 {
   AssertIsOnBackgroundThread();
 
-  class MOZ_STACK_CLASS Helper final
-  {
-    const int64_t mObjectStoreId;
-    bool mIsLastObjectStore;
-    DebugOnly<bool> mFoundTargetId;
-
-  public:
-    static bool
-    IsLastObjectStore(const FullDatabaseMetadata* aDatabaseMetadata,
-                      const int64_t aObjectStoreId)
-    {
-      AssertIsOnBackgroundThread();
-      MOZ_ASSERT(aDatabaseMetadata);
-      MOZ_ASSERT(aObjectStoreId);
-
-      Helper helper(aObjectStoreId);
-      aDatabaseMetadata->mObjectStores.EnumerateRead(&Enumerate, &helper);
-
-      MOZ_ASSERT_IF(helper.mIsLastObjectStore, helper.mFoundTargetId);
-
-      return helper.mIsLastObjectStore;
-    }
-
-  private:
-    explicit
-    Helper(const int64_t aObjectStoreId)
-      : mObjectStoreId(aObjectStoreId)
-      , mIsLastObjectStore(true)
-      , mFoundTargetId(false)
-    { }
-
-    static PLDHashOperator
-    Enumerate(const uint64_t& aKey,
-              FullObjectStoreMetadata* aValue,
-              void* aClosure)
-    {
-      auto* helper = static_cast<Helper*>(aClosure);
-      MOZ_ASSERT(helper);
-
-      if (uint64_t(helper->mObjectStoreId) == aKey) {
-        helper->mFoundTargetId = true;
-      } else if(!aValue->mDeleted) {
-        helper->mIsLastObjectStore = false;
-        return PL_DHASH_STOP;
-      }
-
-      return PL_DHASH_NEXT;
-    }
-  };
-
   if (NS_WARN_IF(!aObjectStoreId)) {
     ASSERT_UNLESS_FUZZING();
     return false;
   }
 
   const nsRefPtr<FullDatabaseMetadata> dbMetadata = GetDatabase()->Metadata();
   MOZ_ASSERT(dbMetadata);
   MOZ_ASSERT(dbMetadata->mNextObjectStoreId > 0);
@@ -13323,20 +11633,17 @@ VersionChangeTransaction::RecvDeleteObje
   if (NS_WARN_IF(mCommitOrAbortReceived)) {
     ASSERT_UNLESS_FUZZING();
     return false;
   }
 
   foundMetadata->mDeleted = true;
 
   nsRefPtr<DeleteObjectStoreOp> op =
-    new DeleteObjectStoreOp(this,
-                            foundMetadata,
-                            Helper::IsLastObjectStore(dbMetadata,
-                                                      aObjectStoreId));
+    new DeleteObjectStoreOp(this, foundMetadata);
 
   if (NS_WARN_IF(!op->Init(this))) {
     op->Cleanup();
     return false;
   }
 
   op->DispatchToConnectionPool();
 
@@ -13414,64 +11721,16 @@ VersionChangeTransaction::RecvCreateInde
 }
 
 bool
 VersionChangeTransaction::RecvDeleteIndex(const int64_t& aObjectStoreId,
                                           const int64_t& aIndexId)
 {
   AssertIsOnBackgroundThread();
 
-  class MOZ_STACK_CLASS Helper final
-  {
-    const int64_t mIndexId;
-    bool mIsLastIndex;
-    DebugOnly<bool> mFoundTargetId;
-
-  public:
-    static bool
-    IsLastIndex(const FullObjectStoreMetadata* aObjectStoreMetadata,
-                const int64_t aIndexId)
-    {
-      AssertIsOnBackgroundThread();
-      MOZ_ASSERT(aObjectStoreMetadata);
-      MOZ_ASSERT(aIndexId);
-
-      Helper helper(aIndexId);
-      aObjectStoreMetadata->mIndexes.EnumerateRead(&Enumerate, &helper);
-
-      MOZ_ASSERT_IF(helper.mIsLastIndex, helper.mFoundTargetId);
-
-      return helper.mIsLastIndex;
-    }
-
-  private:
-    explicit
-    Helper(const int64_t aIndexId)
-      : mIndexId(aIndexId)
-      , mIsLastIndex(true)
-      , mFoundTargetId(false)
-    { }
-
-    static PLDHashOperator
-    Enumerate(const uint64_t& aKey, FullIndexMetadata* aValue, void* aClosure)
-    {
-      auto* helper = static_cast<Helper*>(aClosure);
-      MOZ_ASSERT(helper);
-
-      if (uint64_t(helper->mIndexId) == aKey) {
-        helper->mFoundTargetId = true;
-      } else if (!aValue->mDeleted) {
-        helper->mIsLastIndex = false;
-        return PL_DHASH_STOP;
-      }
-
-      return PL_DHASH_NEXT;
-    }
-  };
-
   if (NS_WARN_IF(!aObjectStoreId)) {
     ASSERT_UNLESS_FUZZING();
     return false;
   }
 
   if (NS_WARN_IF(!aIndexId)) {
     ASSERT_UNLESS_FUZZING();
     return false;
@@ -13510,22 +11769,17 @@ VersionChangeTransaction::RecvDeleteInde
 
   if (NS_WARN_IF(mCommitOrAbortReceived)) {
     ASSERT_UNLESS_FUZZING();
     return false;
   }
 
   foundIndexMetadata->mDeleted = true;
 
-  nsRefPtr<DeleteIndexOp> op =
-    new DeleteIndexOp(this,
-                      aObjectStoreId,
-                      aIndexId,
-                      foundIndexMetadata->mCommonMetadata.unique(),
-                      Helper::IsLastIndex(foundObjectStoreMetadata, aIndexId));
+  nsRefPtr<DeleteIndexOp> op = new DeleteIndexOp(this, aIndexId);
 
   if (NS_WARN_IF(!op->Init(this))) {
     op->Cleanup();
     return false;
   }
 
   op->DispatchToConnectionPool();
 
@@ -15342,117 +13596,87 @@ DatabaseOperationBase::ReinterpretDouble
     double d;
     uint64_t u;
   } pun;
   pun.d = aDouble;
   return pun.u;
 }
 
 // static
-template <typename T>
-nsresult
-DatabaseOperationBase::GetStructuredCloneReadInfoFromSource(
-                                                 T* aSource,
-                                                 uint32_t aDataIndex,
-                                                 uint32_t aFileIdsIndex,
-                                                 FileManager* aFileManager,
-                                                 StructuredCloneReadInfo* aInfo)
+nsresult
+DatabaseOperationBase::GetStructuredCloneReadInfoFromStatement(
+                                               mozIStorageStatement* aStatement,
+                                               uint32_t aDataIndex,
+                                               uint32_t aFileIdsIndex,
+                                               FileManager* aFileManager,
+                                               StructuredCloneReadInfo* aInfo)
 {
   MOZ_ASSERT(!IsOnBackgroundThread());
-  MOZ_ASSERT(aSource);
+  MOZ_ASSERT(aStatement);
   MOZ_ASSERT(aFileManager);
-  MOZ_ASSERT(aInfo);
-
-#ifdef DEBUG
-  {
-    int32_t columnType;
-    MOZ_ASSERT(NS_SUCCEEDED(aSource->GetTypeOfIndex(aDataIndex, &columnType)));
-    MOZ_ASSERT(columnType == mozIStorageStatement::VALUE_TYPE_BLOB);
+
+  PROFILER_LABEL("IndexedDB",
+                 "DatabaseOperationBase::"
+                 "GetStructuredCloneReadInfoFromStatement",
+                 js::ProfileEntry::Category::STORAGE);
+
+#ifdef DEBUG
+  {
+    int32_t type;
+    MOZ_ASSERT(NS_SUCCEEDED(aStatement->GetTypeOfIndex(aDataIndex, &type)));
+    MOZ_ASSERT(type == mozIStorageStatement::VALUE_TYPE_BLOB);
   }
 #endif
 
   const uint8_t* blobData;
   uint32_t blobDataLength;
-  nsresult rv = aSource->GetSharedBlob(aDataIndex, &blobDataLength, &blobData);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  bool isNull;
-  rv = aSource->GetIsNull(aFileIdsIndex, &isNull);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  nsString fileIds;
-
-  if (isNull) {
-    fileIds.SetIsVoid(true);
-  } else {
-    rv = aSource->GetString(aFileIdsIndex, fileIds);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
-  rv = GetStructuredCloneReadInfoFromBlob(blobData,
-                                          blobDataLength,
-                                          fileIds,
-                                          aFileManager,
-                                          aInfo);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
-}
-
-// static
-nsresult
-DatabaseOperationBase::GetStructuredCloneReadInfoFromBlob(
-                                                 const uint8_t* aBlobData,
-                                                 uint32_t aBlobDataLength,
-                                                 const nsAString& aFileIds,
-                                                 FileManager* aFileManager,
-                                                 StructuredCloneReadInfo* aInfo)
-{
-  MOZ_ASSERT(!IsOnBackgroundThread());
-  MOZ_ASSERT(aFileManager);
-  MOZ_ASSERT(aInfo);
-
-  PROFILER_LABEL("IndexedDB",
-                 "DatabaseOperationBase::GetStructuredCloneReadInfoFromBlob",
-                 js::ProfileEntry::Category::STORAGE);
-
-  const char* compressed = reinterpret_cast<const char*>(aBlobData);
-  size_t compressedLength = size_t(aBlobDataLength);
+  nsresult rv =
+    aStatement->GetSharedBlob(aDataIndex, &blobDataLength, &blobData);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  const char* compressed = reinterpret_cast<const char*>(blobData);
+  size_t compressedLength = size_t(blobDataLength);
 
   size_t uncompressedLength;
   if (NS_WARN_IF(!snappy::GetUncompressedLength(compressed, compressedLength,
                                                 &uncompressedLength))) {
     return NS_ERROR_FILE_CORRUPTED;
   }
 
-  AutoFallibleTArray<uint8_t, 512> uncompressed;
+  FallibleTArray<uint8_t> uncompressed;
   if (NS_WARN_IF(!uncompressed.SetLength(uncompressedLength))) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   char* uncompressedBuffer = reinterpret_cast<char*>(uncompressed.Elements());
 
   if (NS_WARN_IF(!snappy::RawUncompress(compressed, compressedLength,
                                         uncompressedBuffer))) {
     return NS_ERROR_FILE_CORRUPTED;
   }
 
   aInfo->mData.SwapElements(uncompressed);
 
-  if (!aFileIds.IsVoid()) {
+  bool isNull;
+  rv = aStatement->GetIsNull(aFileIdsIndex, &isNull);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!isNull) {
+    nsString ids;
+    rv = aStatement->GetString(aFileIdsIndex, ids);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
     nsAutoTArray<int64_t, 10> array;
-    nsresult rv = ConvertFileIdsToArray(aFileIds, array);
+    rv = ConvertFileIdsToArray(ids, array);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     for (uint32_t count = array.Length(), index = 0; index < count; index++) {
       MOZ_ASSERT(array[index] > 0);
 
       nsRefPtr<FileInfo> fileInfo = aFileManager->GetFileInfo(array[index]);
@@ -15523,565 +13747,144 @@ DatabaseOperationBase::AppendConditionCl
     aResult.Append('=');
   }
 
   aResult += NS_LITERAL_CSTRING(" :") + aArgName;
 }
 
 // static
 nsresult
-DatabaseOperationBase::GetUniqueIndexTableForObjectStore(
-                                TransactionBase* aTransaction,
-                                int64_t aObjectStoreId,
-                                Maybe<UniqueIndexTable>& aMaybeUniqueIndexTable)
-{
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(aTransaction);
-  MOZ_ASSERT(aObjectStoreId);
-  MOZ_ASSERT(aMaybeUniqueIndexTable.isNothing());
-
-  class MOZ_STACK_CLASS Helper final
-  {
-  public:
-    static nsresult
-    CopyUniqueValues(const IndexTable& aIndexes,
-                     Maybe<UniqueIndexTable>& aMaybeUniqueIndexTable)
-    {
-      const uint32_t indexCount = aIndexes.Count();
-      MOZ_ASSERT(indexCount);
-
-      aMaybeUniqueIndexTable.emplace();
-
-      aIndexes.EnumerateRead(Enumerate, aMaybeUniqueIndexTable.ptr());
-
-      if (NS_WARN_IF(aMaybeUniqueIndexTable.ref().Count() != indexCount)) {
-        IDB_REPORT_INTERNAL_ERR();
-        aMaybeUniqueIndexTable.reset();
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
-
-#ifdef DEBUG
-      aMaybeUniqueIndexTable.ref().MarkImmutable();
-#endif
-      return NS_OK;
-    }
-
-  private:
-    static PLDHashOperator
-    Enumerate(const uint64_t& aKey, FullIndexMetadata* aValue, void* aClosure)
-    {
-      auto* uniqueIndexTable = static_cast<UniqueIndexTable*>(aClosure);
-      MOZ_ASSERT(uniqueIndexTable);
-      MOZ_ASSERT(!uniqueIndexTable->Get(aValue->mCommonMetadata.id()));
-
-      if (NS_WARN_IF(!uniqueIndexTable->Put(aValue->mCommonMetadata.id(),
-                                            aValue->mCommonMetadata.unique(),
-                                            fallible))) {
-        return PL_DHASH_STOP;
-      }
-
-      return PL_DHASH_NEXT;
-    }
-  };
-
-  const nsRefPtr<FullObjectStoreMetadata> objectStoreMetadata = 
-    aTransaction->GetMetadataForObjectStoreId(aObjectStoreId);
-  MOZ_ASSERT(objectStoreMetadata);
-
-  if (!objectStoreMetadata->mIndexes.Count()) {
-    return NS_OK;
-  }
-
-  nsresult rv = Helper::CopyUniqueValues(objectStoreMetadata->mIndexes,
-                                         aMaybeUniqueIndexTable);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
-}
-
-// static
-nsresult
-DatabaseOperationBase::IndexDataValuesFromUpdateInfos(
-                                  const nsTArray<IndexUpdateInfo>& aUpdateInfos,
-                                  const UniqueIndexTable& aUniqueIndexTable,
-                                  FallibleTArray<IndexDataValue>& aIndexValues)
-{
-  MOZ_ASSERT(aIndexValues.IsEmpty());
-  MOZ_ASSERT_IF(!aUpdateInfos.IsEmpty(), aUniqueIndexTable.Count());
-
-  PROFILER_LABEL("IndexedDB",
-                 "DatabaseOperationBase::IndexDataValuesFromUpdateInfos",
-                 js::ProfileEntry::Category::STORAGE);
-
-  const uint32_t count = aUpdateInfos.Length();
-
-  if (!count) {
-    return NS_OK;
-  }
-
-  if (NS_WARN_IF(!aIndexValues.SetCapacity(count))) {
-    IDB_REPORT_INTERNAL_ERR();
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  for (uint32_t idxIndex = 0; idxIndex < count; idxIndex++) {
-    const IndexUpdateInfo& updateInfo = aUpdateInfos[idxIndex];
-    const int64_t& indexId = updateInfo.indexId();
-    const Key& key = updateInfo.value();
-
-    bool unique;
-    MOZ_ALWAYS_TRUE(aUniqueIndexTable.Get(indexId, &unique));
-
-    MOZ_ALWAYS_TRUE(
-      aIndexValues.InsertElementSorted(IndexDataValue(indexId, unique, key)));
-  }
-
-  return NS_OK;
-}
-
-// static
-nsresult
-DatabaseOperationBase::InsertIndexTableRows(
-                             DatabaseConnection* aConnection,
-                             const int64_t aObjectStoreId,
-                             const Key& aObjectStoreKey,
-                             const FallibleTArray<IndexDataValue>& aIndexValues)
+DatabaseOperationBase::UpdateIndexes(
+                              DatabaseConnection* aConnection,
+                              const UniqueIndexTable& aUniqueIndexTable,
+                              const Key& aObjectStoreKey,
+                              bool aOverwrite,
+                              int64_t aObjectDataId,
+                              const nsTArray<IndexUpdateInfo>& aUpdateInfoArray)
 {
   MOZ_ASSERT(aConnection);
   aConnection->AssertIsOnConnectionThread();
   MOZ_ASSERT(!aObjectStoreKey.IsUnset());
 
   PROFILER_LABEL("IndexedDB",
-                 "DatabaseOperationBase::InsertIndexTableRows",
-                 js::ProfileEntry::Category::STORAGE);
-
-  const uint32_t count = aIndexValues.Length();
-  if (!count) {
-    return NS_OK;
-  }
-
-  NS_NAMED_LITERAL_CSTRING(objectStoreIdString, "object_store_id");
-  NS_NAMED_LITERAL_CSTRING(objectDataKeyString, "object_data_key");
-  NS_NAMED_LITERAL_CSTRING(indexIdString, "index_id");
-  NS_NAMED_LITERAL_CSTRING(valueString, "value");
-
+                 "DatabaseOperationBase::UpdateIndexes",
+                 js::ProfileEntry::Category::STORAGE);
+
+  nsresult rv;
+  NS_NAMED_LITERAL_CSTRING(objectDataId, "object_data_id");
+
+  if (aOverwrite) {
+    DatabaseConnection::CachedStatement stmt;
+    rv = aConnection->GetCachedStatement(
+        "DELETE FROM unique_index_data "
+        "WHERE object_data_id = :object_data_id; "
+        "DELETE FROM index_data "
+        "WHERE object_data_id = :object_data_id",
+        &stmt);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = stmt->BindInt64ByName(objectDataId, aObjectDataId);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = stmt->Execute();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  // Avoid lots of hash lookups for objectStores with lots of indexes by lazily
+  // holding the necessary statements on the stack outside the loop.
   DatabaseConnection::CachedStatement insertUniqueStmt;
   DatabaseConnection::CachedStatement insertStmt;
 
-  nsresult rv;
-
-  for (uint32_t index = 0; index < count; index++) {
-    const IndexDataValue& info = aIndexValues[index];
+  for (uint32_t idxCount = aUpdateInfoArray.Length(), idxIndex = 0;
+       idxIndex < idxCount;
+       idxIndex++) {
+    const IndexUpdateInfo& updateInfo = aUpdateInfoArray[idxIndex];
+
+    bool unique;
+    MOZ_ALWAYS_TRUE(aUniqueIndexTable.Get(updateInfo.indexId(), &unique));
 
     DatabaseConnection::CachedStatement& stmt =
-      info.mUnique ? insertUniqueStmt : insertStmt;
+      unique ? insertUniqueStmt : insertStmt;
 
     if (stmt) {
       stmt.Reset();
-    } else if (info.mUnique) {
+    } else if (unique) {
       rv = aConnection->GetCachedStatement(
         "INSERT INTO unique_index_data "
-          "(index_id, value, object_store_id, object_data_key) "
-          "VALUES (:index_id, :value, :object_store_id, :object_data_key);",
+          "(index_id, object_data_id, object_data_key, value) "
+        "VALUES (:index_id, :object_data_id, :object_data_key, :value)",
         &stmt);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     } else {
       rv = aConnection->GetCachedStatement(
-        "INSERT OR IGNORE INTO index_data "
-          "(index_id, value, object_data_key, object_store_id) "
-          "VALUES (:index_id, :value, :object_data_key, :object_store_id);",
+        "INSERT OR IGNORE INTO index_data ("
+          "index_id, object_data_id, object_data_key, value) "
+        "VALUES (:index_id, :object_data_id, :object_data_key, :value)",
         &stmt);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
 
-    rv = stmt->BindInt64ByName(indexIdString, info.mIndexId);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = info.mKey.BindToStatement(stmt, valueString);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = stmt->BindInt64ByName(objectStoreIdString, aObjectStoreId);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = aObjectStoreKey.BindToStatement(stmt, objectDataKeyString);
+    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("index_id"),
+                               updateInfo.indexId());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = stmt->BindInt64ByName(objectDataId, aObjectDataId);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = aObjectStoreKey.BindToStatement(stmt,
+                                         NS_LITERAL_CSTRING("object_data_key"));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = updateInfo.value().BindToStatement(stmt, NS_LITERAL_CSTRING("value"));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     rv = stmt->Execute();
-    if (rv == NS_ERROR_STORAGE_CONSTRAINT && info.mUnique) {
+    if (rv == NS_ERROR_STORAGE_CONSTRAINT && unique) {
       // If we're inserting multiple entries for the same unique index, then
       // we might have failed to insert due to colliding with another entry for
       // the same index in which case we should ignore it.
-      for (int32_t index2 = int32_t(index) - 1;
-           index2 >= 0 && aIndexValues[index2].mIndexId == info.mIndexId;
-           --index2) {
-        if (info.mKey == aIndexValues[index2].mKey) {
+      for (int32_t index = int32_t(idxIndex) - 1;
+           index >= 0 &&
+           aUpdateInfoArray[index].indexId() == updateInfo.indexId();
+           --index) {
+        if (updateInfo.value() == aUpdateInfoArray[index].value()) {
           // We found a key with the same value for the same index. So we
           // must have had a collision with a value we just inserted.
           rv = NS_OK;
           break;
         }
       }
     }
 
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
-// static
-nsresult
-DatabaseOperationBase::DeleteIndexDataTableRows(
-                             DatabaseConnection* aConnection,
-                             const Key& aObjectStoreKey,
-                             const FallibleTArray<IndexDataValue>& aIndexValues)
-{
-  MOZ_ASSERT(aConnection);
-  aConnection->AssertIsOnConnectionThread();
-  MOZ_ASSERT(!aObjectStoreKey.IsUnset());
-
-  PROFILER_LABEL("IndexedDB",
-                 "DatabaseOperationBase::DeleteIndexDataTableRows",
-                 js::ProfileEntry::Category::STORAGE);
-
-  const uint32_t count = aIndexValues.Length();
-  if (!count) {
-    return NS_OK;
-  }
-
-  NS_NAMED_LITERAL_CSTRING(indexIdString, "index_id");
-  NS_NAMED_LITERAL_CSTRING(valueString, "value");
-  NS_NAMED_LITERAL_CSTRING(objectDataKeyString, "object_data_key");
-
-  DatabaseConnection::CachedStatement deleteUniqueStmt;
-  DatabaseConnection::CachedStatement deleteStmt;
-
-  nsresult rv;
-
-  for (uint32_t index = 0; index < count; index++) {
-    const IndexDataValue& indexValue = aIndexValues[index];
-
-    DatabaseConnection::CachedStatement& stmt =
-      indexValue.mUnique ? deleteUniqueStmt : deleteStmt;
-
-    if (stmt) {
-      stmt.Reset();
-    } else if (indexValue.mUnique) {
-      rv = aConnection->GetCachedStatement(
-        "DELETE FROM unique_index_data "
-          "WHERE index_id = :index_id "
-          "AND value = :value;",
-        &stmt);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-    } else {
-      rv = aConnection->GetCachedStatement(
-        "DELETE FROM index_data "
-          "WHERE index_id = :index_id "
-          "AND value = :value "
-          "AND object_data_key = :object_data_key;",
-        &stmt);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-    }
-
-    rv = stmt->BindInt64ByName(indexIdString, indexValue.mIndexId);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = indexValue.mKey.BindToStatement(stmt, valueString);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    if (!indexValue.mUnique) {
-      rv = aObjectStoreKey.BindToStatement(stmt, objectDataKeyString);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-    }
-
-    rv = stmt->Execute();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
-  return NS_OK;
-}
-
-// static
-nsresult
-DatabaseOperationBase::DeleteObjectStoreDataTableRowsWithIndexes(
-                                              DatabaseConnection* aConnection,
-                                              const int64_t aObjectStoreId,
-                                              const OptionalKeyRange& aKeyRange)
-{
-  MOZ_ASSERT(aConnection);
-  aConnection->AssertIsOnConnectionThread();
-  MOZ_ASSERT(aObjectStoreId);
-  MOZ_ASSERT(ObjectStoreHasIndexes(aConnection, aObjectStoreId),
-             "Don't use this slow method if there are no indexes!");
-
-  PROFILER_LABEL("IndexedDB",
-                 "DatabaseOperationBase::"
-                 "DeleteObjectStoreDataTableRowsWithIndexes",
-                 js::ProfileEntry::Category::STORAGE);
-
-  const bool singleRowOnly =
-    aKeyRange.type() == OptionalKeyRange::TSerializedKeyRange &&
-    aKeyRange.get_SerializedKeyRange().isOnly();
-
-  NS_NAMED_LITERAL_CSTRING(objectStoreIdString, "object_store_id");
-  NS_NAMED_LITERAL_CSTRING(keyString, "key");
-
-  nsresult rv;
-  Key objectStoreKey;
-  DatabaseConnection::CachedStatement selectStmt;
-
-  if (singleRowOnly) {
-    rv = aConnection->GetCachedStatement(
-      "SELECT index_data_values "
-        "FROM object_data "
-        "WHERE object_store_id = :object_store_id "
-        "AND key = :key;",
-      &selectStmt);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    objectStoreKey = aKeyRange.get_SerializedKeyRange().lower();
-
-    rv = objectStoreKey.BindToStatement(selectStmt, keyString);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  } else {
-    nsAutoCString keyRangeClause;
-    if (aKeyRange.type() == OptionalKeyRange::TSerializedKeyRange) {
-      GetBindingClauseForKeyRange(aKeyRange.get_SerializedKeyRange(),
-                                  keyString,
-                                  keyRangeClause);
-    }
-
-    rv = aConnection->GetCachedStatement(
-      NS_LITERAL_CSTRING("SELECT index_data_values, key "
-                           "FROM object_data "
-                           "WHERE object_store_id = :") + objectStoreIdString +
-      keyRangeClause +
-      NS_LITERAL_CSTRING(";"),
-      &selectStmt);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    if (aKeyRange.type() == OptionalKeyRange::TSerializedKeyRange) {
-      rv = BindKeyRangeToStatement(aKeyRange, selectStmt);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-    }
-  }
-
-  rv = selectStmt->BindInt64ByName(objectStoreIdString, aObjectStoreId);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  DatabaseConnection::CachedStatement deleteStmt;
-  AutoFallibleTArray<IndexDataValue, 32> indexValues;
-
-  DebugOnly<uint32_t> resultCountDEBUG = 0;
-
-  bool hasResult;
-  while (NS_SUCCEEDED(rv = selectStmt->ExecuteStep(&hasResult)) && hasResult) {
-    if (!singleRowOnly) {
-      rv = objectStoreKey.SetFromStatement(selectStmt, 1);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      indexValues.ClearAndRetainStorage();
-    }
-
-    rv = ReadCompressedIndexDataValues(selectStmt, 0, indexValues);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = DeleteIndexDataTableRows(aConnection, objectStoreKey, indexValues);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    if (deleteStmt) {
-      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(deleteStmt->Reset()));
-    } else {
-      rv = aConnection->GetCachedStatement(
-        "DELETE FROM object_data "
-          "WHERE object_store_id = :object_store_id "
-          "AND key = :key;",
-        &deleteStmt);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-    }
-
-    rv = deleteStmt->BindInt64ByName(objectStoreIdString, aObjectStoreId);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = objectStoreKey.BindToStatement(deleteStmt, keyString);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = deleteStmt->Execute();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    resultCountDEBUG++;
-  }
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  MOZ_ASSERT_IF(singleRowOnly, resultCountDEBUG <= 1);
-
-  return NS_OK;
-}
-
-// static
-nsresult
-DatabaseOperationBase::UpdateIndexValues(
-                             DatabaseConnection* aConnection,
-                             const int64_t aObjectStoreId,
-                             const Key& aObjectStoreKey,
-                             const FallibleTArray<IndexDataValue>& aIndexValues)
-{
-  MOZ_ASSERT(aConnection);
-  aConnection->AssertIsOnConnectionThread();
-  MOZ_ASSERT(!aObjectStoreKey.IsUnset());
-
-  PROFILER_LABEL("IndexedDB",
-                 "DatabaunseOperationBase::UpdateIndexValues",
-                 js::ProfileEntry::Category::STORAGE);
-
-  UniqueMozFreePtr<uint8_t> indexDataValues;
-  uint32_t indexDataValuesLength;
-  nsresult rv = MakeCompressedIndexDataValues(aIndexValues,
-                                              indexDataValues,
-                                              &indexDataValuesLength);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  MOZ_ASSERT(!indexDataValuesLength == !(indexDataValues.get()));
-
-  DatabaseConnection::CachedStatement updateStmt;
-  rv = aConnection->GetCachedStatement(
-    "UPDATE object_data "
-      "SET index_data_values = :index_data_values "
-      "WHERE object_store_id = :object_store_id "
-      "AND key = :key;",
-    &updateStmt);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  NS_NAMED_LITERAL_CSTRING(indexDataValuesString, "index_data_values");
-
-  if (indexDataValues) {
-    rv = updateStmt->BindAdoptedBlobByName(indexDataValuesString,
-                                           indexDataValues.get(),
-                                           indexDataValuesLength);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    indexDataValues.release();
-  } else {
-    rv = updateStmt->BindNullByName(indexDataValuesString);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
-  rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
-                                   aObjectStoreId);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aObjectStoreKey.BindToStatement(updateStmt, NS_LITERAL_CSTRING("key"));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = updateStmt->Execute();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
-}
-
-#ifdef DEBUG
-
-// static
-bool
-DatabaseOperationBase::ObjectStoreHasIndexes(DatabaseConnection* aConnection,
-                                             const int64_t aObjectStoreId)
-{
-  MOZ_ASSERT(aConnection);
-  aConnection->AssertIsOnConnectionThread();
-  MOZ_ASSERT(aObjectStoreId);
-
-  DatabaseConnection::CachedStatement stmt;
-
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-    aConnection->GetCachedStatement(
-      "SELECT id "
-        "FROM object_store_index "
-        "WHERE object_store_id = :object_store_id;",
-      &stmt)));
-
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-    stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
-                          aObjectStoreId)));
-
-  bool hasResult;
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)));
-
-  return hasResult;
-}
-
-#endif // DEBUG
-
 NS_IMPL_ISUPPORTS_INHERITED(DatabaseOperationBase,
                             nsRunnable,
                             mozIStorageProgressHandler)
 
 NS_IMETHODIMP
 DatabaseOperationBase::OnProgress(mozIStorageConnection* aConnection,
                                   bool* _retval)
 {
@@ -18087,29 +15890,38 @@ DeleteDatabaseOp::LoadPreviousVersion(ns
   rv = OpenDatabaseAndHandleBusy(ss, aDatabaseFile, getter_AddRefs(connection));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
 #ifdef DEBUG
   {
     nsCOMPtr<mozIStorageStatement> stmt;
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-      connection->CreateStatement(NS_LITERAL_CSTRING(
-        "SELECT name "
-          "FROM database"
-        ), getter_AddRefs(stmt))));
-
-    bool hasResult;
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)));
-
-    nsString databaseName;
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(stmt->GetString(0, databaseName)));
-
-    MOZ_ASSERT(mCommonParams.metadata().name() == databaseName);
+    rv = connection->CreateStatement(NS_LITERAL_CSTRING(
+      "SELECT name "
+      "FROM database"
+    ), getter_AddRefs(stmt));
+    NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "CreateStatement failed!");
+
+    if (NS_SUCCEEDED(rv)) {
+      bool hasResult;
+      rv = stmt->ExecuteStep(&hasResult);
+      NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "ExecuteStep failed!");
+
+      if (NS_SUCCEEDED(rv)) {
+        nsString databaseName;
+        rv = stmt->GetString(0, databaseName);
+        NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "GetString failed!");
+
+        if (NS_SUCCEEDED(rv)) {
+          NS_WARN_IF_FALSE(mCommonParams.metadata().name() == databaseName,
+                           "Database names don't match!");
+        }
+      }
+    }
   }
 #endif
 
   nsCOMPtr<mozIStorageStatement> stmt;
   rv = connection->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT version "
     "FROM database"
   ), getter_AddRefs(stmt));
@@ -19021,52 +16833,16 @@ CommitOp::CommitOrRollbackAutoIncrementC
         metadata->mComittedAutoIncrementId = metadata->mNextAutoIncrementId;
       } else {
         metadata->mNextAutoIncrementId = metadata->mComittedAutoIncrementId;
       }
     }
   }
 }
 
-#ifdef DEBUG
-
-void
-TransactionBase::
-CommitOp::AssertForeignKeyConsistency(DatabaseConnection* aConnection)
-{
-  MOZ_ASSERT(aConnection);
-  MOZ_ASSERT(mTransaction);
-  mTransaction->AssertIsOnConnectionThread();
-  MOZ_ASSERT(mTransaction->GetMode() != IDBTransaction::READ_ONLY);
-
-  DatabaseConnection::CachedStatement pragmaStmt;
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-    aConnection->GetCachedStatement("PRAGMA foreign_keys;", &pragmaStmt)));
-
-  bool hasResult;
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(pragmaStmt->ExecuteStep(&hasResult)));
-
-  MOZ_ASSERT(hasResult);
-
-  int32_t foreignKeysEnabled;
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(pragmaStmt->GetInt32(0, &foreignKeysEnabled)));
-
-  MOZ_ASSERT(foreignKeysEnabled, "Database doesn't have foreign keys enabled!");
-
-  DatabaseConnection::CachedStatement checkStmt;
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-    aConnection->GetCachedStatement("PRAGMA foreign_key_check;", &checkStmt)));
-
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(checkStmt->ExecuteStep(&hasResult)));
-
-  MOZ_ASSERT(!hasResult, "Database has inconsisistent foreign keys!");
-}
-
-#endif // DEBUG
-
 NS_IMPL_ISUPPORTS_INHERITED0(TransactionBase::CommitOp, DatabaseOperationBase)
 
 NS_IMETHODIMP
 TransactionBase::
 CommitOp::Run()
 {
   MOZ_ASSERT(mTransaction);
   mTransaction->AssertIsOnConnectionThread();
@@ -19096,18 +16872,16 @@ CommitOp::Run()
         NS_WARN_IF_FALSE(NS_SUCCEEDED(mResultCode), "WillCommit() failed!");
 
         if (NS_SUCCEEDED(mResultCode)) {
           mResultCode = WriteAutoIncrementCounts();
           NS_WARN_IF_FALSE(NS_SUCCEEDED(mResultCode),
                            "WriteAutoIncrementCounts() failed!");
 
           if (NS_SUCCEEDED(mResultCode)) {
-            AssertForeignKeyConsistency(connection);
-
             DatabaseConnection::CachedStatement stmt;
             mResultCode = connection->GetCachedStatement("COMMIT", &stmt);
             NS_WARN_IF_FALSE(NS_SUCCEEDED(mResultCode),
                              "Failed to get 'COMMIT' statement!");
 
             if (NS_SUCCEEDED(mResultCode)) {
               mResultCode = stmt->Execute();
               NS_WARN_IF_FALSE(NS_SUCCEEDED(mResultCode), "Commit failed!");
@@ -19248,38 +17022,16 @@ CreateObjectStoreOp::DoDatabaseWork(Data
   PROFILER_LABEL("IndexedDB",
                  "CreateObjectStoreOp::DoDatabaseWork",
                  js::ProfileEntry::Category::STORAGE);
 
   if (NS_WARN_IF(IndexedDatabaseManager::InLowDiskSpaceMode())) {
     return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
   }
 
-#ifdef DEBUG
-  {
-    // Make sure that we're not creating an object store with the same name as
-    // another that already exists. This should be impossible because we should
-    // have thrown an error long before now...
-    DatabaseConnection::CachedStatement stmt;
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-      aConnection->GetCachedStatement(
-        "SELECT name "
-          "FROM object_store "
-          "WHERE name = :name;",
-        &stmt)));
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-      stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mMetadata.name())));
-
-    bool hasResult;
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)));
-    MOZ_ASSERT(!hasResult);
-  }
-#endif
-
   DatabaseConnection::AutoSavepoint autoSave;
   nsresult rv = autoSave.Start(Transaction());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   DatabaseConnection::CachedStatement stmt;
   rv = aConnection->GetCachedStatement(
@@ -19348,362 +17100,257 @@ DeleteObjectStoreOp::DoDatabaseWork(Data
 {
   MOZ_ASSERT(aConnection);
   aConnection->AssertIsOnConnectionThread();
 
   PROFILER_LABEL("IndexedDB",
                  "DeleteObjectStoreOp::DoDatabaseWork",
                  js::ProfileEntry::Category::STORAGE);
 
-  NS_NAMED_LITERAL_CSTRING(objectStoreIdString, "object_store_id");
-
-#ifdef DEBUG
-  {
-    // Make sure |mIsLastObjectStore| is telling the truth.
-    DatabaseConnection::CachedStatement stmt;
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-      aConnection->GetCachedStatement(
-        "SELECT id "
-          "FROM object_store;",
-        &stmt)));
-
-    bool foundThisObjectStore = false;
-    bool foundOtherObjectStore = false;
-
-    while (true) {
-      bool hasResult;
-      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)));
-
-      if (!hasResult) {
-        break;
-      }
-
-      int64_t id;
-      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(stmt->GetInt64(0, &id)));
-
-      if (id == mMetadata->mCommonMetadata.id()) {
-        foundThisObjectStore = true;
-      } else {
-        foundOtherObjectStore = true;
-      }
-    }
-
-    MOZ_ASSERT_IF(mIsLastObjectStore,
-                  foundThisObjectStore && !foundOtherObjectStore);
-    MOZ_ASSERT_IF(!mIsLastObjectStore,
-                  foundThisObjectStore && foundOtherObjectStore);
-
-    // Make sure |hasIndexes| is telling the truth.
-    MOZ_ASSERT(mObjectStoreHasIndexes ==
-                 ObjectStoreHasIndexes(aConnection,
-                                       mMetadata->mCommonMetadata.id()));
-  }
-#endif
-
   DatabaseConnection::AutoSavepoint autoSave;
   nsresult rv = autoSave.Start(Transaction());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  if (mIsLastObjectStore) {
-    // We can just delete everything if this is the last object store.
-    DatabaseConnection::CachedStatement stmt;
-    rv = aConnection->GetCachedStatement(
-      "DELETE FROM index_data;",
-      &stmt);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = stmt->Execute();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = aConnection->GetCachedStatement(
-      "DELETE FROM unique_index_data;",
-      &stmt);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = stmt->Execute();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = aConnection->GetCachedStatement(
-      "DELETE FROM object_data;",
-      &stmt);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = stmt->Execute();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = aConnection->GetCachedStatement(
-      "DELETE FROM object_store_index;",
-      &stmt);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = stmt->Execute();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = aConnection->GetCachedStatement(
-      "DELETE FROM object_store;",
-      &stmt);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = stmt->Execute();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  } else {
-    if (mObjectStoreHasIndexes) {
-      rv = DeleteObjectStoreDataTableRowsWithIndexes(
-        aConnection,
-        mMetadata->mCommonMetadata.id(),
-        void_t());
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      // Now clean up the object store index table.
-      DatabaseConnection::CachedStatement stmt;
-      rv = aConnection->GetCachedStatement(
-        "DELETE FROM object_store_index "
-          "WHERE object_store_id = :object_store_id;",
-        &stmt);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      rv = stmt->BindInt64ByName(objectStoreIdString,
-                                 mMetadata->mCommonMetadata.id());
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      rv = stmt->Execute();
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-    } else {
-      // We only have to worry about object data if this object store has no
-      // indexes.
-      DatabaseConnection::CachedStatement stmt;
-      rv = aConnection->GetCachedStatement(
-        "DELETE FROM object_data "
-          "WHERE object_store_id = :object_store_id;",
-        &stmt);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      rv = stmt->BindInt64ByName(objectStoreIdString,
-                                 mMetadata->mCommonMetadata.id());
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      rv = stmt->Execute();
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-    }
-
-    DatabaseConnection::CachedStatement stmt;
-    rv = aConnection->GetCachedStatement(
-      "DELETE FROM object_store "
-        "WHERE id = :object_store_id;",
-      &stmt);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = stmt->BindInt64ByName(objectStoreIdString,
-                               mMetadata->mCommonMetadata.id());
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = stmt->Execute();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-#ifdef DEBUG
-    {
-      int32_t deletedRowCount;
-      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-        aConnection->GetStorageConnection()->
-          GetAffectedRows(&deletedRowCount)));
-      MOZ_ASSERT(deletedRowCount == 1);
-    }
-#endif
-  }
-
-  rv = autoSave.Commit();
+  DatabaseConnection::CachedStatement stmt;
+  rv = aConnection->GetCachedStatement(
+    "DELETE FROM object_store "
+    "WHERE id = :id",
+    &stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"),
+                             mMetadata->mCommonMetadata.id());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->Execute();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (mMetadata->mCommonMetadata.autoIncrement()) {
     Transaction()->ForgetModifiedAutoIncrementObjectStore(mMetadata);
   }
 
+  rv = autoSave.Commit();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   return NS_OK;
 }
 
 CreateIndexOp::CreateIndexOp(VersionChangeTransaction* aTransaction,
                              const int64_t aObjectStoreId,
                              const IndexMetadata& aMetadata)
   : VersionChangeTransactionOp(aTransaction)
   , mMetadata(aMetadata)
   , mFileManager(aTransaction->GetDatabase()->GetFileManager())
   , mDatabaseId(aTransaction->DatabaseId())
   , mObjectStoreId(aObjectStoreId)
 {
   MOZ_ASSERT(aObjectStoreId);
   MOZ_ASSERT(aMetadata.id());
   MOZ_ASSERT(mFileManager);
   MOZ_ASSERT(!mDatabaseId.IsEmpty());
+
+  class MOZ_STACK_CLASS Helper final
+  {
+  public:
+    static void
+    CopyUniqueValues(const IndexTable& aIndexes,
+                     Maybe<UniqueIndexTable>& aMaybeUniqueIndexTable)
+    {
+      aMaybeUniqueIndexTable.emplace();
+
+      const uint32_t indexCount = aIndexes.Count();
+      MOZ_ASSERT(indexCount);
+
+      aIndexes.EnumerateRead(Enumerate, aMaybeUniqueIndexTable.ptr());
+
+      if (NS_WARN_IF(aMaybeUniqueIndexTable.ref().Count() != indexCount)) {
+        aMaybeUniqueIndexTable.reset();
+        return;
+      }
+
+#ifdef DEBUG
+      aMaybeUniqueIndexTable.ref().MarkImmutable();
+#endif
+    }
+
+  private:
+    static PLDHashOperator
+    Enumerate(const uint64_t& aKey, FullIndexMetadata* aValue, void* aClosure)
+    {
+      auto* uniqueIndexTable = static_cast<UniqueIndexTable*>(aClosure);
+      MOZ_ASSERT(uniqueIndexTable);
+      MOZ_ASSERT(!uniqueIndexTable->Get(aValue->mCommonMetadata.id()));
+
+      if (NS_WARN_IF(!uniqueIndexTable->Put(aValue->mCommonMetadata.id(),
+                                            aValue->mCommonMetadata.unique(),
+                                            fallible))) {
+        return PL_DHASH_STOP;
+      }
+
+      return PL_DHASH_NEXT;
+    }
+  };
+
+  InitThreadLocals();
+
+  const nsRefPtr<FullObjectStoreMetadata> objectStoreMetadata = 
+    aTransaction->GetMetadataForObjectStoreId(aObjectStoreId);
+  MOZ_ASSERT(objectStoreMetadata);
+
+  Helper::CopyUniqueValues(objectStoreMetadata->mIndexes,
+                           mMaybeUniqueIndexTable);
 }
 
 unsigned int CreateIndexOp::sThreadLocalIndex = kBadThreadLocalIndex;
 
+// static
+void
+CreateIndexOp::InitThreadLocals()
+{
+  AssertIsOnBackgroundThread();
+
+  class MOZ_STACK_CLASS Helper final
+  {
+  public:
+    static void
+    Destroy(void* aThreadLocal)
+    {
+      delete static_cast<ThreadLocalJSRuntime*>(aThreadLocal);
+    }
+  };
+
+  if (sThreadLocalIndex == kBadThreadLocalIndex) {
+    if (NS_WARN_IF(PR_SUCCESS !=
+                     PR_NewThreadPrivateIndex(&sThreadLocalIndex,
+                                              &Helper::Destroy))) {
+      return;
+    }
+  }
+
+  MOZ_ASSERT(sThreadLocalIndex != kBadThreadLocalIndex);
+}
+
 nsresult
 CreateIndexOp::InsertDataFromObjectStore(DatabaseConnection* aConnection)
 {
   MOZ_ASSERT(aConnection);
   aConnection->AssertIsOnConnectionThread();
   MOZ_ASSERT(!IndexedDatabaseManager::InLowDiskSpaceMode());
   MOZ_ASSERT(mMaybeUniqueIndexTable);
 
   PROFILER_LABEL("IndexedDB",
                  "CreateIndexOp::InsertDataFromObjectStore",
                  js::ProfileEntry::Category::STORAGE);
 
-  nsCOMPtr<mozIStorageConnection> storageConnection =
-    aConnection->GetStorageConnection();
-  MOZ_ASSERT(storageConnection);
+  DatabaseConnection::CachedStatement stmt;
+  nsresult rv = aConnection->GetCachedStatement(
+    "SELECT id, data, file_ids, key_value "
+    "FROM object_data "
+    "WHERE object_store_id = :osid",
+    &stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mObjectStoreId);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  bool hasResult;
+  rv = stmt->ExecuteStep(&hasResult);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!hasResult) {
+    // Bail early if we have no data to avoid creating the runtime below.
+    return NS_OK;
+  }
 
   ThreadLocalJSRuntime* runtime = ThreadLocalJSRuntime::GetOrCreate();
   if (NS_WARN_IF(!runtime)) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   JSContext* cx = runtime->Context();
   JSAutoRequest ar(cx);
   JSAutoCompartment ac(cx, runtime->Global());
 
-  nsRefPtr<UpdateIndexDataValuesFunction> updateFunction =
-    new UpdateIndexDataValuesFunction(this, aConnection, cx);
-
-  NS_NAMED_LITERAL_CSTRING(updateFunctionName, "update_index_data_values");
-
-  nsresult rv =
-    storageConnection->CreateFunction(updateFunctionName,
-                                      4,
-                                      updateFunction);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = InsertDataFromObjectStoreInternal(aConnection);
-
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-    storageConnection->RemoveFunction(updateFunctionName)));
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
-}
-
-nsresult
-CreateIndexOp::InsertDataFromObjectStoreInternal(
-                                                DatabaseConnection* aConnection)
-{
-  MOZ_ASSERT(aConnection);
-  aConnection->AssertIsOnConnectionThread();
-  MOZ_ASSERT(!IndexedDatabaseManager::InLowDiskSpaceMode());
-  MOZ_ASSERT(mMaybeUniqueIndexTable);
-
-  nsCOMPtr<mozIStorageConnection> storageConnection =
-    aConnection->GetStorageConnection();
-  MOZ_ASSERT(storageConnection);
-
-  DatabaseConnection::CachedStatement stmt;
-  nsresult rv = aConnection->GetCachedStatement(
-    "UPDATE object_data "
-      "SET index_data_values = update_index_data_values "
-        "(key, index_data_values, file_ids, data) "
-      "WHERE object_store_id = :object_store_id;",
-    &stmt);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
-                             mObjectStoreId);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = stmt->Execute();
+  do {
+    StructuredCloneReadInfo cloneInfo;
+    rv = GetStructuredCloneReadInfoFromStatement(stmt, 1, 2, mFileManager,
+                                                 &cloneInfo);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    JS::Rooted<JS::Value> clone(cx);
+    if (NS_WARN_IF(!IDBObjectStore::DeserializeIndexValue(cx, cloneInfo,
+                                                          &clone))) {
+      return NS_ERROR_DOM_DATA_CLONE_ERR;
+    }
+
+    nsTArray<IndexUpdateInfo> updateInfo;
+    rv = IDBObjectStore::AppendIndexUpdateInfo(mMetadata.id(),
+                                               mMetadata.keyPath(),
+                                               mMetadata.unique(),
+                                               mMetadata.multiEntry(),
+                                               cx,
+                                               clone,
+                                               updateInfo);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    int64_t objectDataId = stmt->AsInt64(0);
+
+    Key key;
+    rv = key.SetFromStatement(stmt, 3);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = UpdateIndexes(aConnection,
+                       mMaybeUniqueIndexTable.ref(),
+                       key,
+                       false,
+                       objectDataId,
+                       updateInfo);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  } while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&hasResult)) && hasResult);
+
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 bool
 CreateIndexOp::Init(TransactionBase* aTransaction)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aTransaction);
 
-  struct MOZ_STACK_CLASS Helper final
-  {
-    static void
-    Destroy(void* aThreadLocal)
-    {
-      delete static_cast<ThreadLocalJSRuntime*>(aThreadLocal);
-    }
-  };
-
-  if (sThreadLocalIndex == kBadThreadLocalIndex) {
-    if (NS_WARN_IF(PR_SUCCESS !=
-                     PR_NewThreadPrivateIndex(&sThreadLocalIndex,
-                                              &Helper::Destroy))) {
-      return false;
-    }
-  }
-
-  MOZ_ASSERT(sThreadLocalIndex != kBadThreadLocalIndex);
-
-  nsresult rv =
-    GetUniqueIndexTableForObjectStore(aTransaction,
-                                      mObjectStoreId,
-                                      mMaybeUniqueIndexTable);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
+  if (NS_WARN_IF(!mMaybeUniqueIndexTable) ||
+      NS_WARN_IF(sThreadLocalIndex == kBadThreadLocalIndex)) {
     return false;
   }
 
   return true;
 }
 
 nsresult
 CreateIndexOp::DoDatabaseWork(DatabaseConnection* aConnection)
@@ -19714,41 +17361,16 @@ CreateIndexOp::DoDatabaseWork(DatabaseCo
   PROFILER_LABEL("IndexedDB",
                  "CreateIndexOp::DoDatabaseWork",
                  js::ProfileEntry::Category::STORAGE);
 
   if (NS_WARN_IF(IndexedDatabaseManager::InLowDiskSpaceMode())) {
     return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
   }
 
-#ifdef DEBUG
-  {
-    // Make sure that we're not creating an index with the same name and object
-    // store as another that already exists. This should be impossible because
-    // we should have thrown an error long before now...
-    DatabaseConnection::CachedStatement stmt;
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-      aConnection->GetCachedStatement(
-        "SELECT name "
-          "FROM object_store_index "
-          "WHERE object_store_id = :osid "
-          "AND name = :name;",
-        &stmt)));
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-      stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mObjectStoreId)));
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-      stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mMetadata.name())));
-
-    bool hasResult;
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)));
-
-    MOZ_ASSERT(!hasResult);
-  }
-#endif
-
   DatabaseConnection::AutoSavepoint autoSave;
   nsresult rv = autoSave.Start(Transaction());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   DatabaseConnection::CachedStatement stmt;
   rv = aConnection->GetCachedStatement(
@@ -19892,652 +17514,50 @@ ThreadLocalJSRuntime::Init()
                                JS::FireOnNewGlobalHook);
   if (NS_WARN_IF(!mGlobal)) {
     return false;
   }
 
   return true;
 }
 
-NS_IMPL_ISUPPORTS(CreateIndexOp::UpdateIndexDataValuesFunction,
-                  mozIStorageFunction);
-
-NS_IMETHODIMP
-CreateIndexOp::
-UpdateIndexDataValuesFunction::OnFunctionCall(mozIStorageValueArray* aValues,
-                                              nsIVariant** _retval)
-{
-  MOZ_ASSERT(aValues);
-  MOZ_ASSERT(_retval);
-  MOZ_ASSERT(mConnection);
-  mConnection->AssertIsOnConnectionThread();
-  MOZ_ASSERT(mOp);
-  MOZ_ASSERT(mCx);
-
-  PROFILER_LABEL("IndexedDB",
-                 "CreateIndexOp::UpdateIndexDataValuesFunction::OnFunctionCall",
-                 js::ProfileEntry::Category::STORAGE);
-
-#ifdef DEBUG
-  {
-    uint32_t argCount;
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetNumEntries(&argCount)));
-    MOZ_ASSERT(argCount == 4); // key, index_data_values, file_ids, data
-
-    int32_t valueType;
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(0, &valueType)));
-    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(1, &valueType)));
-    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
-               valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(2, &valueType)));
-    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
-               valueType == mozIStorageValueArray::VALUE_TYPE_TEXT);
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(3, &valueType)));
-    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
-  }
-#endif
-
-  StructuredCloneReadInfo cloneInfo;
-  nsresult rv =
-    GetStructuredCloneReadInfoFromValueArray(aValues,
-                                             /* aDataIndex */ 3,
-                                             /* aFileIdsIndex */ 2,
-                                             mOp->mFileManager,
-                                             &cloneInfo);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  JS::Rooted<JS::Value> clone(mCx);
-  if (NS_WARN_IF(!IDBObjectStore::DeserializeIndexValue(mCx,
-                                                        cloneInfo,
-                                                        &clone))) {
-    return NS_ERROR_DOM_DATA_CLONE_ERR;
-  }
-
-  const IndexMetadata& metadata = mOp->mMetadata;
-  const int64_t& objectStoreId = mOp->mObjectStoreId;
-
-  nsAutoTArray<IndexUpdateInfo, 32> updateInfos;
-  rv = IDBObjectStore::AppendIndexUpdateInfo(metadata.id(),
-                                             metadata.keyPath(),
-                                             metadata.unique(),
-                                             metadata.multiEntry(),
-                                             mCx,
-                                             clone,
-                                             updateInfos);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  if (updateInfos.IsEmpty()) {
-    // XXX See if we can do this without copying...
-
-    nsCOMPtr<nsIVariant> unmodifiedValue;
-
-    // No changes needed, just return the original value.
-    int32_t valueType;
-    rv = aValues->GetTypeOfIndex(1, &valueType);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
-               valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
-
-    if (valueType == mozIStorageValueArray::VALUE_TYPE_NULL) {
-      unmodifiedValue = new storage::NullVariant();
-      unmodifiedValue.forget(_retval);
-      return NS_OK;
-    }
-
-    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
-
-    const uint8_t* blobData;
-    uint32_t blobDataLength;
-    rv = aValues->GetSharedBlob(1, &blobDataLength, &blobData);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    std::pair<uint8_t *, int> copiedBlobDataPair(
-      static_cast<uint8_t*>(moz_malloc(blobDataLength)),
-      blobDataLength);
-
-    if (!copiedBlobDataPair.first) {
-      IDB_REPORT_INTERNAL_ERR();
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    memcpy(copiedBlobDataPair.first, blobData, blobDataLength);
-
-    unmodifiedValue = new storage::AdoptedBlobVariant(copiedBlobDataPair);
-    unmodifiedValue.forget(_retval);
-
-    return NS_OK;
-  }
-
-  Key key;
-  rv = key.SetFromValueArray(aValues, 0);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  AutoFallibleTArray<IndexDataValue, 32> indexValues;
-  rv = ReadCompressedIndexDataValues(aValues, 1, indexValues);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  const bool hadPreviousIndexValues = !indexValues.IsEmpty();
-
-  const uint32_t updateInfoCount = updateInfos.Length();
-
-  if (NS_WARN_IF(!indexValues.SetCapacity(indexValues.Length() +
-                                          updateInfoCount))) {
-    IDB_REPORT_INTERNAL_ERR();
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  // First construct the full list to update the index_data_values row.
-  for (uint32_t index = 0; index < updateInfoCount; index++) {
-    const IndexUpdateInfo& info = updateInfos[index];
-
-    MOZ_ALWAYS_TRUE(
-      indexValues.InsertElementSorted(IndexDataValue(metadata.id(),
-                                                     metadata.unique(),
-                                                     info.value())));
-  }
-
-  UniqueMozFreePtr<uint8_t> indexValuesBlob;
-  uint32_t indexValuesBlobLength;
-  rv = MakeCompressedIndexDataValues(indexValues,
-                                     indexValuesBlob,
-                                     &indexValuesBlobLength);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  MOZ_ASSERT(!indexValuesBlobLength == !(indexValuesBlob.get()));
-
-  nsCOMPtr<nsIVariant> value;
-
-  if (!indexValuesBlob) {
-    value = new storage::NullVariant();
-
-    value.forget(_retval);
-    return NS_OK;
-  }
-
-  // Now insert the new table rows. We only need to construct a new list if
-  // the full list is different.
-  if (hadPreviousIndexValues) {
-    indexValues.ClearAndRetainStorage();
-
-    MOZ_ASSERT(indexValues.Capacity() >= updateInfoCount);
-
-    for (uint32_t index = 0; index < updateInfoCount; index++) {
-      const IndexUpdateInfo& info = updateInfos[index];
-
-      MOZ_ALWAYS_TRUE(
-        indexValues.InsertElementSorted(IndexDataValue(metadata.id(),
-                                                       metadata.unique(),
-                                                       info.value())));
-    }
-  }
-
-  rv = InsertIndexTableRows(mConnection, objectStoreId, key, indexValues);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  std::pair<uint8_t *, int> copiedBlobDataPair(indexValuesBlob.get(),
-                                               indexValuesBlobLength);
-
-  value = new storage::AdoptedBlobVariant(copiedBlobDataPair);
-
-  indexValuesBlob.release();
-
-  value.forget(_retval);
-  return NS_OK;
-}
-
-DeleteIndexOp::DeleteIndexOp(VersionChangeTransaction* aTransaction,
-                             const int64_t aObjectStoreId,
-                             const int64_t aIndexId,
-                             const bool aUnique,
-                             const bool aIsLastIndex)
-  : VersionChangeTransactionOp(aTransaction)
-  , mObjectStoreId(aObjectStoreId)
-  , mIndexId(aIndexId)
-  , mUnique(aUnique)
-  , mIsLastIndex(aIsLastIndex)
-{
-  MOZ_ASSERT(aObjectStoreId);
-  MOZ_ASSERT(aIndexId);
-}
-
-nsresult
-DeleteIndexOp::RemoveReferencesToIndex(
-                                   DatabaseConnection* aConnection,
-                                   const Key& aObjectStoreKey,
-                                   FallibleTArray<IndexDataValue>& aIndexValues)
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-  MOZ_ASSERT(!IsOnBackgroundThread());
-  MOZ_ASSERT(aConnection);
-  MOZ_ASSERT(!aObjectStoreKey.IsUnset());
-  MOZ_ASSERT_IF(!mIsLastIndex, !aIndexValues.IsEmpty());
-
-  struct MOZ_STACK_CLASS IndexIdComparator final
-  {
-    bool
-    Equals(const IndexDataValue& aA, const IndexDataValue& aB) const
-    {
-      // Ignore everything but the index id.
-      return aA.mIndexId == aB.mIndexId;
-    };
-
-    bool
-    LessThan(const IndexDataValue& aA, const IndexDataValue& aB) const
-    {
-      return aA.mIndexId < aB.mIndexId;
-    };
-  };
-
-  PROFILER_LABEL("IndexedDB",
-                 "DeleteIndexOp::RemoveReferencesToIndex",
-                 js::ProfileEntry::Category::STORAGE);
-
-  if (mIsLastIndex) {
-    // There is no need to parse the previous entry in the index_data_values
-    // column if this is the last index. Simply set it to NULL.
-    DatabaseConnection::CachedStatement stmt;
-    nsresult rv = aConnection->GetCachedStatement(
-      "UPDATE object_data "
-        "SET index_data_values = NULL "
-        "WHERE object_store_id = :object_store_id "
-        "AND key = :key;",
-      &stmt);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
-                               mObjectStoreId);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = aObjectStoreKey.BindToStatement(stmt, NS_LITERAL_CSTRING("key"));
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = stmt->Execute();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    return NS_OK;
-  }
-
-  IndexDataValue search;
-  search.mIndexId = mIndexId;
-
-  // This returns the first element that matches our index id found during a
-  // binary search. However, there could still be other elements before that.
-  size_t firstElementIndex =
-    aIndexValues.BinaryIndexOf(search, IndexIdComparator());
-  if (NS_WARN_IF(firstElementIndex == aIndexValues.NoIndex) ||
-      NS_WARN_IF(aIndexValues[firstElementIndex].mIndexId != mIndexId)) {
-    IDB_REPORT_INTERNAL_ERR();
-    return NS_ERROR_FILE_CORRUPTED;
-  }
-
-  MOZ_ASSERT(aIndexValues[firstElementIndex].mIndexId == mIndexId);
-
-  // Walk backwards to find the real first index.
-  while (firstElementIndex) {
-    if (aIndexValues[firstElementIndex - 1].mIndexId == mIndexId) {
-      firstElementIndex--;
-    } else {
-      break;
-    }
-  }
-
-  MOZ_ASSERT(aIndexValues[firstElementIndex].mIndexId == mIndexId);
-
-  const size_t indexValuesLength = aIndexValues.Length();
-
-  // Find the last element with the same index id.
-  size_t lastElementIndex = firstElementIndex;
-
-  while (lastElementIndex < indexValuesLength) {
-    if (aIndexValues[lastElementIndex].mIndexId == mIndexId) {
-      lastElementIndex++;
-    } else {
-      break;
-    }
-  }
-
-  MOZ_ASSERT(lastElementIndex > firstElementIndex);
-  MOZ_ASSERT_IF(lastElementIndex < indexValuesLength,
-                aIndexValues[lastElementIndex].mIndexId != mIndexId);
-  MOZ_ASSERT(aIndexValues[lastElementIndex - 1].mIndexId == mIndexId);
-
-  aIndexValues.RemoveElementsAt(firstElementIndex,
-                                lastElementIndex - firstElementIndex);
-
-  nsresult rv = UpdateIndexValues(aConnection,
-                                  mObjectStoreId,
-                                  aObjectStoreKey,
-                                  aIndexValues);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
-}
-
 nsresult
 DeleteIndexOp::DoDatabaseWork(DatabaseConnection* aConnection)
 {
   MOZ_ASSERT(aConnection);
   aConnection->AssertIsOnConnectionThread();
 
-#ifdef DEBUG
-  {
-    // Make sure |mIsLastIndex| is telling the truth.
-    DatabaseConnection::CachedStatement stmt;
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-      aConnection->GetCachedStatement(
-        "SELECT id "
-          "FROM object_store_index "
-          "WHERE object_store_id = :object_store_id;",
-        &stmt)));
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-      stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
-                            mObjectStoreId)));
-
-    bool foundThisIndex = false;
-    bool foundOtherIndex = false;
-
-    while (true) {
-      bool hasResult;
-      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)));
-
-      if (!hasResult) {
-        break;
-      }
-
-      int64_t id;
-      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(stmt->GetInt64(0, &id)));
-
-      if (id == mIndexId) {
-        foundThisIndex = true;
-      } else {
-        foundOtherIndex = true;
-      }
-    }
-
-    MOZ_ASSERT_IF(mIsLastIndex, foundThisIndex && !foundOtherIndex);
-    MOZ_ASSERT_IF(!mIsLastIndex, foundThisIndex && foundOtherIndex);
-  }
-#endif
-
   PROFILER_LABEL("IndexedDB",
                  "DeleteIndexOp::DoDatabaseWork",
                  js::ProfileEntry::Category::STORAGE);
 
   DatabaseConnection::AutoSavepoint autoSave;
   nsresult rv = autoSave.Start(Transaction());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  DatabaseConnection::CachedStatement selectStmt;
-
-  // mozStorage warns that these statements trigger a sort operation but we
-  // don't care because this is a very rare call and we expect it to be slow.
-  // The cost of having an index on this field is too high.
-  if (mUnique) {
-    if (mIsLastIndex) {
-      rv = aConnection->GetCachedStatement(
-        "/* do not warn (bug someone else) */ "
-        "SELECT value, object_data_key "
-          "FROM unique_index_data "
-          "WHERE index_id = :index_id "
-          "ORDER BY object_data_key ASC;",
-        &selectStmt);
-    } else {
-      rv = aConnection->GetCachedStatement(
-        "/* do not warn (bug out) */ "
-        "SELECT unique_index_data.value, "
-               "unique_index_data.object_data_key, "
-               "object_data.index_data_values "
-          "FROM unique_index_data "
-          "JOIN object_data "
-          "ON unique_index_data.object_data_key = object_data.key "
-          "WHERE unique_index_data.index_id = :index_id "
-          "AND object_data.object_store_id = :object_store_id "
-          "ORDER BY unique_index_data.object_data_key ASC;",
-        &selectStmt);
-    }
-  } else {
-    if (mIsLastIndex) {
-      rv = aConnection->GetCachedStatement(
-        "/* do not warn (bug me not) */ "
-        "SELECT value, object_data_key "
-          "FROM index_data "
-          "WHERE index_id = :index_id "
-          "AND object_store_id = :object_store_id "
-          "ORDER BY object_data_key ASC;",
-        &selectStmt);
-    } else {
-      rv = aConnection->GetCachedStatement(
-        "/* do not warn (bug off) */ "
-        "SELECT index_data.value, "
-               "index_data.object_data_key, "
-               "object_data.index_data_values "
-          "FROM index_data "
-          "JOIN object_data "
-          "ON index_data.object_data_key = object_data.key "
-          "WHERE index_data.index_id = :index_id "
-          "AND object_data.object_store_id = :object_store_id "
-          "ORDER BY index_data.object_data_key ASC;",
-        &selectStmt);
-    }
-  }
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  NS_NAMED_LITERAL_CSTRING(indexIdString, "index_id");
-
-  rv = selectStmt->BindInt64ByName(indexIdString, mIndexId);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  if (!mUnique || !mIsLastIndex) {
-    rv = selectStmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
-                                     mObjectStoreId);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
-  NS_NAMED_LITERAL_CSTRING(valueString, "value");
-  NS_NAMED_LITERAL_CSTRING(objectDataKeyString, "object_data_key");
-
-  DatabaseConnection::CachedStatement deleteIndexRowStmt;
-  DatabaseConnection::CachedStatement nullIndexDataValuesStmt;
-
-  Key lastObjectStoreKey;
-  AutoFallibleTArray<IndexDataValue, 32> lastIndexValues;
-
-  bool hasResult;
-  while (NS_SUCCEEDED(rv = selectStmt->ExecuteStep(&hasResult)) && hasResult) {
-    // We always need the index key to delete the index row.
-    Key indexKey;
-    rv = indexKey.SetFromStatement(selectStmt, 0);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    if (NS_WARN_IF(indexKey.IsUnset())) {
-      IDB_REPORT_INTERNAL_ERR();
-      return NS_ERROR_FILE_CORRUPTED;
-    }
-
-    // Don't call |lastObjectStoreKey.BindToStatement()| directly because we
-    // don't want to copy the same key multiple times.
-    const uint8_t* objectStoreKeyData;
-    uint32_t objectStoreKeyDataLength;
-    rv = selectStmt->GetSharedBlob(1,
-                                   &objectStoreKeyDataLength,
-                                   &objectStoreKeyData);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    if (NS_WARN_IF(!objectStoreKeyDataLength)) {
-      IDB_REPORT_INTERNAL_ERR();
-      return NS_ERROR_FILE_CORRUPTED;
-    }
-
-    nsDependentCString currentObjectStoreKeyBuffer(
-      reinterpret_cast<const char*>(objectStoreKeyData),
-      objectStoreKeyDataLength);
-    if (currentObjectStoreKeyBuffer != lastObjectStoreKey.GetBuffer()) {
-      // We just walked to the next object store key.
-      if (!lastObjectStoreKey.IsUnset()) {
-        // Before we move on to the next key we need to update the previous
-        // key's index_data_values column.
-        rv = RemoveReferencesToIndex(aConnection,
-                                      lastObjectStoreKey,
-                                      lastIndexValues);
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          return rv;
-        }
-      }
-
-      // Save the object store key.
-      lastObjectStoreKey = Key(currentObjectStoreKeyBuffer);
-
-      // And the |index_data_values| row if this isn't the only index.
-      if (!mIsLastIndex) {
-        lastIndexValues.ClearAndRetainStorage();
-        rv = ReadCompressedIndexDataValues(selectStmt, 2, lastIndexValues);
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          return rv;
-        }
-
-        if (NS_WARN_IF(lastIndexValues.IsEmpty())) {
-          IDB_REPORT_INTERNAL_ERR();
-          return NS_ERROR_FILE_CORRUPTED;
-        }
-      }
-    }
-
-    // Now delete the index row.
-    if (deleteIndexRowStmt) {
-        MOZ_ALWAYS_TRUE(NS_SUCCEEDED(deleteIndexRowStmt->Reset()));
-    } else {
-      if (mUnique) {
-        rv = aConnection->GetCachedStatement(
-          "DELETE FROM unique_index_data "
-            "WHERE index_id = :index_id "
-            "AND value = :value;",
-          &deleteIndexRowStmt);
-      } else {
-        rv = aConnection->GetCachedStatement(
-          "DELETE FROM index_data "
-            "WHERE index_id = :index_id "
-            "AND value = :value "
-            "AND object_data_key = :object_data_key;",
-          &deleteIndexRowStmt);
-      }
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-    }
-
-    rv = deleteIndexRowStmt->BindInt64ByName(indexIdString, mIndexId);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = indexKey.BindToStatement(deleteIndexRowStmt, valueString);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    if (!mUnique) {
-      rv = lastObjectStoreKey.BindToStatement(deleteIndexRowStmt,
-                                              objectDataKeyString);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-    }
-
-    rv = deleteIndexRowStmt->Execute();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Take care of the last key.
-  if (!lastObjectStoreKey.IsUnset()) {
-    MOZ_ASSERT_IF(!mIsLastIndex, !lastIndexValues.IsEmpty());
-
-    rv = RemoveReferencesToIndex(aConnection,
-                                 lastObjectStoreKey,
-                                 lastIndexValues);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
-  DatabaseConnection::CachedStatement deleteStmt;
+  DatabaseConnection::CachedStatement stmt;
   rv = aConnection->GetCachedStatement(
     "DELETE FROM object_store_index "
-      "WHERE id = :index_id;",
-    &deleteStmt);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = deleteStmt->BindInt64ByName(indexIdString, mIndexId);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = deleteStmt->Execute();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-#ifdef DEBUG
-  {
-    int32_t deletedRowCount;
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-      aConnection->GetStorageConnection()->GetAffectedRows(&deletedRowCount)));
-    MOZ_ASSERT(deletedRowCount == 1);
-  }
-#endif
+    "WHERE id = :id ",
+    &stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mIndexId);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   rv = autoSave.Commit();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
@@ -20613,82 +17633,23 @@ ObjectStoreAddOrPutRequestOp::ObjectStor
   : NormalTransactionOp(aTransaction)
   , mParams(aParams.type() == RequestParams::TObjectStoreAddParams ?
               aParams.get_ObjectStoreAddParams().commonParams() :
               aParams.get_ObjectStorePutParams().commonParams())
   , mGroup(aTransaction->GetDatabase()->Group())
   , mOrigin(aTransaction->GetDatabase()->Origin())
   , mPersistenceType(aTransaction->GetDatabase()->Type())
   , mOverwrite(aParams.type() == RequestParams::TObjectStorePutParams)
-  , mObjectStoreHasIndexes(false)
 {
   MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreAddParams ||
              aParams.type() == RequestParams::TObjectStorePutParams);
 
   mMetadata =
     aTransaction->GetMetadataForObjectStoreId(mParams.objectStoreId());
   MOZ_ASSERT(mMetadata);
-
-  const_cast<bool&>(mObjectStoreHasIndexes) = mMetadata->HasLiveIndexes();
-}
-
-nsresult
-ObjectStoreAddOrPutRequestOp::RemoveOldIndexDataValues(
-                                                DatabaseConnection* aConnection)
-{
-  AssertIsOnConnectionThread();
-  MOZ_ASSERT(aConnection);
-  MOZ_ASSERT(mOverwrite);
-  MOZ_ASSERT(!mResponse.IsUnset());
-  MOZ_ASSERT(mObjectStoreHasIndexes);
-
-  DatabaseConnection::CachedStatement indexValuesStmt;
-  nsresult rv = aConnection->GetCachedStatement(
-    "SELECT index_data_values "
-      "FROM object_data "
-      "WHERE object_store_id = :object_store_id "
-      "AND key = :key;",
-    &indexValuesStmt);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = indexValuesStmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
-                                        mParams.objectStoreId());
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = mResponse.BindToStatement(indexValuesStmt, NS_LITERAL_CSTRING("key"));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  bool hasResult;
-  rv = indexValuesStmt->ExecuteStep(&hasResult);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  if (hasResult) {
-    AutoFallibleTArray<IndexDataValue, 32> existingIndexValues;
-    rv = ReadCompressedIndexDataValues(indexValuesStmt,
-                                        0,
-                                        existingIndexValues);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = DeleteIndexDataTableRows(aConnection, mResponse, existingIndexValues);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
-  return NS_OK;
 }
 
 nsresult
 ObjectStoreAddOrPutRequestOp::CopyFileData(nsIInputStream* aInputStream,
                                            nsIOutputStream* aOutputStream)
 {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(aInputStream);
@@ -20772,21 +17733,22 @@ ObjectStoreAddOrPutRequestOp::Init(Trans
       MOZ_ASSERT_IF(!indexMetadata->mCommonMetadata.multiEntry(),
                     !mUniqueIndexTable.ref().Get(indexId));
 
       if (NS_WARN_IF(!mUniqueIndexTable.ref().Put(indexId, unique, fallible))) {
         return false;
       }
     }
   } else if (mOverwrite) {
+    // Kinda lame...
     mUniqueIndexTable.emplace();
   }
 
 #ifdef DEBUG
-  if (mUniqueIndexTable.isSome()) {
+  if (mUniqueIndexTable) {
     mUniqueIndexTable.ref().MarkImmutable();
   }
 #endif
 
   const nsTArray<DatabaseFileOrMutableFileId>& files = mParams.files();
 
   if (!files.IsEmpty()) {
     const uint32_t count = files.Length();
@@ -20845,18 +17807,16 @@ ObjectStoreAddOrPutRequestOp::Init(Trans
 
 nsresult
 ObjectStoreAddOrPutRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
 {
   MOZ_ASSERT(aConnection);
   aConnection->AssertIsOnConnectionThread();
   MOZ_ASSERT(aConnection->GetStorageConnection());
   MOZ_ASSERT_IF(mFileManager, !mStoredFileInfos.IsEmpty());
-  MOZ_ASSERT(mObjectStoreHasIndexes ==
-               ObjectStoreHasIndexes(aConnection, mParams.objectStoreId()));
 
   PROFILER_LABEL("IndexedDB",
                  "ObjectStoreAddOrPutRequestOp::DoDatabaseWork",
                  js::ProfileEntry::Category::STORAGE);
 
   if (NS_WARN_IF(IndexedDatabaseManager::InLowDiskSpaceMode())) {
     return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
   }
@@ -20870,41 +17830,31 @@ ObjectStoreAddOrPutRequestOp::DoDatabase
   // This will be the final key we use.
   Key& key = mResponse;
   key = mParams.key();
 
   const bool keyUnset = key.IsUnset();
   const int64_t osid = mParams.objectStoreId();
   const KeyPath& keyPath = mMetadata->mCommonMetadata.keyPath();
 
-  // First delete old index_data_values if we're overwriting something and we
-  // have indexes.
-  if (mOverwrite && !keyUnset && mObjectStoreHasIndexes) {
-    rv = RemoveOldIndexDataValues(aConnection);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
   // The "|| keyUnset" here is mostly a debugging tool. If a key isn't
   // specified we should never have a collision and so it shouldn't matter
   // if we allow overwrite or not. By not allowing overwrite we raise
   // detectable errors rather than corrupting data.
   DatabaseConnection::CachedStatement stmt;
   if (!mOverwrite || keyUnset) {
     rv = aConnection->GetCachedStatement(
-      "INSERT INTO object_data "
-        "(object_store_id, key, file_ids, data) "
-        "VALUES (:osid, :key, :file_ids, :data);",
+      "INSERT INTO object_data (object_store_id, key_value, data, file_ids) "
+      "VALUES (:osid, :key_value, :data, :file_ids)",
       &stmt);
   } else {
     rv = aConnection->GetCachedStatement(
-      "INSERT OR REPLACE INTO object_data "
-        "(object_store_id, key, file_ids, data) "
-        "VALUES (:osid, :key, :file_ids, :data);",
+      "INSERT OR REPLACE INTO object_data (object_store_id, key_value, data, "
+                                          "file_ids) "
+      "VALUES (:osid, :key_value, :data, :file_ids)",
     &stmt);
   }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), osid);
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -20948,17 +17898,17 @@ ObjectStoreAddOrPutRequestOp::DoDatabase
                              cloneInfo.offsetToKeyProp());
       uint64_t keyPropValue =
         ReinterpretDoubleAsUInt64(static_cast<double>(autoIncrementNum));
 
       LittleEndian::writeUint64(keyPropPointer, keyPropValue);
     }
   }
 
-  key.BindToStatement(stmt, NS_LITERAL_CSTRING("key"));
+  key.BindToStatement(stmt, NS_LITERAL_CSTRING("key_value"));
 
   // Compress the bytes before adding into the database.
   const char* uncompressed =
     reinterpret_cast<const char*>(mParams.cloneInfo().data().Elements());
   size_t uncompressedLength = mParams.cloneInfo().data().Length();
 
   // We don't have a smart pointer class that calls moz_free, so we need to
   // manage | compressed | manually.
@@ -21175,35 +18125,32 @@ ObjectStoreAddOrPutRequestOp::DoDatabase
     MOZ_ASSERT(!keyUnset, "Generated key had a collision!");
     return rv;
   }
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  int64_t objectDataId;
+  rv = aConnection->GetStorageConnection()->GetLastInsertRowID(&objectDataId);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   // Update our indexes if needed.
-  if (!mParams.indexUpdateInfos().IsEmpty()) {
-    MOZ_ASSERT(mUniqueIndexTable.isSome());
-
-    // Write the index_data_values column.
-    AutoFallibleTArray<IndexDataValue, 32> indexValues;
-    rv = IndexDataValuesFromUpdateInfos(mParams.indexUpdateInfos(),
-                                        mUniqueIndexTable.ref(),
-                                        indexValues);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = UpdateIndexValues(aConnection, osid, key, indexValues);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = InsertIndexTableRows(aConnection, osid, key, indexValues);
+  if (mOverwrite || !mParams.indexUpdateInfos().IsEmpty()) {
+    MOZ_ASSERT(mUniqueIndexTable);
+
+    rv = UpdateIndexes(aConnection,
+                       mUniqueIndexTable.ref(),
+                       key,
+                       mOverwrite,
+                       objectDataId,
+                       mParams.indexUpdateInfos());
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
 
   rv = autoSave.Commit();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -21327,32 +18274,32 @@ ObjectStoreGetRequestOp::DoDatabaseWork(
                  js::ProfileEntry::Category::STORAGE);
 
   const bool hasKeyRange =
     mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
 
   nsAutoCString keyRangeClause;
   if (hasKeyRange) {
     GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
-                                NS_LITERAL_CSTRING("key"),
+                                NS_LITERAL_CSTRING("key_value"),
                                 keyRangeClause);
   }
 
   nsCString limitClause;
   if (mLimit) {
     limitClause.AssignLiteral(" LIMIT ");
     limitClause.AppendInt(mLimit);
   }
 
   nsCString query =
-    NS_LITERAL_CSTRING("SELECT file_ids, data "
+    NS_LITERAL_CSTRING("SELECT data, file_ids "
                        "FROM object_data "
                        "WHERE object_store_id = :osid") +
     keyRangeClause +
-    NS_LITERAL_CSTRING(" ORDER BY key ASC") +
+    NS_LITERAL_CSTRING(" ORDER BY key_value ASC") +
     limitClause;
 
   DatabaseConnection::CachedStatement stmt;
   nsresult rv = aConnection->GetCachedStatement(query, &stmt);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
@@ -21371,17 +18318,17 @@ ObjectStoreGetRequestOp::DoDatabaseWork(
 
   bool hasResult;
   while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
     StructuredCloneReadInfo* cloneInfo = mResponse.AppendElement();
     if (NS_WARN_IF(!cloneInfo)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
-    rv = GetStructuredCloneReadInfoFromStatement(stmt, 1, 0, mFileManager,
+    rv = GetStructuredCloneReadInfoFromStatement(stmt, 0, 1, mFileManager,
                                                  cloneInfo);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -21451,32 +18398,32 @@ ObjectStoreGetAllKeysRequestOp::DoDataba
 
   const bool hasKeyRange =
     mParams.optionalKeyRange().type() == OptionalKeyRange::TSerializedKeyRange;
 
   nsAutoCString keyRangeClause;
   if (hasKeyRange) {
     GetBindingClauseForKeyRange(
       mParams.optionalKeyRange().get_SerializedKeyRange(),
-      NS_LITERAL_CSTRING("key"),
+      NS_LITERAL_CSTRING("key_value"),
       keyRangeClause);
   }
 
   nsAutoCString limitClause;
   if (uint32_t limit = mParams.limit()) {
     limitClause.AssignLiteral(" LIMIT ");
     limitClause.AppendInt(limit);
   }
 
   nsCString query =
-    NS_LITERAL_CSTRING("SELECT key "
+    NS_LITERAL_CSTRING("SELECT key_value "
                        "FROM object_data "
                        "WHERE object_store_id = :osid") +
     keyRangeClause +
-    NS_LITERAL_CSTRING(" ORDER BY key ASC") +
+    NS_LITERAL_CSTRING(" ORDER BY key_value ASC") +
     limitClause;
 
   DatabaseConnection::CachedStatement stmt;
   nsresult rv = aConnection->GetCachedStatement(query, &stmt);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
@@ -21522,163 +18469,106 @@ ObjectStoreGetAllKeysRequestOp::GetRespo
 
   if (!mResponse.IsEmpty()) {
     nsTArray<Key>& response =
       aResponse.get_ObjectStoreGetAllKeysResponse().keys();
     mResponse.SwapElements(response);
   }
 }
 
-ObjectStoreDeleteRequestOp::ObjectStoreDeleteRequestOp(
-                                         TransactionBase* aTransaction,
-                                         const ObjectStoreDeleteParams& aParams)
-  : NormalTransactionOp(aTransaction)
-  , mParams(aParams)
-  , mObjectStoreHasIndexes(false)
-{
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(aTransaction);
-
-  nsRefPtr<FullObjectStoreMetadata> metadata =
-    aTransaction->GetMetadataForObjectStoreId(mParams.objectStoreId());
-  MOZ_ASSERT(metadata);
-
-  const_cast<bool&>(mObjectStoreHasIndexes) = metadata->HasLiveIndexes();
-}
-
 nsresult
 ObjectStoreDeleteRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
 {
   MOZ_ASSERT(aConnection);
   aConnection->AssertIsOnConnectionThread();
-  MOZ_ASSERT(mObjectStoreHasIndexes ==
-               ObjectStoreHasIndexes(aConnection, mParams.objectStoreId()));
 
   PROFILER_LABEL("IndexedDB",
                  "ObjectStoreDeleteRequestOp::DoDatabaseWork",
                  js::ProfileEntry::Category::STORAGE);
 
   DatabaseConnection::AutoSavepoint autoSave;
   nsresult rv = autoSave.Start(Transaction());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  if (mObjectStoreHasIndexes) {
-    rv = DeleteObjectStoreDataTableRowsWithIndexes(aConnection,
-                                                   mParams.objectStoreId(),
-                                                   mParams.keyRange());
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  } else {
-    NS_NAMED_LITERAL_CSTRING(objectStoreIdString, "object_store_id");
-
-    nsAutoCString keyRangeClause;
-    GetBindingClauseForKeyRange(mParams.keyRange(),
-                                NS_LITERAL_CSTRING("key"),
-                                keyRangeClause);
-
-    DatabaseConnection::CachedStatement stmt;
-    rv = aConnection->GetCachedStatement(
-      NS_LITERAL_CSTRING("DELETE FROM object_data "
-                           "WHERE object_store_id = :") + objectStoreIdString +
-      keyRangeClause +
-      NS_LITERAL_CSTRING(";"),
-      &stmt);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = stmt->BindInt64ByName(objectStoreIdString, mParams.objectStoreId());
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = BindKeyRangeToStatement(mParams.keyRange(), stmt);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = stmt->Execute();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
+  nsAutoCString keyRangeClause;
+  GetBindingClauseForKeyRange(mParams.keyRange(),
+                              NS_LITERAL_CSTRING("key_value"),
+                              keyRangeClause);
+
+  nsCString query =
+    NS_LITERAL_CSTRING("DELETE FROM object_data "
+                       "WHERE object_store_id = :osid") +
+    keyRangeClause;
+
+  DatabaseConnection::CachedStatement stmt;
+  rv = aConnection->GetCachedStatement(query, &stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"),
+                             mParams.objectStoreId());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = BindKeyRangeToStatement(mParams.keyRange(), stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
 
   rv = autoSave.Commit();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
-ObjectStoreClearRequestOp::ObjectStoreClearRequestOp(
-                                          TransactionBase* aTransaction,
-                                          const ObjectStoreClearParams& aParams)
-  : NormalTransactionOp(aTransaction)
-  , mParams(aParams)
-  , mObjectStoreHasIndexes(false)
-{
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(aTransaction);
-
-  nsRefPtr<FullObjectStoreMetadata> metadata =
-    aTransaction->GetMetadataForObjectStoreId(mParams.objectStoreId());
-  MOZ_ASSERT(metadata);
-
-  const_cast<bool&>(mObjectStoreHasIndexes) = metadata->HasLiveIndexes();
-}
-
 nsresult
 ObjectStoreClearRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
 {
   MOZ_ASSERT(aConnection);
   aConnection->AssertIsOnConnectionThread();
-  MOZ_ASSERT(mObjectStoreHasIndexes ==
-               ObjectStoreHasIndexes(aConnection, mParams.objectStoreId()));
 
   PROFILER_LABEL("IndexedDB",
                  "ObjectStoreClearRequestOp::DoDatabaseWork",
                  js::ProfileEntry::Category::STORAGE);
 
   DatabaseConnection::AutoSavepoint autoSave;
   nsresult rv = autoSave.Start(Transaction());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  if (mObjectStoreHasIndexes) {
-    rv = DeleteObjectStoreDataTableRowsWithIndexes(aConnection,
-                                                   mParams.objectStoreId(),
-                                                   void_t());
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  } else {
-    DatabaseConnection::CachedStatement stmt;
-    rv = aConnection->GetCachedStatement(
-      "DELETE FROM object_data "
-        "WHERE object_store_id = :object_store_id;",
-      &stmt);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
-                               mParams.objectStoreId());
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = stmt->Execute();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
+  DatabaseConnection::CachedStatement stmt;
+  rv = aConnection->GetCachedStatement(
+    "DELETE FROM object_data "
+    "WHERE object_store_id = :osid",
+    &stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"),
+                             mParams.objectStoreId());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
 
   rv = autoSave.Commit();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
@@ -21696,17 +18586,17 @@ ObjectStoreCountRequestOp::DoDatabaseWor
 
   const bool hasKeyRange =
     mParams.optionalKeyRange().type() == OptionalKeyRange::TSerializedKeyRange;
 
   nsAutoCString keyRangeClause;
   if (hasKeyRange) {
     GetBindingClauseForKeyRange(
       mParams.optionalKeyRange().get_SerializedKeyRange(),
-      NS_LITERAL_CSTRING("key"),
+      NS_LITERAL_CSTRING("key_value"),
       keyRangeClause);
   }
 
   nsCString query =
     NS_LITERAL_CSTRING("SELECT count(*) "
                        "FROM object_data "
                        "WHERE object_store_id = :osid") +
     keyRangeClause;
@@ -21879,25 +18769,22 @@ IndexGetRequestOp::DoDatabaseWork(Databa
 
   nsCString limitClause;
   if (mLimit) {
     limitClause.AssignLiteral(" LIMIT ");
     limitClause.AppendInt(mLimit);
   }
 
   nsCString query =
-    NS_LITERAL_CSTRING("SELECT file_ids, data "
+    NS_LITERAL_CSTRING("SELECT data, file_ids "
                        "FROM object_data "
                        "INNER JOIN ") +
     indexTable +
     NS_LITERAL_CSTRING("AS index_table "
-                       "ON object_data.object_store_id = "
-                         "index_table.object_store_id "
-                       "AND object_data.key = "
-                         "index_table.object_data_key "
+                       "ON object_data.id = index_table.object_data_id "
                        "WHERE index_id = :index_id") +
     keyRangeClause +
     limitClause;
 
   DatabaseConnection::CachedStatement stmt;
   nsresult rv = aConnection->GetCachedStatement(query, &stmt);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -21919,17 +18806,17 @@ IndexGetRequestOp::DoDatabaseWork(Databa
 
   bool hasResult;
   while(NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
     StructuredCloneReadInfo* cloneInfo = mResponse.AppendElement();
     if (NS_WARN_IF(!cloneInfo)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
-    rv = GetStructuredCloneReadInfoFromStatement(stmt, 1, 0, mFileManager,
+    rv = GetStructuredCloneReadInfoFromStatement(stmt, 0, 1, mFileManager,
                                                  cloneInfo);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -22310,36 +19197,36 @@ OpenOp::DoObjectStoreDatabaseWork(Databa
 
   PROFILER_LABEL("IndexedDB",
                  "Cursor::OpenOp::DoObjectStoreDatabaseWork",
                  js::ProfileEntry::Category::STORAGE);
 
   const bool usingKeyRange =
     mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
 
-  NS_NAMED_LITERAL_CSTRING(keyString, "key");
+  NS_NAMED_LITERAL_CSTRING(keyValue, "key_value");
   NS_NAMED_LITERAL_CSTRING(id, "id");
   NS_NAMED_LITERAL_CSTRING(openLimit, " LIMIT ");
 
   nsCString queryStart =
     NS_LITERAL_CSTRING("SELECT ") +
-    keyString +
-    NS_LITERAL_CSTRING(", file_ids, data "
+    keyValue +
+    NS_LITERAL_CSTRING(", data, file_ids "
                        "FROM object_data "
                        "WHERE object_store_id = :") +
     id;
 
   nsAutoCString keyRangeClause;
   if (usingKeyRange) {
     GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
-                                keyString,
+                                keyValue,
                                 keyRangeClause);
   }
 
-  nsAutoCString directionClause = NS_LITERAL_CSTRING(" ORDER BY ") + keyString;
+  nsAutoCString directionClause = NS_LITERAL_CSTRING(" ORDER BY ") + keyValue;
   switch (mCursor->mDirection) {
     case IDBCursor::NEXT:
     case IDBCursor::NEXT_UNIQUE:
       directionClause.AppendLiteral(" ASC");
       break;
 
     case IDBCursor::PREV:
     case IDBCursor::PREV_UNIQUE:
@@ -22389,18 +19276,18 @@ OpenOp::DoObjectStoreDatabaseWork(Databa
 
   rv = mCursor->mKey.SetFromStatement(stmt, 0);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   StructuredCloneReadInfo cloneInfo;
   rv = GetStructuredCloneReadInfoFromStatement(stmt,
+                                               1,
                                                2,
-                                               1,
                                                mCursor->mFileManager,
                                                &cloneInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Now we need to make the query to get the next match.
   keyRangeClause.Truncate();
@@ -22410,41 +19297,40 @@ OpenOp::DoObjectStoreDatabaseWork(Databa
   NS_NAMED_LITERAL_CSTRING(rangeKey, "range_key");
 
   switch (mCursor->mDirection) {
     case IDBCursor::NEXT:
     case IDBCursor::NEXT_UNIQUE: {
       Key upper;
       bool open;
       GetRangeKeyInfo(false, &upper, &open);
-      AppendConditionClause(keyString, currentKey, false, false,
+      AppendConditionClause(keyValue, currentKey, false, false,
                             keyRangeClause);
-      AppendConditionClause(keyString, currentKey, false, true,
+      AppendConditionClause(keyValue, currentKey, false, true,
                             continueToKeyRangeClause);
       if (usingKeyRange && !upper.IsUnset()) {
-        AppendConditionClause(keyString, rangeKey, true, !open, keyRangeClause);
-        AppendConditionClause(keyString, rangeKey, true, !open,
+        AppendConditionClause(keyValue, rangeKey, true, !open, keyRangeClause);
+        AppendConditionClause(keyValue, rangeKey, true, !open,
                               continueToKeyRangeClause);
         mCursor->mRangeKey = upper;
       }
       break;
     }
 
     case IDBCursor::PREV:
     case IDBCursor::PREV_UNIQUE: {
       Key lower;
       bool open;
       GetRangeKeyInfo(true, &lower, &open);
-      AppendConditionClause(keyString, currentKey, true, false, keyRangeClause);
-      AppendConditionClause(keyString, currentKey, true, true,
+      AppendConditionClause(keyValue, currentKey, true, false, keyRangeClause);
+      AppendConditionClause(keyValue, currentKey, true, true,
                            continueToKeyRangeClause);
       if (usingKeyRange && !lower.IsUnset()) {
-        AppendConditionClause(keyString, rangeKey, false, !open,
-                              keyRangeClause);
-        AppendConditionClause(keyString, rangeKey, false, !open,
+        AppendConditionClause(keyValue, rangeKey, false, !open, keyRangeClause);
+        AppendConditionClause(keyValue, rangeKey, false, !open,
                               continueToKeyRangeClause);
         mCursor->mRangeKey = lower;
       }
       break;
     }
 
     default:
       MOZ_CRASH("Should never get here!");
@@ -22486,35 +19372,35 @@ OpenOp::DoObjectStoreKeyDatabaseWork(Dat
 
   PROFILER_LABEL("IndexedDB",
                  "Cursor::OpenOp::DoObjectStoreKeyDatabaseWork",
                  js::ProfileEntry::Category::STORAGE);
 
   const bool usingKeyRange =
     mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
 
-  NS_NAMED_LITERAL_CSTRING(keyString, "key");
+  NS_NAMED_LITERAL_CSTRING(keyValue, "key_value");
   NS_NAMED_LITERAL_CSTRING(id, "id");
   NS_NAMED_LITERAL_CSTRING(openLimit, " LIMIT ");
 
   nsCString queryStart =
     NS_LITERAL_CSTRING("SELECT ") +
-    keyString +
+    keyValue +
     NS_LITERAL_CSTRING(" FROM object_data "
                        "WHERE object_store_id = :") +
     id;
 
   nsAutoCString keyRangeClause;
   if (usingKeyRange) {
     GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
-                                keyString,
+                                keyValue,
                                 keyRangeClause);
   }
 
-  nsAutoCString directionClause = NS_LITERAL_CSTRING(" ORDER BY ") + keyString;
+  nsAutoCString directionClause = NS_LITERAL_CSTRING(" ORDER BY ") + keyValue;
   switch (mCursor->mDirection) {
     case IDBCursor::NEXT:
     case IDBCursor::NEXT_UNIQUE:
       directionClause.AppendLiteral(" ASC");
       break;
 
     case IDBCursor::PREV:
     case IDBCursor::PREV_UNIQUE:
@@ -22575,41 +19461,40 @@ OpenOp::DoObjectStoreKeyDatabaseWork(Dat
   NS_NAMED_LITERAL_CSTRING(rangeKey, "range_key");
 
   switch (mCursor->mDirection) {
     case IDBCursor::NEXT:
     case IDBCursor::NEXT_UNIQUE: {
       Key upper;
       bool open;
       GetRangeKeyInfo(false, &upper, &open);
-      AppendConditionClause(keyString, currentKey, false, false,
+      AppendConditionClause(keyValue, currentKey, false, false,
                             keyRangeClause);
-      AppendConditionClause(keyString, currentKey, false, true,
+      AppendConditionClause(keyValue, currentKey, false, true,
                             continueToKeyRangeClause);
       if (usingKeyRange && !upper.IsUnset()) {
-        AppendConditionClause(keyString, rangeKey, true, !open, keyRangeClause);
-        AppendConditionClause(keyString, rangeKey, true, !open,
+        AppendConditionClause(keyValue, rangeKey, true, !open, keyRangeClause);
+        AppendConditionClause(keyValue, rangeKey, true, !open,
                               continueToKeyRangeClause);
         mCursor->mRangeKey = upper;
       }
       break;
     }
 
     case IDBCursor::PREV:
     case IDBCursor::PREV_UNIQUE: {
       Key lower;
       bool open;
       GetRangeKeyInfo(true, &lower, &open);
-      AppendConditionClause(keyString, currentKey, true, false, keyRangeClause);
-      AppendConditionClause(keyString, currentKey, true, true,
+      AppendConditionClause(keyValue, currentKey, true, false, keyRangeClause);
+      AppendConditionClause(keyValue, currentKey, true, true,
                             continueToKeyRangeClause);
       if (usingKeyRange && !lower.IsUnset()) {
-        AppendConditionClause(keyString, rangeKey, false, !open,
-                              keyRangeClause);
-        AppendConditionClause(keyString, rangeKey, false, !open,
+        AppendConditionClause(keyValue, rangeKey, false, !open, keyRangeClause);
+        AppendConditionClause(keyValue, rangeKey, false, !open,
                               continueToKeyRangeClause);
         mCursor->mRangeKey = lower;
       }
       break;
     }
 
     default:
       MOZ_CRASH("Should never get here!");
@@ -22684,26 +19569,23 @@ OpenOp::DoIndexDatabaseWork(DatabaseConn
 
     default:
       MOZ_CRASH("Should never get here!");
   }
 
   nsAutoCString queryStart =
     NS_LITERAL_CSTRING("SELECT index_table.value, "
                               "index_table.object_data_key, "
-                              "object_data.file_ids, "
-                              "object_data.data "
+                              "object_data.data, "
+                              "object_data.file_ids "
                        "FROM ") +
     indexTable +
     NS_LITERAL_CSTRING(" AS index_table "
                        "JOIN object_data "
-                       "ON index_table.object_store_id = "
-                         "object_data.object_store_id "
-                       "AND index_table.object_data_key = "
-                         "object_data.key "
+                       "ON index_table.object_data_id = object_data.id "
                        "WHERE index_table.index_id = :") +
     id;
 
   nsCString firstQuery =
     queryStart +
     keyRangeClause +
     directionClause +
     openLimit +
@@ -22746,18 +19628,18 @@ OpenOp::DoIndexDatabaseWork(DatabaseConn
 
   rv = mCursor->mObjectKey.SetFromStatement(stmt, 1);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   StructuredCloneReadInfo cloneInfo;
   rv = GetStructuredCloneReadInfoFromStatement(stmt,
+                                               2,
                                                3,
-                                               2,
                                                mCursor->mFileManager,
                                                &cloneInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Now we need to make the query to get the next match.
   NS_NAMED_LITERAL_CSTRING(rangeKey, "range_key");
@@ -23283,18 +20165,18 @@ ContinueOp::DoDatabaseWork(DatabaseConne
     case OpenCursorParams::TObjectStoreOpenCursorParams: {
       rv = mCursor->mKey.SetFromStatement(stmt, 0);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       StructuredCloneReadInfo cloneInfo;
       rv = GetStructuredCloneReadInfoFromStatement(stmt,
+                                                   1,
                                                    2,
-                                                   1,
                                                    mCursor->mFileManager,
                                                    &cloneInfo);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       mResponse = ObjectStoreCursorResponse();
 
@@ -23326,18 +20208,18 @@ ContinueOp::DoDatabaseWork(DatabaseConne
 
       rv = mCursor->mObjectKey.SetFromStatement(stmt, 1);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       StructuredCloneReadInfo cloneInfo;
       rv = GetStructuredCloneReadInfoFromStatement(stmt,
+                                                   2,
                                                    3,
-                                                   2,
                                                    mCursor->mFileManager,
                                                    &cloneInfo);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       mResponse = IndexCursorResponse();
 
--- a/dom/indexedDB/Key.cpp
+++ b/dom/indexedDB/Key.cpp
@@ -8,35 +8,34 @@
 #include "Key.h"
 
 #include <algorithm>
 #include "js/Value.h"
 #include "jsfriendapi.h"
 #include "mozilla/Endian.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozIStorageStatement.h"
-#include "mozIStorageValueArray.h"
 #include "nsAlgorithm.h"
 #include "nsJSUtils.h"
 #include "ReportInternalError.h"
 #include "xpcpublic.h"
 
 namespace mozilla {
 namespace dom {
 namespace indexedDB {
 
 /*
  Here's how we encode keys:
 
  Basic strategy is the following
 
- Numbers: 0x10 n n n n n n n n    ("n"s are encoded 64bit float)
- Dates:   0x20 n n n n n n n n    ("n"s are encoded 64bit float)
- Strings: 0x30 s s s ... 0        ("s"s are encoded unicode bytes)
- Arrays:  0x50 i i i ... 0        ("i"s are encoded array items)
+ Numbers: 1 n n n n n n n n    ("n"s are encoded 64bit float)
+ Dates:   2 n n n n n n n n    ("n"s are encoded 64bit float)
+ Strings: 3 s s s ... 0        ("s"s are encoded unicode bytes)
+ Arrays:  4 i i i ... 0        ("i"s are encoded array items)
 
 
  When encoding floats, 64bit IEEE 754 are almost sortable, except that
  positive sort lower than negative, and negative sort descending. So we use
  the following encoding:
  
  value < 0 ?
    (-to64bitInt(value)) :
@@ -51,68 +50,75 @@ namespace indexedDB {
 
  This ensures that the first byte is never encoded as 0, which means that the
  string terminator (per basic-stategy table) sorts before any character.
  The reason that (3FFF+80) - FFFF is encoded "shifted up" 6 bits is to maximize
  the chance that the last character is 0. See below for why.
 
 
  When encoding Arrays, we use an additional trick. Rather than adding a byte
- containing the value 0x50 to indicate type, we instead add 0x50 to the next byte.
+ containing the value '4' to indicate type, we instead add 4 to the next byte.
  This is usually the byte containing the type of the first item in the array.
  So simple examples are
 
- ["foo"]      0x80 s s s 0 0                              // 0x80 is 0x30 + 0x50
- [1, 2]       0x60 n n n n n n n n 1 n n n n n n n n 0    // 0x60 is 0x10 + 0x50
+ ["foo"]       7 s s s 0 0                              // 7 is 3 + 4
+ [1, 2]        5 n n n n n n n n 1 n n n n n n n n 0    // 5 is 1 + 4
 
  Whe do this iteratively if the first item in the array is also an array
 
- [["foo"]]    0xA0 s s s 0 0 0
+ [["foo"]]    11 s s s 0 0 0
 
  However, to avoid overflow in the byte, we only do this 3 times. If the first
  item in an array is an array, and that array also has an array as first item,
  we simply write out the total value accumulated so far and then follow the
  "normal" rules.
 
- [[["foo"]]]  0xF0 0x30 s s s 0 0 0 0
+ [[["foo"]]]  12 3 s s s 0 0 0 0
 
  There is another edge case that can happen though, which is that the array
- doesn't have a first item to which we can add 0x50 to the type. Instead the
+ doesn't have a first item to which we can add 4 to the type. Instead the
  next byte would normally be the array terminator (per basic-strategy table)
- so we simply add the 0x50 there.
+ so we simply add the 4 there.
 
- [[]]         0xA0 0                // 0xA0 is 0x50 + 0x50 + 0
- []           0x50                  // 0x50 is 0x50 + 0
- [[], "foo"]  0xA0 0x30 s s s 0 0   // 0xA0 is 0x50 + 0x50 + 0
+ [[]]         8 0             // 8 is 4 + 4 + 0
+ []           4               // 4 is 4 + 0
+ [[], "foo"]  8 3 s s s 0 0   // 8 is 4 + 4 + 0
 
  Note that the max-3-times rule kicks in before we get a chance to add to the
  array terminator
 
- [[[]]]       0xF0 0 0 0        // 0xF0 is 0x50 + 0x50 + 0x50
+ [[[]]]       12 0 0 0        // 12 is 4 + 4 + 4
+
+ We could use a much higher number than 3 at no complexity or performance cost,
+ however it seems unlikely that it'll make a practical difference, and the low
+ limit makes testing eaiser.
+
 
  As a final optimization we do a post-encoding step which drops all 0s at the
  end of the encoded buffer.
  
- "foo"         // 0x30 s s s
- 1             // 0x10 bf f0
- ["a", "b"]    // 0x80 s 0 0x30 s
- [1, 2]        // 0x60 bf f0 0 0 0 0 0 0 0x10 c0
- [[]]          // 0x80
+ "foo"         // 3 s s s
+ 1             // 1 bf f0
+ ["a", "b"]    // 7 s 3 s
+ [1, 2]        // 5 bf f0 0 0 0 0 0 0 1 c0
+ [[]]          // 8
 */
 
+const int MaxArrayCollapse = 3;
+
+const int MaxRecursionDepth = 256;
+
 nsresult
 Key::EncodeJSValInternal(JSContext* aCx, JS::Handle<JS::Value> aVal,
                          uint8_t aTypeOffset, uint16_t aRecursionDepth)
 {
-  static_assert(eMaxType * kMaxArrayCollapse < 256,
-                "Unable to encode jsvals.");
+  NS_ENSURE_TRUE(aRecursionDepth < MaxRecursionDepth, NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
 
-  if (NS_WARN_IF(aRecursionDepth == kMaxRecursionDepth)) {
-    return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
-  }
+  static_assert(eMaxType * MaxArrayCollapse < 256,
+                "Unable to encode jsvals.");
 
   if (aVal.isString()) {
     nsAutoJSString str;
     if (!str.init(aCx, aVal)) {
       IDB_REPORT_INTERNAL_ERR();
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
     EncodeString(str, aTypeOffset);
@@ -128,22 +134,22 @@ Key::EncodeJSValInternal(JSContext* aCx,
     return NS_OK;
   }
 
   if (aVal.isObject()) {
     JS::Rooted<JSObject*> obj(aCx, &aVal.toObject());
     if (JS_IsArrayObject(aCx, obj)) {
       aTypeOffset += eMaxType;
 
-      if (aTypeOffset == eMaxType * kMaxArrayCollapse) {
+      if (aTypeOffset == eMaxType * MaxArrayCollapse) {
         mBuffer.Append(aTypeOffset);
         aTypeOffset = 0;
       }
       NS_ASSERTION((aTypeOffset % eMaxType) == 0 &&
-                   aTypeOffset < (eMaxType * kMaxArrayCollapse),
+                   aTypeOffset < (eMaxType * MaxArrayCollapse),
                    "Wrong typeoffset");
 
       uint32_t length;
       if (!JS_GetArrayLength(aCx, obj, &length)) {
         IDB_REPORT_INTERNAL_ERR();
         return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
       }
 
@@ -181,31 +187,29 @@ Key::EncodeJSValInternal(JSContext* aCx,
 }
 
 // static
 nsresult
 Key::DecodeJSValInternal(const unsigned char*& aPos, const unsigned char* aEnd,
                          JSContext* aCx, uint8_t aTypeOffset, JS::MutableHandle<JS::Value> aVal,
                          uint16_t aRecursionDepth)
 {
-  if (NS_WARN_IF(aRecursionDepth == kMaxRecursionDepth)) {
-    return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
-  }
+  NS_ENSURE_TRUE(aRecursionDepth < MaxRecursionDepth, NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
 
   if (*aPos - aTypeOffset >= eArray) {
     JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, 0));
     if (!array) {
       NS_WARNING("Failed to make array!");
       IDB_REPORT_INTERNAL_ERR();
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
 
     aTypeOffset += eMaxType;
 
-    if (aTypeOffset == eMaxType * kMaxArrayCollapse) {
+    if (aTypeOffset == eMaxType * MaxArrayCollapse) {
       ++aPos;
       aTypeOffset = 0;
     }
 
     uint32_t index = 0;
     JS::Rooted<JS::Value> val(aCx);
     while (aPos < aEnd && *aPos - aTypeOffset != eTerminator) {
       nsresult rv = DecodeJSValInternal(aPos, aEnd, aCx, aTypeOffset,
@@ -322,19 +326,20 @@ Key::EncodeString(const nsAString& aStri
   NS_ASSERTION(buffer == mBuffer.EndReading(), "Wrote wrong number of bytes");
 }
 
 // static
 nsresult
 Key::DecodeJSVal(const unsigned char*& aPos,
                  const unsigned char* aEnd,
                  JSContext* aCx,
+                 uint8_t aTypeOffset,
                  JS::MutableHandle<JS::Value> aVal)
 {
-  return DecodeJSValInternal(aPos, aEnd, aCx, 0, aVal, 0);
+  return DecodeJSValInternal(aPos, aEnd, aCx, aTypeOffset, aVal, 0);
 }
 
 // static
 void
 Key::DecodeString(const unsigned char*& aPos, const unsigned char* aEnd,
                   nsString& aString)
 {
   NS_ASSERTION(*aPos % eMaxType == eString, "Don't call me!");
@@ -456,24 +461,26 @@ Key::BindToStatement(mozIStorageStatemen
 
   return NS_SUCCEEDED(rv) ? NS_OK : NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
 }
 
 nsresult
 Key::SetFromStatement(mozIStorageStatement* aStatement,
                       uint32_t aIndex)
 {
-  return SetFromSource(aStatement, aIndex);
-}
+  uint8_t* data;
+  uint32_t dataLength = 0;
 
-nsresult
-Key::SetFromValueArray(mozIStorageValueArray* aValues,
-                      uint32_t aIndex)
-{
-  return SetFromSource(aValues, aIndex);
+  nsresult rv = aStatement->GetBlob(aIndex, &dataLength, &data);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  mBuffer.Adopt(
+    reinterpret_cast<char*>(const_cast<uint8_t*>(data)), dataLength);
+
+  return NS_OK;
 }
 
 nsresult
 Key::SetFromJSVal(JSContext* aCx,
                   JS::Handle<JS::Value> aVal)
 {
   mBuffer.Truncate();
 
@@ -497,17 +504,17 @@ Key::ToJSVal(JSContext* aCx,
              JS::MutableHandle<JS::Value> aVal) const
 {
   if (IsUnset()) {
     aVal.setUndefined();
     return NS_OK;
   }
 
   const unsigned char* pos = BufferStart();
-  nsresult rv = DecodeJSVal(pos, BufferEnd(), aCx, aVal);
+  nsresult rv = DecodeJSVal(pos, BufferEnd(), aCx, 0, aVal);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   MOZ_ASSERT(pos >= BufferEnd());
 
   return NS_OK;
 }
@@ -531,33 +538,16 @@ Key::AppendItem(JSContext* aCx, bool aFi
   if (NS_FAILED(rv)) {
     Unset();
     return rv;
   }
 
   return NS_OK;
 }
 
-template <typename T>
-nsresult
-Key::SetFromSource(T* aSource, uint32_t aIndex)
-{
-  const uint8_t* data;
-  uint32_t dataLength = 0;
-
-  nsresult rv = aSource->GetSharedBlob(aIndex, &dataLength, &data);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-  }
-
-  mBuffer.Assign(reinterpret_cast<const char*>(data), dataLength);
-
-  return NS_OK;
-}
-
 #ifdef DEBUG
 
 void
 Key::Assert(bool aCondition) const
 {
   MOZ_ASSERT(aCondition);
 }
 
--- a/dom/indexedDB/Key.h
+++ b/dom/indexedDB/Key.h
@@ -6,17 +6,16 @@
 
 #ifndef mozilla_dom_indexeddb_key_h__
 #define mozilla_dom_indexeddb_key_h__
 
 #include "js/RootingAPI.h"
 #include "nsString.h"
 
 class mozIStorageStatement;
-class mozIStorageValueArray;
 
 namespace IPC {
 
 template <typename> struct ParamTraits;
 
 } // namespace IPC
 
 namespace mozilla {
@@ -25,38 +24,21 @@ namespace indexedDB {
 
 class Key
 {
   friend struct IPC::ParamTraits<Key>;
 
   nsCString mBuffer;
 
 public:
-  enum {
-    eTerminator = 0,
-    eFloat = 0x10,
-    eDate = 0x20,
-    eString = 0x30,
-    eArray = 0x50,
-    eMaxType = eArray
-  };
-
-  static const uint8_t kMaxArrayCollapse = uint8_t(3);
-  static const uint8_t kMaxRecursionDepth = uint8_t(64);
-
   Key()
   {
     Unset();
   }
 
-  explicit
-  Key(const nsACString& aBuffer)
-    : mBuffer(aBuffer)
-  { }
-
   Key&
   operator=(const nsAString& aString)
   {
     SetFromString(aString);
     return *this;
   }
 
   Key&
@@ -124,35 +106,35 @@ public:
   IsUnset() const
   {
     return mBuffer.IsVoid();
   }
 
   bool
   IsFloat() const
   {
-    return !IsUnset() && *BufferStart() == eFloat;
+    return !IsUnset() && mBuffer.First() == eFloat;
   }
 
   bool
   IsDate() const
   {
-    return !IsUnset() && *BufferStart() == eDate;
+    return !IsUnset() && mBuffer.First() == eDate;
   }
 
   bool
   IsString() const
   {
-    return !IsUnset() && *BufferStart() == eString;
+    return !IsUnset() && mBuffer.First() == eString;
   }
 
   bool
   IsArray() const
   {
-    return !IsUnset() && *BufferStart() >= eArray;
+    return !IsUnset() && mBuffer.First() >= eArray;
   }
 
   double
   ToFloat() const
   {
     Assert(IsFloat());
     const unsigned char* pos = BufferStart();
     double res = DecodeNumber(pos, BufferEnd());
@@ -221,19 +203,16 @@ public:
 
   nsresult
   BindToStatement(mozIStorageStatement* aStatement,
                   const nsACString& aParamName) const;
 
   nsresult
   SetFromStatement(mozIStorageStatement* aStatement, uint32_t aIndex);
 
-  nsresult
-  SetFromValueArray(mozIStorageValueArray* aValues, uint32_t aIndex);
-
   static int16_t
   CompareKeys(Key& aFirst, Key& aSecond)
   {
     int32_t result = Compare(aFirst.mBuffer, aSecond.mBuffer);
 
     if (result < 0) {
       return -1;
     }
@@ -253,16 +232,25 @@ private:
   }
 
   const unsigned char*
   BufferEnd() const
   {
     return reinterpret_cast<const unsigned char*>(mBuffer.EndReading());
   }
 
+  enum {
+    eTerminator = 0,
+    eFloat = 1,
+    eDate = 2,
+    eString = 3,
+    eArray = 4,
+    eMaxType = eArray
+  };
+
   // Encoding helper. Trims trailing zeros off of mBuffer as a post-processing
   // step.
   void
   TrimBuffer()
   {
     const char* end = mBuffer.EndReading() - 1;
     while (!*end) {
       --end;
@@ -282,16 +270,17 @@ private:
   EncodeNumber(double aFloat, uint8_t aType);
 
   // Decoding functions. aPos points into mBuffer and is adjusted to point
   // past the consumed value.
   static nsresult
   DecodeJSVal(const unsigned char*& aPos,
               const unsigned char* aEnd,
               JSContext* aCx,
+              uint8_t aTypeOffset,
               JS::MutableHandle<JS::Value> aVal);
 
   static void
   DecodeString(const unsigned char*& aPos,
                const unsigned char* aEnd,
                nsString& aString);
 
   static double
@@ -306,20 +295,16 @@ private:
   static nsresult
   DecodeJSValInternal(const unsigned char*& aPos,
                       const unsigned char* aEnd,
                       JSContext* aCx,
                       uint8_t aTypeOffset,
                       JS::MutableHandle<JS::Value> aVal,
                       uint16_t aRecursionDepth);
 
-  template <typename T>
-  nsresult
-  SetFromSource(T* aSource, uint32_t aIndex);
-
   void
   Assert(bool aCondition) const
 #ifdef DEBUG
   ;
 #else
   { }
 #endif
 };
deleted file mode 100644
index e13cce9d2e20fcc9426ea7db8a04642125fef1a2..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/dom/indexedDB/test/unit/test_schema18upgrade.js
+++ /dev/null
@@ -1,336 +0,0 @@
-/**
- * Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-var testGenerator = testSteps();
-
-function testSteps()
-{
-  const testName = "schema18upgrade";
-  const testKeys = [
-    -1/0,
-    -1.7e308,
-    -10000,
-    -2,
-    -1.5,
-    -1,
-    -1.00001e-200,
-    -1e-200,
-    0,
-    1e-200,
-    1.00001e-200,
-    1,
-    2,
-    10000,
-    1.7e308,
-    1/0,
-    new Date("1750-01-02"),
-    new Date("1800-12-31T12:34:56.001Z"),
-    new Date(-1000),
-    new Date(-10),
-    new Date(-1),
-    new Date(0),
-    new Date(1),
-    new Date(2),
-    new Date(1000),
-    new Date("1971-01-01"),
-    new Date("1971-01-01T01:01:01Z"),
-    new Date("1971-01-01T01:01:01.001Z"),
-    new Date("1971-01-01T01:01:01.01Z"),
-    new Date("1971-01-01T01:01:01.1Z"),
-    new Date("1980-02-02"),
-    new Date("3333-03-19T03:33:33.333Z"),
-    "",
-    "\x00",
-    "\x00\x00",
-    "\x00\x01",
-    "\x01",
-    "\x02",
-    "\x03",
-    "\x04",
-    "\x07",
-    "\x08",
-    "\x0F",
-    "\x10",
-    "\x1F",
-    "\x20",
-    "01234",
-    "\x3F",
-    "\x40",
-    "A",
-    "A\x00",
-    "A1",
-    "ZZZZ",
-    "a",
-    "a\x00",
-    "aa",
-    "azz",
-    "}",
-    "\x7E",
-    "\x7F",
-    "\x80",
-    "\xFF",
-    "\u0100",
-    "\u01FF",
-    "\u0200",
-    "\u03FF",
-    "\u0400",
-    "\u07FF",
-    "\u0800",
-    "\u0FFF",
-    "\u1000",
-    "\u1FFF",
-    "\u2000",
-    "\u3FFF",
-    "\u4000",
-    "\u7FFF",
-    "\u8000",
-    "\uD800",
-    "\uD800a",
-    "\uD800\uDC01",
-    "\uDBFF",
-    "\uDC00",
-    "\uDFFF\uD800",
-    "\uFFFE",
-    "\uFFFF",
-      "\uFFFF\x00",
-    "\uFFFFZZZ",
-    [],
-    [-1/0],
-    [-1],
-    [0],
-    [1],
-    [1, "a"],
-    [1, []],
-    [1, [""]],
-    [2, 3],
-    [2, 3.0000000000001],
-    [12, [[]]],
-    [12, [[[]]]],
-    [12, [[[""]]]],
-    [12, [[["foo"]]]],
-    [12, [[[[[3]]]]]],
-    [12, [[[[[[3]]]]]]],
-    [12, [[[[[[3],[[[[[4.2]]]]]]]]]]],
-    [new Date(-1)],
-    [new Date(1)],
-    [""],
-    ["", [[]]],
-    ["", [[[]]]],
-    ["abc"],
-    ["abc", "def"],
-    ["abc\x00"],
-    ["abc\x00", "\x00\x01"],
-    ["abc\x00", "\x00def"],
-    ["abc\x00\x00def"],
-    ["x", [[]]],
-    ["x", [[[]]]],
-    [[]],
-    [[],"foo"],
-    [[],[]],
-    [[[]]],
-    [[[]], []],
-    [[[]], [[]]],
-    [[[]], [[1]]],
-    [[[]], [[[]]]],
-    [[[1]]],
-    [[[[]], []]],
-  ];
-  const testString =
-    "abcdefghijklmnopqrstuvwxyz0123456789`~!@#$%^&*()-_+=,<.>/?\\|";
-
-  clearAllDatabases(continueToNextStepSync);
-  yield undefined;
-
-  info("Installing profile");
-
-  installPackagedProfile(testName + "_profile");
-
-  info("Opening database with no version");
-
-  let request = indexedDB.open(testName);
-  request.onerror = errorHandler;
-  request.onupgradeneeded = unexpectedSuccessHandler;
-  request.onsuccess = grabEventAndContinueHandler;
-  let event = yield undefined;
-
-  let db = event.target.result;
-
-  is(db.version, 1, "Correct db version");
-
-  let transaction = db.transaction(testName);
-  transaction.oncomplete = grabEventAndContinueHandler;
-
-  let objectStore = transaction.objectStore(testName);
-  let index = objectStore.index("uniqueIndex");
-
-  info("Starting 'uniqueIndex' cursor");
-
-  let keyIndex = 0;
-  index.openCursor().onsuccess = event => {
-    let cursor = event.target.result;
-    if (cursor) {
-      info("Comparing " + JSON.stringify(cursor.primaryKey) + " to " +
-           JSON.stringify(testKeys[cursor.key]) +
-           " [" + cursor.key + "]");
-      is(indexedDB.cmp(cursor.primaryKey, testKeys[cursor.key]), 0,
-         "Keys compare equally via 'indexedDB.cmp'");
-      is(compareKeys(cursor.primaryKey, testKeys[cursor.key]), true,
-         "Keys compare equally via 'compareKeys'");
-
-      let indexProperty = cursor.value.index;
-      is(Array.isArray(indexProperty), true, "index property is Array");
-      is(indexProperty[0], cursor.key, "index property first item correct");
-      is(indexProperty[1], cursor.key + 1, "index property second item correct");
-
-      is(cursor.key, keyIndex, "Cursor key property is correct");
-
-      is(cursor.value.testString, testString, "Test string compared equally");
-
-      keyIndex++;
-      cursor.continue();
-    }
-  };
-  yield undefined;
-
-  is(keyIndex, testKeys.length, "Saw all keys");
-
-  transaction = db.transaction(testName, "readwrite");
-  transaction.oncomplete = grabEventAndContinueHandler;
-
-  objectStore = transaction.objectStore(testName);
-  index = objectStore.index("index");
-
-  info("Getting all 'index' keys");
-
-  index.getAllKeys().onsuccess = grabEventAndContinueHandler;
-  event = yield undefined;
-
-  is(event.target.result.length, testKeys.length * 2, "Got all keys");
-
-  info("Starting objectStore cursor");
-
-  objectStore.openCursor().onsuccess = event => {
-    let cursor = event.target.result;
-    if (cursor) {
-      let value = cursor.value;
-      is(value.testString, testString, "Test string compared equally");
-
-      delete value.index;
-      cursor.update(value);
-
-      cursor.continue();
-    } else {
-      continueToNextStepSync();
-    }
-  };
-  yield undefined;
-
-  info("Getting all 'index' keys");
-
-  index.getAllKeys().onsuccess = grabEventAndContinueHandler;
-  event = yield undefined;
-
-  is(event.target.result.length, 0, "Removed all keys");
-  yield undefined;
-
-  db.close();
-
-  info("Opening database with new version");
-
-  request = indexedDB.open(testName, 2);
-  request.onerror = errorHandler;
-  request.onupgradeneeded = grabEventAndContinueHandler;
-  request.onsuccess = grabEventAndContinueHandler;
-  event = yield undefined;
-
-  info("Deleting indexes");
-
-  objectStore = event.target.transaction.objectStore(testName);
-  objectStore.deleteIndex("index");
-  objectStore.deleteIndex("uniqueIndex");
-
-  event = yield undefined;
-
-  db = event.target.result;
-
-  transaction = db.transaction(testName, "readwrite");
-  transaction.oncomplete = grabEventAndContinueHandler;
-
-  info("Starting objectStore cursor");
-
-  objectStore = transaction.objectStore(testName);
-  objectStore.openCursor().onsuccess = event => {
-    let cursor = event.target.result;
-    if (cursor) {
-      let value = cursor.value;
-      is(value.testString, testString, "Test string compared equally");
-
-      value.index = value.keyPath;
-      cursor.update(value);
-
-      cursor.continue();
-    }
-  };
-  event = yield undefined;
-
-  db.close();
-
-  info("Opening database with new version");
-
-  request = indexedDB.open(testName, 3);
-  request.onerror = errorHandler;
-  request.onupgradeneeded = grabEventAndContinueHandler;
-  request.onsuccess = grabEventAndContinueHandler;
-  event = yield undefined;
-
-  info("Creating indexes");
-
-  objectStore = event.target.transaction.objectStore(testName);
-  objectStore.createIndex("index", "index");
-
-  event = yield undefined;
-
-  db = event.target.result;
-
-  transaction = db.transaction(testName);
-  transaction.oncomplete = grabEventAndContinueHandler;
-
-  objectStore = transaction.objectStore(testName);
-  index = objectStore.index("index");
-
-  info("Starting 'index' cursor");
-
-  keyIndex = 0;
-  index.openCursor().onsuccess = event => {
-    let cursor = event.target.result;
-    if (cursor) {
-      is(indexedDB.cmp(cursor.primaryKey, testKeys[keyIndex]), 0,
-         "Keys compare equally via 'indexedDB.cmp'");
-      is(compareKeys(cursor.primaryKey, testKeys[keyIndex]), true,
-         "Keys compare equally via 'compareKeys'");
-      is(indexedDB.cmp(cursor.key, testKeys[keyIndex]), 0,
-         "Keys compare equally via 'indexedDB.cmp'");
-      is(compareKeys(cursor.key, testKeys[keyIndex]), true,
-         "Keys compare equally via 'compareKeys'");
-
-      let indexProperty = cursor.value.index;
-      is(indexedDB.cmp(indexProperty, testKeys[keyIndex]), 0,
-         "Keys compare equally via 'indexedDB.cmp'");
-      is(compareKeys(indexProperty, testKeys[keyIndex]), true,
-         "Keys compare equally via 'compareKeys'");
-
-      is(cursor.value.testString, testString, "Test string compared equally");
-
-      keyIndex++;
-      cursor.continue();
-    }
-  };
-  yield undefined;
-
-  is(keyIndex, testKeys.length, "Added all keys again");
-
-  finishTest();
-  yield undefined;
-}
--- a/dom/indexedDB/test/unit/xpcshell-parent-process.ini
+++ b/dom/indexedDB/test/unit/xpcshell-parent-process.ini
@@ -10,27 +10,25 @@ skip-if = toolkit == 'gonk'
 support-files =
   bug1056939_profile.zip
   defaultStorageUpgrade_profile.zip
   GlobalObjectsChild.js
   GlobalObjectsComponent.js
   GlobalObjectsComponent.manifest
   GlobalObjectsModule.jsm
   GlobalObjectsSandbox.js
-  schema18upgrade_profile.zip
   xpcshell-shared.ini
 
 [include:xpcshell-shared.ini]
 
 [test_blob_file_backed.js]
 [test_bug1056939.js]
 [test_defaultStorageUpgrade.js]
 [test_globalObjects_ipc.js]
 skip-if = toolkit == 'android'
 [test_invalidate.js]
 # disabled for the moment.
 skip-if = true
 [test_lowDiskSpace.js]
 [test_readwriteflush_disabled.js]
-[test_schema18upgrade.js]
 [test_temporary_storage.js]
 # bug 951017: intermittent failure on Android x86 emulator
 skip-if = os == "android" && processor == "x86"
--- a/dom/quota/FileStreams.cpp
+++ b/dom/quota/FileStreams.cpp
@@ -18,17 +18,17 @@ FileQuotaStream<FileStreamBase>::SetEOF(
   nsresult rv = FileStreamBase::SetEOF();
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (mQuotaObject) {
     int64_t offset;
     nsresult rv = FileStreamBase::Tell(&offset);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    mQuotaObject->MaybeUpdateSize(offset, /* aTruncate */ true);
+    mQuotaObject->UpdateSize(offset);
   }
 
   return NS_OK;
 }
 
 template <class FileStreamBase>
 NS_IMETHODIMP
 FileQuotaStream<FileStreamBase>::Close()
@@ -51,17 +51,17 @@ FileQuotaStream<FileStreamBase>::DoOpen(
   NS_ASSERTION(!mQuotaObject, "Creating quota object more than once?");
   mQuotaObject = quotaManager->GetQuotaObject(mPersistenceType, mGroup, mOrigin,
     FileStreamBase::mOpenParams.localFile);
 
   nsresult rv = FileStreamBase::DoOpen();
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (mQuotaObject && (FileStreamBase::mOpenParams.ioFlags & PR_TRUNCATE)) {
-    mQuotaObject->MaybeUpdateSize(0, /* aTruncate */ true);
+    mQuotaObject->UpdateSize(0);
   }
 
   return NS_OK;
 }
 
 template <class FileStreamBase>
 NS_IMETHODIMP
 FileQuotaStreamWithWrite<FileStreamBase>::Write(const char* aBuf,
@@ -70,21 +70,18 @@ FileQuotaStreamWithWrite<FileStreamBase>
 {
   nsresult rv;
 
   if (FileQuotaStreamWithWrite::mQuotaObject) {
     int64_t offset;
     rv = FileStreamBase::Tell(&offset);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    MOZ_ASSERT(INT64_MAX - offset >= int64_t(aCount));
-
     if (!FileQuotaStreamWithWrite::
-         mQuotaObject->MaybeUpdateSize(offset + int64_t(aCount),
-                                       /* aTruncate */ false)) {
+         mQuotaObject->MaybeAllocateMoreSpace(offset, aCount)) {
       return NS_ERROR_FILE_NO_DEVICE_SPACE;
     }
   }
 
   rv = FileStreamBase::Write(aBuf, aCount, _retval);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
--- a/dom/quota/QuotaObject.cpp
+++ b/dom/quota/QuotaObject.cpp
@@ -5,57 +5,64 @@
  * 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, bool = mozilla::IsUnsigned<T>::value>
-struct IntChecker
-{
-  static void
-  Assert(T aInt)
-  {
-    static_assert(mozilla::IsIntegral<T>::value, "Not an integer!");
-    MOZ_ASSERT(aInt >= 0);
-  }
-};
-
-template <typename T>
-struct IntChecker<T, true>
+template <typename T, typename U>
+void
+AssertPositiveIntegers(T aOne, U aTwo)
 {
-  static void
-  Assert(T aInt)
-  {
-    static_assert(mozilla::IsIntegral<T>::value, "Not an integer!");
-  }
-};
-
-template <typename T>
-void
-AssertNoOverflow(uint64_t aDest, T aArg)
-{
-  IntChecker<T>::Assert(aDest);
-  IntChecker<T>::Assert(aArg);
-  MOZ_ASSERT(UINT64_MAX - aDest >= uint64_t(aArg));
+  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
-AssertNoUnderflow(T aDest, U aArg)
+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)
 {
-  IntChecker<T>::Assert(aDest);
-  IntChecker<T>::Assert(aArg);
-  MOZ_ASSERT(uint64_t(aDest) >= uint64_t(aArg));
+  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();
@@ -100,61 +107,97 @@ QuotaObject::Release()
     if (mOriginInfo) {
       mOriginInfo->mQuotaObjects.Remove(mPath);
     }
   }
 
   delete this;
 }
 
-bool
-QuotaObject::MaybeUpdateSize(int64_t aSize, bool aTruncate)
+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();
-  MOZ_ASSERT(quotaManager);
+  NS_ASSERTION(quotaManager, "Shouldn't be null!");
 
   MutexAutoLock lock(quotaManager->mQuotaMutex);
 
-  if (mSize == aSize) {
-    return true;
+  if (!mOriginInfo || mSize == aSize) {
+    return;
   }
 
-  if (!mOriginInfo) {
-    mSize = aSize;
+  AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, mSize);
+  quotaManager->mTemporaryStorageUsage -= mSize;
+
+  GroupInfo* groupInfo = mOriginInfo->mGroupInfo;
+
+  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) {
     return true;
   }
 
   GroupInfo* groupInfo = mOriginInfo->mGroupInfo;
-  MOZ_ASSERT(groupInfo);
-
-  if (mSize > aSize) {
-    if (aTruncate) {
-      const int64_t delta = mSize - aSize;
-
-      AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, delta);
-      quotaManager->mTemporaryStorageUsage -= delta;
-
-      AssertNoUnderflow(groupInfo->mUsage, delta);
-      groupInfo->mUsage -= delta;
-
-      AssertNoUnderflow(mOriginInfo->mUsage, delta);
-      mOriginInfo->mUsage -= delta;
-
-      mSize = aSize;
-    }
-    return true;
-  }
-
-  MOZ_ASSERT(mSize < aSize);
 
   nsRefPtr<GroupInfo> complementaryGroupInfo =
     groupInfo->mGroupInfoPair->LockedGetGroupInfo(
       ComplementaryPersistenceType(groupInfo->mPersistenceType));
 
-  uint64_t delta = aSize - mSize;
+  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);
@@ -224,18 +267,18 @@ QuotaObject::MaybeUpdateSize(int64_t aSi
 #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(aSize, mSize);
-    delta = aSize - mSize;
+    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;
@@ -266,33 +309,33 @@ QuotaObject::MaybeUpdateSize(int64_t aSi
     // 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.
-    MOZ_ASSERT(mSize < aSize);
-    mSize = aSize;
+    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);
 
     return true;
   }
 
   mOriginInfo->mUsage = newUsage;
   groupInfo->mUsage = newGroupUsage;
   quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage;
 
-  mSize = aSize;
+  mSize = end;
 
   return true;
 }
 
 void
 OriginInfo::LockedDecreaseUsage(int64_t aSize)
 {
   AssertCurrentThreadOwnsQuotaMutex();
--- a/dom/quota/QuotaObject.h
+++ b/dom/quota/QuotaObject.h
@@ -27,18 +27,21 @@ class QuotaObject
 
 public:
   void
   AddRef();
 
   void
   Release();
 
+  void
+  UpdateSize(int64_t aSize);
+
   bool
-  MaybeUpdateSize(int64_t aSize, bool aTruncate);
+  MaybeAllocateMoreSpace(int64_t aOffset, int32_t aCount);
 
 private:
   QuotaObject(OriginInfo* aOriginInfo, const nsAString& aPath, int64_t aSize)
   : mOriginInfo(aOriginInfo), mPath(aPath), mSize(aSize)
   {
     MOZ_COUNT_CTOR(QuotaObject);
   }
 
--- a/storage/src/TelemetryVFS.cpp
+++ b/storage/src/TelemetryVFS.cpp
@@ -135,20 +135,16 @@ struct telemetry_file {
   sqlite3_file base;
 
   // histograms pertaining to this file
   Histograms *histograms;
 
   // quota object for this file
   nsRefPtr<QuotaObject> quotaObject;
 
-  // The chunk size for this file. See the documentation for
-  // sqlite3_file_control() and FCNTL_CHUNK_SIZE.
-  int fileChunkSize;
-
   // This contains the vfs that actually does work
   sqlite3_file pReal[1];
 };
 
 const char*
 DatabasePathFromWALPath(const char *zWALName)
 {
   /**
@@ -282,17 +278,18 @@ DatabasePathFromWALPath(const char *zWAL
 already_AddRefed<QuotaObject>
 GetQuotaObjectFromNameAndParameters(const char *zName,
                                     const char *zURIParameterKey)
 {
   MOZ_ASSERT(zName);
   MOZ_ASSERT(zURIParameterKey);
 
   const char *persistenceType =
-    sqlite3_uri_parameter(zURIParameterKey, "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;
@@ -350,19 +347,16 @@ xClose(sqlite3_file *pFile)
   { // Scope for IOThreadAutoTimer
     IOThreadAutoTimer ioTimer(IOInterposeObserver::OpClose);
     rc = p->pReal->pMethods->xClose(p->pReal);
   }
   if( rc==SQLITE_OK ){
     delete p->base.pMethods;
     p->base.pMethods = nullptr;
     p->quotaObject = nullptr;
-#ifdef DEBUG
-    p->fileChunkSize = 0;
-#endif
   }
   return rc;
 }
 
 /*
 ** Read data from a telemetry_file.
 */
 int
@@ -374,92 +368,65 @@ xRead(sqlite3_file *pFile, void *zBuf, i
   rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst);
   // sqlite likes to read from empty files, this is normal, ignore it.
   if (rc != SQLITE_IOERR_SHORT_READ)
     Telemetry::Accumulate(p->histograms->readB, rc == SQLITE_OK ? iAmt : 0);
   return rc;
 }
 
 /*
+** Write data to a telemetry_file.
+*/
+int
+xWrite(sqlite3_file *pFile, const void *zBuf, int iAmt, sqlite_int64 iOfst)
+{
+  telemetry_file *p = (telemetry_file *)pFile;
+  if (p->quotaObject && !p->quotaObject->MaybeAllocateMoreSpace(iOfst, iAmt)) {
+    return SQLITE_FULL;
+  }
+  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;
 }
 
 /*
-** Write data to a telemetry_file.
-*/
-int
-xWrite(sqlite3_file *pFile, const void *zBuf, int iAmt, sqlite_int64 iOfst)
-{
-  telemetry_file *p = (telemetry_file *)pFile;
-  IOThreadAutoTimer ioTimer(p->histograms->writeMS, IOInterposeObserver::OpWrite);
-  int rc;
-  if (p->quotaObject) {
-    MOZ_ASSERT(INT64_MAX - iOfst >= iAmt);
-    if (!p->quotaObject->MaybeUpdateSize(iOfst + iAmt, /* aTruncate */ false)) {
-      return SQLITE_FULL;
-    }
-  }
-  rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst);
-  Telemetry::Accumulate(p->histograms->writeB, rc == SQLITE_OK ? iAmt : 0);
-  if (p->quotaObject && rc != SQLITE_OK) {
-    NS_WARNING("xWrite failed on a quota-controlled file, attempting to "
-               "update its current size...");
-    sqlite_int64 currentSize;
-    if (xFileSize(pFile, &currentSize) == SQLITE_OK) {
-      p->quotaObject->MaybeUpdateSize(currentSize, /* aTruncate */ true);
-    }
-  }
-  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;
-  if (p->quotaObject) {
-    if (p->fileChunkSize > 0) {
-      // Round up to the smallest multiple of the chunk size that will hold all
-      // the data.
-      size =
-        ((size + p->fileChunkSize - 1) / p->fileChunkSize) * p->fileChunkSize;
-    }
-    if (!p->quotaObject->MaybeUpdateSize(size, /* aTruncate */ true)) {
-      return SQLITE_FULL;
-    }
-  }
   rc = p->pReal->pMethods->xTruncate(p->pReal, size);
-  if (p->quotaObject) {
+  if (rc == SQLITE_OK && p->quotaObject) {
+    // 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) {
-#ifdef DEBUG
-      // Make sure xTruncate set the size exactly as we calculated above.
-      sqlite_int64 newSize;
-      MOZ_ASSERT(xFileSize(pFile, &newSize) == SQLITE_OK);
-      MOZ_ASSERT(newSize == size);
-#endif
-    } else {
-      NS_WARNING("xTruncate failed on a quota-controlled file, attempting to "
-                 "update its current size...");
-      if (xFileSize(pFile, &size) == SQLITE_OK) {
-        p->quotaObject->MaybeUpdateSize(size, /* aTruncate */ true);
-      }
+      p->quotaObject->UpdateSize(newSize);
     }
   }
   return rc;
 }
 
 /*
 ** Sync a telemetry_file.
 */
@@ -508,51 +475,17 @@ xCheckReservedLock(sqlite3_file *pFile, 
 
 /*
 ** File control method. For custom operations on a telemetry_file.
 */
 int
 xFileControl(sqlite3_file *pFile, int op, void *pArg)
 {
   telemetry_file *p = (telemetry_file *)pFile;
-  int rc;
-  // Hook SQLITE_FCNTL_SIZE_HINT for quota-controlled files and do the necessary
-  // work before passing to the SQLite VFS.
-  if (op == SQLITE_FCNTL_SIZE_HINT && p->quotaObject) {
-    sqlite3_int64 hintSize = *static_cast<sqlite3_int64*>(pArg);
-    sqlite3_int64 currentSize;
-    rc = xFileSize(pFile, &currentSize);
-    if (rc != SQLITE_OK) {
-      return rc;
-    }
-    if (hintSize > currentSize) {
-      rc = xTruncate(pFile, hintSize);
-      if (rc != SQLITE_OK) {
-        return rc;
-      }
-    }
-  }
-  rc = p->pReal->pMethods->xFileControl(p->pReal, op, pArg);
-  // Grab the file chunk size after the SQLite VFS has approved.
-  if (op == SQLITE_FCNTL_CHUNK_SIZE && rc == SQLITE_OK) {
-    p->fileChunkSize = *static_cast<int*>(pArg);
-  }
-#ifdef DEBUG
-  if (op == SQLITE_FCNTL_SIZE_HINT && p->quotaObject && rc == SQLITE_OK) {
-    sqlite3_int64 hintSize = *static_cast<sqlite3_int64*>(pArg);
-    if (p->fileChunkSize > 0) {
-      hintSize =
-        ((hintSize + p->fileChunkSize - 1) / p->fileChunkSize) *
-          p->fileChunkSize;
-    }
-    sqlite3_int64 currentSize;
-    MOZ_ASSERT(xFileSize(pFile, &currentSize) == SQLITE_OK);
-    MOZ_ASSERT(currentSize >= hintSize);
-  }
-#endif
+  int rc = p->pReal->pMethods->xFileControl(p->pReal, op, pArg);
   return rc;
 }
 
 /*
 ** Return the sector-size in bytes for a telemetry_file.
 */
 int
 xSectorSize(sqlite3_file *pFile)
@@ -712,17 +645,17 @@ xDelete(sqlite3_vfs* vfs, const char *zN
     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) {
-    MOZ_ALWAYS_TRUE(quotaObject->MaybeUpdateSize(0, /* aTruncate */ true));
+    quotaObject->UpdateSize(0);
   }
 
   return rc;
 }
 
 int
 xAccess(sqlite3_vfs *vfs, const char *zName, int flags, int *pResOut)
 {
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -8952,17 +8952,16 @@
         "url": "/IndexedDB/interfaces.worker"
       },
       {
         "path": "IndexedDB/key_invalid.htm",
         "url": "/IndexedDB/key_invalid.htm"
       },
       {
         "path": "IndexedDB/key_valid.html",
-        "timeout": "long",
         "url": "/IndexedDB/key_valid.html"
       },
       {
         "path": "IndexedDB/keygenerator-constrainterror.htm",
         "url": "/IndexedDB/keygenerator-constrainterror.htm"
       },
       {
         "path": "IndexedDB/keygenerator-overflow.htm",
--- a/testing/web-platform/tests/IndexedDB/key_valid.html
+++ b/testing/web-platform/tests/IndexedDB/key_valid.html
@@ -1,12 +1,11 @@
 <!DOCTYPE html>
 <!-- Submitted from TestTWF Paris -->
 <meta charset=utf-8">
-<meta name="timeout" content="long">
 <title>Valid key</title>
 <link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#key-construct">
 <link rel=assert title="A value is said to be a valid key if it is one of the following types: Array JavaScript objects [ECMA-262], DOMString [WEBIDL], Date [ECMA-262] or float [WEBIDL]. However Arrays are only valid keys if every item in the array is defined and is a valid key (i.e. sparse arrays can not be valid keys) and if the Array doesn't directly or indirectly contain itself. Any non-numeric properties are ignored, and thus does not affect whether the Array is a valid key. Additionally, if the value is of type float, it is only a valid key if it is not NaN, and if the value is of type Date it is only a valid key if its [[PrimitiveValue]] internal property, as defined by [ECMA-262], is not NaN. Conforming user agents must support all valid keys as keys.">
 <link rel=author href="mailto:batifon@yahoo.fr" title="Baptiste Fontaine">
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
 <script src=support.js></script>