dom/indexedDB/IDBObjectStore.cpp
author Rob Wood <rwood@mozilla.com>
Tue, 25 Jun 2019 08:28:58 +0000
changeset 480057 d5b60a2a9cd9ebebe089503474ace5dd2c2eaf9a
parent 471541 e8c59393c80952adfffd0be0de34c3ae7e48e36a
child 484926 076029811ae99cf947e6d7aa2d7b89cc8794aa69
permissions -rw-r--r--
Bug 1546661 - Enable cold page loads tests for Reference Browser on Try r=perftest-reviewers,Bebe,sparky Differential Revision: https://phabricator.services.mozilla.com/D35568

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "IDBObjectStore.h"

#include "FileInfo.h"
#include "IDBCursor.h"
#include "IDBDatabase.h"
#include "IDBEvents.h"
#include "IDBFactory.h"
#include "IDBIndex.h"
#include "IDBKeyRange.h"
#include "IDBMutableFile.h"
#include "IDBRequest.h"
#include "IDBTransaction.h"
#include "IndexedDatabase.h"
#include "IndexedDatabaseInlines.h"
#include "IndexedDatabaseManager.h"
#include "js/Class.h"
#include "js/Date.h"
#include "js/StructuredClone.h"
#include "KeyPath.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/JSObjectHolder.h"
#include "mozilla/Move.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/DOMStringList.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FileBlobImpl.h"
#include "mozilla/dom/IDBMutableFileBinding.h"
#include "mozilla/dom/BlobBinding.h"
#include "mozilla/dom/IDBObjectStoreBinding.h"
#include "mozilla/dom/MemoryBlobImpl.h"
#include "mozilla/dom/StreamBlobImpl.h"
#include "mozilla/dom/StructuredCloneHolder.h"
#include "mozilla/dom/StructuredCloneTags.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBSharedTypes.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "mozilla/SystemGroup.h"
#include "nsCOMPtr.h"
#include "nsQueryObject.h"
#include "nsStreamUtils.h"
#include "nsStringStream.h"
#include "ProfilerHelpers.h"
#include "ReportInternalError.h"

// Include this last to avoid path problems on Windows.
#include "ActorsChild.h"

namespace mozilla {
namespace dom {

using namespace mozilla::dom::indexedDB;
using namespace mozilla::dom::quota;
using namespace mozilla::ipc;

struct IDBObjectStore::StructuredCloneWriteInfo {
  JSAutoStructuredCloneBuffer mCloneBuffer;
  nsTArray<StructuredCloneFile> mFiles;
  IDBDatabase* mDatabase;
  uint64_t mOffsetToKeyProp;

  explicit StructuredCloneWriteInfo(IDBDatabase* aDatabase)
      : mCloneBuffer(JS::StructuredCloneScope::DifferentProcessForIndexedDB,
                     nullptr, nullptr),
        mDatabase(aDatabase),
        mOffsetToKeyProp(0) {
    MOZ_ASSERT(aDatabase);

    MOZ_COUNT_CTOR(StructuredCloneWriteInfo);
  }

  StructuredCloneWriteInfo(StructuredCloneWriteInfo&& aCloneWriteInfo)
      : mCloneBuffer(std::move(aCloneWriteInfo.mCloneBuffer)),
        mDatabase(aCloneWriteInfo.mDatabase),
        mOffsetToKeyProp(aCloneWriteInfo.mOffsetToKeyProp) {
    MOZ_ASSERT(mDatabase);

    MOZ_COUNT_CTOR(StructuredCloneWriteInfo);

    mFiles.SwapElements(aCloneWriteInfo.mFiles);
    aCloneWriteInfo.mOffsetToKeyProp = 0;
  }

  ~StructuredCloneWriteInfo() { MOZ_COUNT_DTOR(StructuredCloneWriteInfo); }
};

// Used by ValueWrapper::Clone to hold strong references to any blob-like
// objects through the clone process.  This is necessary because:
// - The structured clone process may trigger content code via getters/other
//   which can potentially cause existing strong references to be dropped,
//   necessitating the clone to hold its own strong references.
// - The structured clone can abort partway through, so it's necessary to track
//   what strong references have been acquired so that they can be freed even
//   if a de-serialization does not occur.
struct IDBObjectStore::StructuredCloneInfo {
  nsTArray<StructuredCloneFile> mFiles;
};

namespace {

struct MOZ_STACK_CLASS MutableFileData final {
  nsString type;
  nsString name;

  MutableFileData() { MOZ_COUNT_CTOR(MutableFileData); }

  ~MutableFileData() { MOZ_COUNT_DTOR(MutableFileData); }
};

struct MOZ_STACK_CLASS BlobOrFileData final {
  uint32_t tag;
  uint64_t size;
  nsString type;
  nsString name;
  int64_t lastModifiedDate;

  BlobOrFileData() : tag(0), size(0), lastModifiedDate(INT64_MAX) {
    MOZ_COUNT_CTOR(BlobOrFileData);
  }

  ~BlobOrFileData() { MOZ_COUNT_DTOR(BlobOrFileData); }
};

struct MOZ_STACK_CLASS WasmModuleData final {
  uint32_t bytecodeIndex;
  uint32_t compiledIndex;
  uint32_t flags;

  explicit WasmModuleData(uint32_t aFlags)
      : bytecodeIndex(0), compiledIndex(0), flags(aFlags) {
    MOZ_COUNT_CTOR(WasmModuleData);
  }

  ~WasmModuleData() { MOZ_COUNT_DTOR(WasmModuleData); }
};

struct MOZ_STACK_CLASS GetAddInfoClosure final {
  IDBObjectStore::StructuredCloneWriteInfo& mCloneWriteInfo;
  JS::Handle<JS::Value> mValue;

  GetAddInfoClosure(IDBObjectStore::StructuredCloneWriteInfo& aCloneWriteInfo,
                    JS::Handle<JS::Value> aValue)
      : mCloneWriteInfo(aCloneWriteInfo), mValue(aValue) {
    MOZ_COUNT_CTOR(GetAddInfoClosure);
  }

  ~GetAddInfoClosure() { MOZ_COUNT_DTOR(GetAddInfoClosure); }
};

already_AddRefed<IDBRequest> GenerateRequest(JSContext* aCx,
                                             IDBObjectStore* aObjectStore) {
  MOZ_ASSERT(aObjectStore);
  aObjectStore->AssertIsOnOwningThread();

  IDBTransaction* transaction = aObjectStore->Transaction();

  RefPtr<IDBRequest> request = IDBRequest::Create(
      aCx, aObjectStore, transaction->Database(), transaction);
  MOZ_ASSERT(request);

  return request.forget();
}

bool StructuredCloneWriteCallback(JSContext* aCx,
                                  JSStructuredCloneWriter* aWriter,
                                  JS::Handle<JSObject*> aObj, void* aClosure) {
  MOZ_ASSERT(aCx);
  MOZ_ASSERT(aWriter);
  MOZ_ASSERT(aClosure);

  auto* cloneWriteInfo =
      static_cast<IDBObjectStore::StructuredCloneWriteInfo*>(aClosure);

  if (JS_GetClass(aObj) == IDBObjectStore::DummyPropClass()) {
    MOZ_ASSERT(!cloneWriteInfo->mOffsetToKeyProp);
    cloneWriteInfo->mOffsetToKeyProp = js::GetSCOffset(aWriter);

    uint64_t value = 0;
    // Omit endian swap
    return JS_WriteBytes(aWriter, &value, sizeof(value));
  }

  // UNWRAP_OBJECT calls might mutate this.
  JS::Rooted<JSObject*> obj(aCx, aObj);

  IDBMutableFile* mutableFile;
  if (NS_SUCCEEDED(UNWRAP_OBJECT(IDBMutableFile, &obj, mutableFile))) {
    if (cloneWriteInfo->mDatabase->IsFileHandleDisabled()) {
      return false;
    }

    IDBDatabase* database = mutableFile->Database();
    MOZ_ASSERT(database);

    // Throw when trying to store IDBMutableFile objects that live in a
    // different database.
    if (database != cloneWriteInfo->mDatabase) {
      MOZ_ASSERT(!SameCOMIdentity(database, cloneWriteInfo->mDatabase));

      if (database->Name() != cloneWriteInfo->mDatabase->Name()) {
        return false;
      }

      nsCString fileOrigin, databaseOrigin;
      PersistenceType filePersistenceType, databasePersistenceType;

      if (NS_WARN_IF(NS_FAILED(
              database->GetQuotaInfo(fileOrigin, &filePersistenceType)))) {
        return false;
      }

      if (NS_WARN_IF(NS_FAILED(cloneWriteInfo->mDatabase->GetQuotaInfo(
              databaseOrigin, &databasePersistenceType)))) {
        return false;
      }

      if (filePersistenceType != databasePersistenceType ||
          fileOrigin != databaseOrigin) {
        return false;
      }
    }

    if (cloneWriteInfo->mFiles.Length() > size_t(UINT32_MAX)) {
      MOZ_ASSERT(false, "Fix the structured clone data to use a bigger type!");
      return false;
    }

    const uint32_t index = cloneWriteInfo->mFiles.Length();

    NS_ConvertUTF16toUTF8 convType(mutableFile->Type());
    uint32_t convTypeLength =
        NativeEndian::swapToLittleEndian(convType.Length());

    NS_ConvertUTF16toUTF8 convName(mutableFile->Name());
    uint32_t convNameLength =
        NativeEndian::swapToLittleEndian(convName.Length());

    if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_MUTABLEFILE, uint32_t(index)) ||
        !JS_WriteBytes(aWriter, &convTypeLength, sizeof(uint32_t)) ||
        !JS_WriteBytes(aWriter, convType.get(), convType.Length()) ||
        !JS_WriteBytes(aWriter, &convNameLength, sizeof(uint32_t)) ||
        !JS_WriteBytes(aWriter, convName.get(), convName.Length())) {
      return false;
    }

    StructuredCloneFile* newFile = cloneWriteInfo->mFiles.AppendElement();
    newFile->mMutableFile = mutableFile;
    newFile->mType = StructuredCloneFile::eMutableFile;

    return true;
  }

  {
    Blob* blob = nullptr;
    if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
      ErrorResult rv;
      uint64_t size = blob->GetSize(rv);
      MOZ_ASSERT(!rv.Failed());

      size = NativeEndian::swapToLittleEndian(size);

      nsString type;
      blob->GetType(type);

      NS_ConvertUTF16toUTF8 convType(type);
      uint32_t convTypeLength =
          NativeEndian::swapToLittleEndian(convType.Length());

      if (cloneWriteInfo->mFiles.Length() > size_t(UINT32_MAX)) {
        MOZ_ASSERT(false,
                   "Fix the structured clone data to use a bigger type!");
        return false;
      }

      const uint32_t index = cloneWriteInfo->mFiles.Length();

      if (!JS_WriteUint32Pair(aWriter,
                              blob->IsFile() ? SCTAG_DOM_FILE : SCTAG_DOM_BLOB,
                              index) ||
          !JS_WriteBytes(aWriter, &size, sizeof(size)) ||
          !JS_WriteBytes(aWriter, &convTypeLength, sizeof(convTypeLength)) ||
          !JS_WriteBytes(aWriter, convType.get(), convType.Length())) {
        return false;
      }

      RefPtr<File> file = blob->ToFile();
      if (file) {
        ErrorResult rv;
        int64_t lastModifiedDate = file->GetLastModified(rv);
        MOZ_ALWAYS_TRUE(!rv.Failed());

        lastModifiedDate = NativeEndian::swapToLittleEndian(lastModifiedDate);

        nsString name;
        file->GetName(name);

        NS_ConvertUTF16toUTF8 convName(name);
        uint32_t convNameLength =
            NativeEndian::swapToLittleEndian(convName.Length());

        if (!JS_WriteBytes(aWriter, &lastModifiedDate,
                           sizeof(lastModifiedDate)) ||
            !JS_WriteBytes(aWriter, &convNameLength, sizeof(convNameLength)) ||
            !JS_WriteBytes(aWriter, convName.get(), convName.Length())) {
          return false;
        }
      }

      StructuredCloneFile* newFile = cloneWriteInfo->mFiles.AppendElement();
      newFile->mBlob = blob;
      newFile->mType = StructuredCloneFile::eBlob;

      return true;
    }
  }

  return StructuredCloneHolder::WriteFullySerializableObjects(aCx, aWriter,
                                                              aObj);
}

bool CopyingStructuredCloneWriteCallback(JSContext* aCx,
                                         JSStructuredCloneWriter* aWriter,
                                         JS::Handle<JSObject*> aObj,
                                         void* aClosure) {
  MOZ_ASSERT(aCx);
  MOZ_ASSERT(aWriter);
  MOZ_ASSERT(aClosure);

  auto* cloneInfo = static_cast<IDBObjectStore::StructuredCloneInfo*>(aClosure);

  // UNWRAP_OBJECT calls might mutate this.
  JS::Rooted<JSObject*> obj(aCx, aObj);

  {
    Blob* blob = nullptr;
    if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
      if (cloneInfo->mFiles.Length() > size_t(UINT32_MAX)) {
        MOZ_ASSERT(false,
                   "Fix the structured clone data to use a bigger type!");
        return false;
      }

      const uint32_t index = cloneInfo->mFiles.Length();

      if (!JS_WriteUint32Pair(aWriter,
                              blob->IsFile() ? SCTAG_DOM_FILE : SCTAG_DOM_BLOB,
                              index)) {
        return false;
      }

      StructuredCloneFile* newFile = cloneInfo->mFiles.AppendElement();
      newFile->mBlob = blob;
      newFile->mType = StructuredCloneFile::eBlob;

      return true;
    }
  }

  {
    IDBMutableFile* mutableFile;
    if (NS_SUCCEEDED(UNWRAP_OBJECT(IDBMutableFile, &obj, mutableFile))) {
      if (cloneInfo->mFiles.Length() > size_t(UINT32_MAX)) {
        MOZ_ASSERT(false,
                   "Fix the structured clone data to use a bigger type!");
        return false;
      }

      const uint32_t index = cloneInfo->mFiles.Length();

      if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_MUTABLEFILE, index)) {
        return false;
      }

      StructuredCloneFile* newFile = cloneInfo->mFiles.AppendElement();
      newFile->mMutableFile = mutableFile;
      newFile->mType = StructuredCloneFile::eMutableFile;

      return true;
    }
  }

  return StructuredCloneHolder::WriteFullySerializableObjects(aCx, aWriter,
                                                              aObj);
}

nsresult GetAddInfoCallback(JSContext* aCx, void* aClosure) {
  static const JSStructuredCloneCallbacks kStructuredCloneCallbacks = {
      nullptr /* read */,
      StructuredCloneWriteCallback /* write */,
      nullptr /* reportError */,
      nullptr /* readTransfer */,
      nullptr /* writeTransfer */,
      nullptr /* freeTransfer */,
      nullptr /* canTransfer */
  };

  MOZ_ASSERT(aCx);

  auto* data = static_cast<GetAddInfoClosure*>(aClosure);
  MOZ_ASSERT(data);

  data->mCloneWriteInfo.mOffsetToKeyProp = 0;

  if (!data->mCloneWriteInfo.mCloneBuffer.write(aCx, data->mValue,
                                                &kStructuredCloneCallbacks,
                                                &data->mCloneWriteInfo)) {
    return NS_ERROR_DOM_DATA_CLONE_ERR;
  }

  return NS_OK;
}

bool ResolveMysteryMutableFile(IDBMutableFile* aMutableFile,
                               const nsString& aName, const nsString& aType) {
  MOZ_ASSERT(aMutableFile);
  aMutableFile->SetLazyData(aName, aType);
  return true;
}

bool StructuredCloneReadString(JSStructuredCloneReader* aReader,
                               nsCString& aString) {
  uint32_t length;
  if (!JS_ReadBytes(aReader, &length, sizeof(uint32_t))) {
    NS_WARNING("Failed to read length!");
    return false;
  }
  length = NativeEndian::swapFromLittleEndian(length);

  if (!aString.SetLength(length, fallible)) {
    NS_WARNING("Out of memory?");
    return false;
  }
  char* buffer = aString.BeginWriting();

  if (!JS_ReadBytes(aReader, buffer, length)) {
    NS_WARNING("Failed to read type!");
    return false;
  }

  return true;
}

bool ReadFileHandle(JSStructuredCloneReader* aReader,
                    MutableFileData* aRetval) {
  static_assert(SCTAG_DOM_MUTABLEFILE == 0xFFFF8004, "Update me!");
  MOZ_ASSERT(aReader && aRetval);

  nsCString type;
  if (!StructuredCloneReadString(aReader, type)) {
    return false;
  }
  CopyUTF8toUTF16(type, aRetval->type);

  nsCString name;
  if (!StructuredCloneReadString(aReader, name)) {
    return false;
  }
  CopyUTF8toUTF16(name, aRetval->name);

  return true;
}

bool ReadBlobOrFile(JSStructuredCloneReader* aReader, uint32_t aTag,
                    BlobOrFileData* aRetval) {
  static_assert(SCTAG_DOM_BLOB == 0xffff8001 &&
                    SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE == 0xffff8002 &&
                    SCTAG_DOM_FILE == 0xffff8005,
                "Update me!");

  MOZ_ASSERT(aReader);
  MOZ_ASSERT(aTag == SCTAG_DOM_FILE ||
             aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE ||
             aTag == SCTAG_DOM_BLOB);
  MOZ_ASSERT(aRetval);

  aRetval->tag = aTag;

  uint64_t size;
  if (NS_WARN_IF(!JS_ReadBytes(aReader, &size, sizeof(uint64_t)))) {
    return false;
  }

  aRetval->size = NativeEndian::swapFromLittleEndian(size);

  nsCString type;
  if (NS_WARN_IF(!StructuredCloneReadString(aReader, type))) {
    return false;
  }

  CopyUTF8toUTF16(type, aRetval->type);

  // Blobs are done.
  if (aTag == SCTAG_DOM_BLOB) {
    return true;
  }

  MOZ_ASSERT(aTag == SCTAG_DOM_FILE ||
             aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE);

  int64_t lastModifiedDate;
  if (aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE) {
    lastModifiedDate = INT64_MAX;
  } else {
    if (NS_WARN_IF(!JS_ReadBytes(aReader, &lastModifiedDate,
                                 sizeof(lastModifiedDate)))) {
      return false;
    }
    lastModifiedDate = NativeEndian::swapFromLittleEndian(lastModifiedDate);
  }

  aRetval->lastModifiedDate = lastModifiedDate;

  nsCString name;
  if (NS_WARN_IF(!StructuredCloneReadString(aReader, name))) {
    return false;
  }

  CopyUTF8toUTF16(name, aRetval->name);

  return true;
}

bool ReadWasmModule(JSStructuredCloneReader* aReader, WasmModuleData* aRetval) {
  static_assert(SCTAG_DOM_WASM == 0xFFFF8006, "Update me!");
  MOZ_ASSERT(aReader && aRetval);

  uint32_t bytecodeIndex;
  uint32_t compiledIndex;
  if (NS_WARN_IF(!JS_ReadUint32Pair(aReader, &bytecodeIndex, &compiledIndex))) {
    return false;
  }

  aRetval->bytecodeIndex = bytecodeIndex;
  aRetval->compiledIndex = compiledIndex;

  return true;
}

class ValueDeserializationHelper {
 public:
  static bool CreateAndWrapMutableFile(JSContext* aCx,
                                       StructuredCloneFile& aFile,
                                       const MutableFileData& aData,
                                       JS::MutableHandle<JSObject*> aResult) {
    MOZ_ASSERT(aCx);

    // If we have eBlob, we are in an IDB SQLite schema upgrade where we don't
    // care about a real 'MutableFile', but we just care of having a propert
    // |mType| flag.
    if (aFile.mType == StructuredCloneFile::eBlob) {
      aFile.mType = StructuredCloneFile::eMutableFile;

      // Just make a dummy object.
      JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));

      if (NS_WARN_IF(!obj)) {
        return false;
      }

      aResult.set(obj);
      return true;
    }

    MOZ_ASSERT(aFile.mType == StructuredCloneFile::eMutableFile);

    if (!aFile.mMutableFile || !NS_IsMainThread()) {
      return false;
    }

    if (NS_WARN_IF(!ResolveMysteryMutableFile(aFile.mMutableFile, aData.name,
                                              aData.type))) {
      return false;
    }

    JS::Rooted<JS::Value> wrappedMutableFile(aCx);
    if (!ToJSValue(aCx, aFile.mMutableFile, &wrappedMutableFile)) {
      return false;
    }

    aResult.set(&wrappedMutableFile.toObject());
    return true;
  }

  static bool CreateAndWrapBlobOrFile(JSContext* aCx, IDBDatabase* aDatabase,
                                      StructuredCloneFile& aFile,
                                      const BlobOrFileData& aData,
                                      JS::MutableHandle<JSObject*> aResult) {
    MOZ_ASSERT(aCx);
    MOZ_ASSERT(aData.tag == SCTAG_DOM_FILE ||
               aData.tag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE ||
               aData.tag == SCTAG_DOM_BLOB);
    MOZ_ASSERT(aFile.mType == StructuredCloneFile::eBlob);

    RefPtr<Blob> blob = aFile.mBlob;

    /* If we are creating an index, we do not have an mBlob but do have an
     * mInfo.  Unlike other index or upgrade cases, we do need a real-looking
     * Blob/File instance because the index's key path can reference their
     * properties.  Rather than create a fake-looking object, create a real
     * Blob. */
    if (!blob) {
      MOZ_ASSERT(aFile.mFileInfo);
      nsCOMPtr<nsIFile> file = FileInfo::GetFileForFileInfo(aFile.mFileInfo);
      if (!file) {
        return false;
      }

      RefPtr<FileBlobImpl> impl = new FileBlobImpl(file);
      impl->SetFileId(aFile.mFileInfo->Id());
      blob = File::Create(nullptr, impl);
    }

    // It can happen that this IDB is chrome code, so there is no parent, but
    // still we want to set a correct parent for the new File object.
    nsCOMPtr<nsISupports> parent;
    if (NS_IsMainThread()) {
      if (aDatabase && aDatabase->GetParentObject()) {
        parent = aDatabase->GetParentObject();
      } else {
        parent = xpc::CurrentNativeGlobal(aCx);
      }
    } else {
      WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
      MOZ_ASSERT(workerPrivate);

      WorkerGlobalScope* globalScope = workerPrivate->GlobalScope();
      MOZ_ASSERT(globalScope);

      parent = do_QueryObject(globalScope);
    }

    MOZ_ASSERT(parent);

    if (aData.tag == SCTAG_DOM_BLOB) {
      blob->Impl()->SetLazyData(VoidString(), aData.type, aData.size,
                                INT64_MAX);
      MOZ_ASSERT(!blob->IsFile());

      // ActorsParent sends here a kind of half blob and half file wrapped into
      // a DOM File object. DOM File and DOM Blob are a WebIDL wrapper around a
      // BlobImpl object. SetLazyData() has just changed the BlobImpl to be a
      // Blob (see the previous assert), but 'blob' still has the WebIDL DOM
      // File wrapping.
      // Before exposing it to content, we must recreate a DOM Blob object.

      RefPtr<Blob> exposedBlob =
          Blob::Create(blob->GetParentObject(), blob->Impl());
      MOZ_ASSERT(exposedBlob);
      JS::Rooted<JS::Value> wrappedBlob(aCx);
      if (!ToJSValue(aCx, exposedBlob, &wrappedBlob)) {
        return false;
      }

      aResult.set(&wrappedBlob.toObject());
      return true;
    }

    blob->Impl()->SetLazyData(aData.name, aData.type, aData.size,
                              aData.lastModifiedDate * PR_USEC_PER_MSEC);

    MOZ_ASSERT(blob->IsFile());
    RefPtr<File> file = blob->ToFile();
    MOZ_ASSERT(file);

    JS::Rooted<JS::Value> wrappedFile(aCx);
    if (!ToJSValue(aCx, file, &wrappedFile)) {
      return false;
    }

    aResult.set(&wrappedFile.toObject());
    return true;
  }

  static bool CreateAndWrapWasmModule(JSContext* aCx,
                                      StructuredCloneFile& aFile,
                                      const WasmModuleData& aData,
                                      JS::MutableHandle<JSObject*> aResult) {
    MOZ_ASSERT(aCx);
    MOZ_ASSERT(aFile.mType == StructuredCloneFile::eWasmBytecode);
    MOZ_ASSERT(!aFile.mBlob);

    // If we don't have a WasmModule, we are probably using it for an index
    // creation, but Wasm module can't be used in index creation, so just make a
    // dummy object.
    if (!aFile.mWasmModule) {
      JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));

      if (NS_WARN_IF(!obj)) {
        return false;
      }

      aResult.set(obj);
      return true;
    }

    JS::Rooted<JSObject*> moduleObj(aCx, aFile.mWasmModule->createObject(aCx));
    if (NS_WARN_IF(!moduleObj)) {
      return false;
    }

    aResult.set(moduleObj);
    return true;
  }
};

JSObject* CommonStructuredCloneReadCallback(JSContext* aCx,
                                            JSStructuredCloneReader* aReader,
                                            uint32_t aTag, uint32_t aData,
                                            void* aClosure) {
  // We need to statically assert that our tag values are what we expect
  // so that if people accidentally change them they notice.
  static_assert(SCTAG_DOM_BLOB == 0xffff8001 &&
                    SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE == 0xffff8002 &&
                    SCTAG_DOM_MUTABLEFILE == 0xffff8004 &&
                    SCTAG_DOM_FILE == 0xffff8005 &&
                    SCTAG_DOM_WASM == 0xffff8006,
                "You changed our structured clone tag values and just ate "
                "everyone's IndexedDB data.  I hope you are happy.");

  if (aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE ||
      aTag == SCTAG_DOM_BLOB || aTag == SCTAG_DOM_FILE ||
      aTag == SCTAG_DOM_MUTABLEFILE || aTag == SCTAG_DOM_WASM) {
    auto* cloneReadInfo = static_cast<StructuredCloneReadInfo*>(aClosure);

    JS::Rooted<JSObject*> result(aCx);

    if (aTag == SCTAG_DOM_WASM) {
      WasmModuleData data(aData);
      if (NS_WARN_IF(!ReadWasmModule(aReader, &data))) {
        return nullptr;
      }

      MOZ_ASSERT(data.compiledIndex == data.bytecodeIndex + 1);
      MOZ_ASSERT(!data.flags);

      if (data.bytecodeIndex >= cloneReadInfo->mFiles.Length() ||
          data.compiledIndex >= cloneReadInfo->mFiles.Length()) {
        MOZ_ASSERT(false, "Bad index value!");
        return nullptr;
      }

      StructuredCloneFile& file = cloneReadInfo->mFiles[data.bytecodeIndex];

      if (NS_WARN_IF(!ValueDeserializationHelper::CreateAndWrapWasmModule(
              aCx, file, data, &result))) {
        return nullptr;
      }

      return result;
    }

    if (aData >= cloneReadInfo->mFiles.Length()) {
      MOZ_ASSERT(false, "Bad index value!");
      return nullptr;
    }

    StructuredCloneFile& file = cloneReadInfo->mFiles[aData];

    if (aTag == SCTAG_DOM_MUTABLEFILE) {
      MutableFileData data;
      if (NS_WARN_IF(!ReadFileHandle(aReader, &data))) {
        return nullptr;
      }

      if (NS_WARN_IF(!ValueDeserializationHelper::CreateAndWrapMutableFile(
              aCx, file, data, &result))) {
        return nullptr;
      }

      return result;
    }

    BlobOrFileData data;
    if (NS_WARN_IF(!ReadBlobOrFile(aReader, aTag, &data))) {
      return nullptr;
    }

    if (NS_WARN_IF(!ValueDeserializationHelper::CreateAndWrapBlobOrFile(
            aCx, cloneReadInfo->mDatabase, file, data, &result))) {
      return nullptr;
    }

    return result;
  }

  return StructuredCloneHolder::ReadFullySerializableObjects(aCx, aReader,
                                                             aTag);
}

JSObject* CopyingStructuredCloneReadCallback(JSContext* aCx,
                                             JSStructuredCloneReader* aReader,
                                             uint32_t aTag, uint32_t aData,
                                             void* aClosure) {
  MOZ_ASSERT(aTag != SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE);

  if (aTag == SCTAG_DOM_BLOB || aTag == SCTAG_DOM_FILE ||
      aTag == SCTAG_DOM_MUTABLEFILE || aTag == SCTAG_DOM_WASM) {
    auto* cloneInfo =
        static_cast<IDBObjectStore::StructuredCloneInfo*>(aClosure);

    JS::Rooted<JSObject*> result(aCx);

    if (aData >= cloneInfo->mFiles.Length()) {
      MOZ_ASSERT(false, "Bad index value!");
      return nullptr;
    }

    StructuredCloneFile& file = cloneInfo->mFiles[aData];

    if (aTag == SCTAG_DOM_BLOB) {
      MOZ_ASSERT(file.mType == StructuredCloneFile::eBlob);
      MOZ_ASSERT(!file.mBlob->IsFile());

      JS::Rooted<JS::Value> wrappedBlob(aCx);
      if (NS_WARN_IF(!ToJSValue(aCx, file.mBlob, &wrappedBlob))) {
        return nullptr;
      }

      result.set(&wrappedBlob.toObject());

      return result;
    }

    if (aTag == SCTAG_DOM_FILE) {
      MOZ_ASSERT(file.mType == StructuredCloneFile::eBlob);

      {
        // Create a scope so ~RefPtr fires before returning an unwrapped
        // JS::Value.
        RefPtr<Blob> blob = file.mBlob;
        MOZ_ASSERT(blob->IsFile());

        RefPtr<File> file = blob->ToFile();
        MOZ_ASSERT(file);

        JS::Rooted<JS::Value> wrappedFile(aCx);
        if (NS_WARN_IF(!ToJSValue(aCx, file, &wrappedFile))) {
          return nullptr;
        }

        result.set(&wrappedFile.toObject());
      }

      return result;
    }

    if (aTag == SCTAG_DOM_MUTABLEFILE) {
      MOZ_ASSERT(file.mType == StructuredCloneFile::eMutableFile);

      JS::Rooted<JS::Value> wrappedMutableFile(aCx);
      if (NS_WARN_IF(!ToJSValue(aCx, file.mMutableFile, &wrappedMutableFile))) {
        return nullptr;
      }

      result.set(&wrappedMutableFile.toObject());

      return result;
    }

    MOZ_ASSERT(file.mType == StructuredCloneFile::eWasmBytecode);

    JS::Rooted<JSObject*> wrappedModule(aCx,
                                        file.mWasmModule->createObject(aCx));
    if (NS_WARN_IF(!wrappedModule)) {
      return nullptr;
    }

    result.set(wrappedModule);

    return result;
  }

  return StructuredCloneHolder::ReadFullySerializableObjects(aCx, aReader,
                                                             aTag);
}

}  // namespace

const JSClass IDBObjectStore::sDummyPropJSClass = {
    "IDBObjectStore Dummy", 0 /* flags */
};

IDBObjectStore::IDBObjectStore(IDBTransaction* aTransaction,
                               const ObjectStoreSpec* aSpec)
    : mTransaction(aTransaction),
      mCachedKeyPath(JS::UndefinedValue()),
      mSpec(aSpec),
      mId(aSpec->metadata().id()),
      mRooted(false) {
  MOZ_ASSERT(aTransaction);
  aTransaction->AssertIsOnOwningThread();
  MOZ_ASSERT(aSpec);
}

IDBObjectStore::~IDBObjectStore() {
  AssertIsOnOwningThread();

  if (mRooted) {
    mCachedKeyPath.setUndefined();
    mozilla::DropJSObjects(this);
  }
}

// static
already_AddRefed<IDBObjectStore> IDBObjectStore::Create(
    IDBTransaction* aTransaction, const ObjectStoreSpec& aSpec) {
  MOZ_ASSERT(aTransaction);
  aTransaction->AssertIsOnOwningThread();

  RefPtr<IDBObjectStore> objectStore = new IDBObjectStore(aTransaction, &aSpec);

  return objectStore.forget();
}

// static
void IDBObjectStore::AppendIndexUpdateInfo(
    int64_t aIndexID, const KeyPath& aKeyPath, bool aUnique, bool aMultiEntry,
    const nsCString& aLocale, JSContext* aCx, JS::Handle<JS::Value> aVal,
    nsTArray<IndexUpdateInfo>& aUpdateInfoArray, ErrorResult& aRv) {
  const bool localeAware = !aLocale.IsEmpty();

  if (!aMultiEntry) {
    Key key;
    aRv = aKeyPath.ExtractKey(aCx, aVal, key);

    // If an index's keyPath doesn't match an object, we ignore that object.
    if (aRv.ErrorCodeIs(NS_ERROR_DOM_INDEXEDDB_DATA_ERR) || key.IsUnset()) {
      aRv.SuppressException();
      return;
    }

    if (aRv.Failed()) {
      return;
    }

    IndexUpdateInfo* updateInfo = aUpdateInfoArray.AppendElement();
    updateInfo->indexId() = aIndexID;
    updateInfo->value() = key;
    if (localeAware) {
      aRv = key.ToLocaleBasedKey(updateInfo->localizedValue(), aLocale);
      if (NS_WARN_IF(aRv.Failed())) {
        aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
        return;
      }
    }

    return;
  }

  JS::Rooted<JS::Value> val(aCx);
  if (NS_FAILED(aKeyPath.ExtractKeyAsJSVal(aCx, aVal, val.address()))) {
    return;
  }

  bool isArray;
  if (!JS_IsArrayObject(aCx, val, &isArray)) {
    IDB_REPORT_INTERNAL_ERR();
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
    return;
  }
  if (isArray) {
    JS::Rooted<JSObject*> array(aCx, &val.toObject());
    uint32_t arrayLength;
    if (NS_WARN_IF(!JS_GetArrayLength(aCx, array, &arrayLength))) {
      IDB_REPORT_INTERNAL_ERR();
      aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
      return;
    }

    for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
      JS::Rooted<JS::Value> arrayItem(aCx);
      if (NS_WARN_IF(!JS_GetElement(aCx, array, arrayIndex, &arrayItem))) {
        IDB_REPORT_INTERNAL_ERR();
        aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
        return;
      }

      Key value;
      value.SetFromJSVal(aCx, arrayItem, aRv);
      if (aRv.Failed() || value.IsUnset()) {
        // Not a value we can do anything with, ignore it.
        aRv.SuppressException();
        continue;
      }

      IndexUpdateInfo* updateInfo = aUpdateInfoArray.AppendElement();
      updateInfo->indexId() = aIndexID;
      updateInfo->value() = value;
      if (localeAware) {
        aRv = value.ToLocaleBasedKey(updateInfo->localizedValue(), aLocale);
        if (NS_WARN_IF(aRv.Failed())) {
          aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
          return;
        }
      }
    }
  } else {
    Key value;
    value.SetFromJSVal(aCx, val, aRv);
    if (aRv.Failed() || value.IsUnset()) {
      // Not a value we can do anything with, ignore it.
      aRv.SuppressException();
      return;
    }

    IndexUpdateInfo* updateInfo = aUpdateInfoArray.AppendElement();
    updateInfo->indexId() = aIndexID;
    updateInfo->value() = value;
    if (localeAware) {
      aRv = value.ToLocaleBasedKey(updateInfo->localizedValue(), aLocale);
      if (NS_WARN_IF(aRv.Failed())) {
        aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
        return;
      }
    }
  }
}

// static
void IDBObjectStore::ClearCloneReadInfo(StructuredCloneReadInfo& aReadInfo) {
  // This is kind of tricky, we only want to release stuff on the main thread,
  // but we can end up being called on other threads if we have already been
  // cleared on the main thread.
  if (!aReadInfo.mFiles.Length()) {
    return;
  }

  aReadInfo.mFiles.Clear();
}

// static
bool IDBObjectStore::DeserializeValue(JSContext* aCx,
                                      StructuredCloneReadInfo& aCloneReadInfo,
                                      JS::MutableHandle<JS::Value> aValue) {
  MOZ_ASSERT(aCx);

  if (!aCloneReadInfo.mData.Size()) {
    aValue.setUndefined();
    return true;
  }

  MOZ_ASSERT(!(aCloneReadInfo.mData.Size() % sizeof(uint64_t)));

  static const JSStructuredCloneCallbacks callbacks = {
      CommonStructuredCloneReadCallback,
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      nullptr};

  // FIXME: Consider to use StructuredCloneHolder here and in other
  //        deserializing methods.
  if (!JS_ReadStructuredClone(
          aCx, aCloneReadInfo.mData, JS_STRUCTURED_CLONE_VERSION,
          JS::StructuredCloneScope::DifferentProcessForIndexedDB, aValue,
          &callbacks, &aCloneReadInfo)) {
    return false;
  }

  return true;
}

namespace {

// This class helps to create only 1 sandbox.
class SandboxHolder final {
 public:
  NS_INLINE_DECL_REFCOUNTING(SandboxHolder)

  static JSObject* GetSandbox(JSContext* aCx) {
    SandboxHolder* holder = GetOrCreate();
    return holder->GetSandboxInternal(aCx);
  }

 private:
  ~SandboxHolder() = default;

  static SandboxHolder* GetOrCreate() {
    MOZ_ASSERT(XRE_IsParentProcess());
    MOZ_ASSERT(NS_IsMainThread());

    static StaticRefPtr<SandboxHolder> sHolder;
    if (!sHolder) {
      sHolder = new SandboxHolder();
      ClearOnShutdown(&sHolder);
    }
    return sHolder;
  }

  JSObject* GetSandboxInternal(JSContext* aCx) {
    if (!mSandbox) {
      nsIXPConnect* xpc = nsContentUtils::XPConnect();
      MOZ_ASSERT(xpc, "This should never be null!");

      // Let's use a null principal.
      nsCOMPtr<nsIPrincipal> principal =
          NullPrincipal::CreateWithoutOriginAttributes();

      JS::Rooted<JSObject*> sandbox(aCx);
      nsresult rv = xpc->CreateSandbox(aCx, principal, sandbox.address());
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return nullptr;
      }

      mSandbox = new JSObjectHolder(aCx, sandbox);
    }

    return mSandbox->GetJSObject();
  }

  RefPtr<JSObjectHolder> mSandbox;
};

class DeserializeIndexValueHelper final : public Runnable {
 public:
  DeserializeIndexValueHelper(int64_t aIndexID, const KeyPath& aKeyPath,
                              bool aUnique, bool aMultiEntry,
                              const nsCString& aLocale,
                              StructuredCloneReadInfo& aCloneReadInfo,
                              nsTArray<IndexUpdateInfo>& aUpdateInfoArray)
      : Runnable("DeserializeIndexValueHelper"),
        mMonitor("DeserializeIndexValueHelper::mMonitor"),
        mIndexID(aIndexID),
        mKeyPath(aKeyPath),
        mUnique(aUnique),
        mMultiEntry(aMultiEntry),
        mLocale(aLocale),
        mCloneReadInfo(aCloneReadInfo),
        mUpdateInfoArray(aUpdateInfoArray),
        mStatus(NS_ERROR_FAILURE) {}

  void DispatchAndWait(ErrorResult& aRv) {
    // We don't need to go to the main-thread and use the sandbox. Let's create
    // the updateInfo data here.
    if (!mCloneReadInfo.mData.Size()) {
      AutoJSAPI jsapi;
      jsapi.Init();

      JS::Rooted<JS::Value> value(jsapi.cx());
      value.setUndefined();

      IDBObjectStore::AppendIndexUpdateInfo(mIndexID, mKeyPath, mUnique,
                                            mMultiEntry, mLocale, jsapi.cx(),
                                            value, mUpdateInfoArray, aRv);
      return;
    }

    // The operation will continue on the main-thread.

    MOZ_ASSERT(!(mCloneReadInfo.mData.Size() % sizeof(uint64_t)));

    MonitorAutoLock lock(mMonitor);

    RefPtr<Runnable> self = this;
    const nsresult rv =
        SystemGroup::Dispatch(TaskCategory::Other, self.forget());
    if (NS_WARN_IF(NS_FAILED(rv))) {
      aRv.Throw(rv);
      return;
    }

    lock.Wait();
    aRv = mStatus;
  }

  NS_IMETHOD
  Run() override {
    MOZ_ASSERT(NS_IsMainThread());

    AutoJSAPI jsapi;
    jsapi.Init();
    JSContext* cx = jsapi.cx();

    JS::Rooted<JSObject*> global(cx, SandboxHolder::GetSandbox(cx));
    if (NS_WARN_IF(!global)) {
      OperationCompleted(NS_ERROR_FAILURE);
      return NS_OK;
    }

    JSAutoRealm ar(cx, global);

    JS::Rooted<JS::Value> value(cx);
    const nsresult rv = DeserializeIndexValue(cx, &value);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      OperationCompleted(rv);
      return NS_OK;
    }

    ErrorResult errorResult;
    IDBObjectStore::AppendIndexUpdateInfo(mIndexID, mKeyPath, mUnique,
                                          mMultiEntry, mLocale, cx, value,
                                          mUpdateInfoArray, errorResult);
    if (NS_WARN_IF(errorResult.Failed())) {
      OperationCompleted(errorResult.StealNSResult());
      return NS_OK;
    }

    OperationCompleted(NS_OK);
    return NS_OK;
  }

 private:
  nsresult DeserializeIndexValue(JSContext* aCx,
                                 JS::MutableHandle<JS::Value> aValue) {
    static const JSStructuredCloneCallbacks callbacks = {
        CommonStructuredCloneReadCallback,
        nullptr,
        nullptr,
        nullptr,
        nullptr,
        nullptr,
        nullptr};

    if (!JS_ReadStructuredClone(
            aCx, mCloneReadInfo.mData, JS_STRUCTURED_CLONE_VERSION,
            JS::StructuredCloneScope::DifferentProcessForIndexedDB, aValue,
            &callbacks, &mCloneReadInfo)) {
      return NS_ERROR_DOM_DATA_CLONE_ERR;
    }

    return NS_OK;
  }

  void OperationCompleted(nsresult aStatus) {
    mStatus = aStatus;

    MonitorAutoLock lock(mMonitor);
    lock.Notify();
  }

  Monitor mMonitor;

  int64_t mIndexID;
  const KeyPath& mKeyPath;
  bool mUnique;
  bool mMultiEntry;
  const nsCString mLocale;
  StructuredCloneReadInfo& mCloneReadInfo;
  nsTArray<IndexUpdateInfo>& mUpdateInfoArray;
  nsresult mStatus;
};

class DeserializeUpgradeValueHelper final : public Runnable {
 public:
  explicit DeserializeUpgradeValueHelper(
      StructuredCloneReadInfo& aCloneReadInfo)
      : Runnable("DeserializeUpgradeValueHelper"),
        mMonitor("DeserializeUpgradeValueHelper::mMonitor"),
        mCloneReadInfo(aCloneReadInfo),
        mStatus(NS_ERROR_FAILURE) {}

  nsresult DispatchAndWait(nsAString& aFileIds) {
    // We don't need to go to the main-thread and use the sandbox.
    if (!mCloneReadInfo.mData.Size()) {
      PopulateFileIds(aFileIds);
      return NS_OK;
    }

    // The operation will continue on the main-thread.

    MOZ_ASSERT(!(mCloneReadInfo.mData.Size() % sizeof(uint64_t)));

    MonitorAutoLock lock(mMonitor);

    RefPtr<Runnable> self = this;
    nsresult rv = SystemGroup::Dispatch(TaskCategory::Other, self.forget());
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    lock.Wait();

    if (NS_FAILED(mStatus)) {
      return mStatus;
    }

    PopulateFileIds(aFileIds);
    return NS_OK;
  }

  NS_IMETHOD
  Run() override {
    MOZ_ASSERT(NS_IsMainThread());

    AutoJSAPI jsapi;
    jsapi.Init();
    JSContext* cx = jsapi.cx();

    JS::Rooted<JSObject*> global(cx, SandboxHolder::GetSandbox(cx));
    if (NS_WARN_IF(!global)) {
      OperationCompleted(NS_ERROR_FAILURE);
      return NS_OK;
    }

    JSAutoRealm ar(cx, global);

    JS::Rooted<JS::Value> value(cx);
    nsresult rv = DeserializeUpgradeValue(cx, &value);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      OperationCompleted(rv);
      return NS_OK;
    }

    OperationCompleted(NS_OK);
    return NS_OK;
  }

 private:
  nsresult DeserializeUpgradeValue(JSContext* aCx,
                                   JS::MutableHandle<JS::Value> aValue) {
    static const JSStructuredCloneCallbacks callbacks = {
        CommonStructuredCloneReadCallback,
        nullptr,
        nullptr,
        nullptr,
        nullptr,
        nullptr,
        nullptr};

    if (!JS_ReadStructuredClone(
            aCx, mCloneReadInfo.mData, JS_STRUCTURED_CLONE_VERSION,
            JS::StructuredCloneScope::DifferentProcessForIndexedDB, aValue,
            &callbacks, &mCloneReadInfo)) {
      return NS_ERROR_DOM_DATA_CLONE_ERR;
    }

    return NS_OK;
  }

  void PopulateFileIds(nsAString& aFileIds) {
    for (uint32_t count = mCloneReadInfo.mFiles.Length(), index = 0;
         index < count; index++) {
      StructuredCloneFile& file = mCloneReadInfo.mFiles[index];
      MOZ_ASSERT(file.mFileInfo);

      const int64_t id = file.mFileInfo->Id();

      if (index) {
        aFileIds.Append(' ');
      }
      aFileIds.AppendInt(file.mType == StructuredCloneFile::eBlob ? id : -id);
    }
  }

  void OperationCompleted(nsresult aStatus) {
    mStatus = aStatus;

    MonitorAutoLock lock(mMonitor);
    lock.Notify();
  }

  Monitor mMonitor;
  StructuredCloneReadInfo& mCloneReadInfo;
  nsresult mStatus;
};

}  // namespace

// static
void IDBObjectStore::DeserializeIndexValueToUpdateInfos(
    int64_t aIndexID, const KeyPath& aKeyPath, bool aUnique, bool aMultiEntry,
    const nsCString& aLocale, StructuredCloneReadInfo& aCloneReadInfo,
    nsTArray<IndexUpdateInfo>& aUpdateInfoArray, ErrorResult& aRv) {
  MOZ_ASSERT(!NS_IsMainThread());

  RefPtr<DeserializeIndexValueHelper> helper = new DeserializeIndexValueHelper(
      aIndexID, aKeyPath, aUnique, aMultiEntry, aLocale, aCloneReadInfo,
      aUpdateInfoArray);
  helper->DispatchAndWait(aRv);
}

// static
nsresult IDBObjectStore::DeserializeUpgradeValueToFileIds(
    StructuredCloneReadInfo& aCloneReadInfo, nsAString& aFileIds) {
  MOZ_ASSERT(!NS_IsMainThread());

  RefPtr<DeserializeUpgradeValueHelper> helper =
      new DeserializeUpgradeValueHelper(aCloneReadInfo);
  return helper->DispatchAndWait(aFileIds);
}

#ifdef DEBUG

void IDBObjectStore::AssertIsOnOwningThread() const {
  MOZ_ASSERT(mTransaction);
  mTransaction->AssertIsOnOwningThread();
}

#endif  // DEBUG

void IDBObjectStore::GetAddInfo(JSContext* aCx, ValueWrapper& aValueWrapper,
                                JS::Handle<JS::Value> aKeyVal,
                                StructuredCloneWriteInfo& aCloneWriteInfo,
                                Key& aKey,
                                nsTArray<IndexUpdateInfo>& aUpdateInfoArray,
                                ErrorResult& aRv) {
  // Return DATA_ERR if a key was passed in and this objectStore uses inline
  // keys.
  if (!aKeyVal.isUndefined() && HasValidKeyPath()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
    return;
  }

  bool isAutoIncrement = AutoIncrement();

  if (!HasValidKeyPath()) {
    // Out-of-line keys must be passed in.
    aKey.SetFromJSVal(aCx, aKeyVal, aRv);
    if (aRv.Failed()) {
      return;
    }
  } else if (!isAutoIncrement) {
    if (!aValueWrapper.Clone(aCx)) {
      aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
      return;
    }

    aRv = GetKeyPath().ExtractKey(aCx, aValueWrapper.Value(), aKey);
    if (aRv.Failed()) {
      return;
    }
  }

  // Return DATA_ERR if no key was specified this isn't an autoIncrement
  // objectStore.
  if (aKey.IsUnset() && !isAutoIncrement) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
    return;
  }

  // Figure out indexes and the index values to update here.

  if (mSpec->indexes().Length() && !aValueWrapper.Clone(aCx)) {
    aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
    return;
  }

  {
    const nsTArray<IndexMetadata>& indexes = mSpec->indexes();
    uint32_t idxCount = indexes.Length();

    aUpdateInfoArray.SetCapacity(idxCount);  // Pretty good estimate

    for (uint32_t idxIndex = 0; idxIndex < idxCount; idxIndex++) {
      const IndexMetadata& metadata = indexes[idxIndex];

      AppendIndexUpdateInfo(metadata.id(), metadata.keyPath(),
                            metadata.unique(), metadata.multiEntry(),
                            metadata.locale(), aCx, aValueWrapper.Value(),
                            aUpdateInfoArray, aRv);
      if (NS_WARN_IF(aRv.Failed())) {
        return;
      }
    }
  }

  if (isAutoIncrement && HasValidKeyPath()) {
    if (!aValueWrapper.Clone(aCx)) {
      aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
      return;
    }

    GetAddInfoClosure data(aCloneWriteInfo, aValueWrapper.Value());

    MOZ_ASSERT(aKey.IsUnset());

    aRv = GetKeyPath().ExtractOrCreateKey(aCx, aValueWrapper.Value(), aKey,
                                          &GetAddInfoCallback, &data);
  } else {
    GetAddInfoClosure data(aCloneWriteInfo, aValueWrapper.Value());

    aRv = GetAddInfoCallback(aCx, &data);
  }
}

already_AddRefed<IDBRequest> IDBObjectStore::AddOrPut(
    JSContext* aCx, ValueWrapper& aValueWrapper, JS::Handle<JS::Value> aKey,
    bool aOverwrite, bool aFromCursor, ErrorResult& aRv) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aCx);
  MOZ_ASSERT_IF(aFromCursor, aOverwrite);

  if (mTransaction->GetMode() == IDBTransaction::CLEANUP || mDeletedSpec) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return nullptr;
  }

  if (!mTransaction->IsOpen()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return nullptr;
  }

  if (!mTransaction->IsWriteAllowed()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
    return nullptr;
  }

  Key key;
  StructuredCloneWriteInfo cloneWriteInfo(mTransaction->Database());
  nsTArray<IndexUpdateInfo> updateInfo;

  GetAddInfo(aCx, aValueWrapper, aKey, cloneWriteInfo, key, updateInfo, aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  if (!mTransaction->IsOpen()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return nullptr;
  }

  // Check the size limit of the serialized message which mainly consists of
  // a StructuredCloneBuffer, an encoded object key, and the encoded index keys.
  // kMaxIDBMsgOverhead covers the minor stuff not included in this calculation
  // because the precise calculation would slow down this AddOrPut operation.
  static const size_t kMaxIDBMsgOverhead = 1024 * 1024;  // 1MB
  const uint32_t maximalSizeFromPref =
      IndexedDatabaseManager::MaxSerializedMsgSize();
  MOZ_ASSERT(maximalSizeFromPref > kMaxIDBMsgOverhead);
  const size_t kMaxMessageSize = maximalSizeFromPref - kMaxIDBMsgOverhead;

  size_t indexUpdateInfoSize = 0;
  for (size_t i = 0; i < updateInfo.Length(); i++) {
    indexUpdateInfoSize += updateInfo[i].value().GetBuffer().Length();
    indexUpdateInfoSize += updateInfo[i].localizedValue().GetBuffer().Length();
  }

  size_t messageSize = cloneWriteInfo.mCloneBuffer.data().Size() +
                       key.GetBuffer().Length() + indexUpdateInfoSize;

  if (messageSize > kMaxMessageSize) {
    IDB_REPORT_INTERNAL_ERR();
    aRv.ThrowDOMException(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
                          nsPrintfCString("The serialized value is too large"
                                          " (size=%zu bytes, max=%zu bytes).",
                                          messageSize, kMaxMessageSize));
    return nullptr;
  }

  ObjectStoreAddPutParams commonParams;
  commonParams.objectStoreId() = Id();
  commonParams.cloneInfo().data().data =
      std::move(cloneWriteInfo.mCloneBuffer.data());
  commonParams.cloneInfo().offsetToKeyProp() = cloneWriteInfo.mOffsetToKeyProp;
  commonParams.key() = key;
  commonParams.indexUpdateInfos().SwapElements(updateInfo);

  // Convert any blobs or mutable files into FileAddInfo.
  nsTArray<StructuredCloneFile>& files = cloneWriteInfo.mFiles;

  if (!files.IsEmpty()) {
    const uint32_t count = files.Length();

    FallibleTArray<FileAddInfo> fileAddInfos;
    if (NS_WARN_IF(!fileAddInfos.SetCapacity(count, fallible))) {
      aRv = NS_ERROR_OUT_OF_MEMORY;
      return nullptr;
    }

    IDBDatabase* database = mTransaction->Database();

    for (uint32_t index = 0; index < count; index++) {
      StructuredCloneFile& file = files[index];

      FileAddInfo* fileAddInfo = fileAddInfos.AppendElement(fallible);
      MOZ_ASSERT(fileAddInfo);

      switch (file.mType) {
        case StructuredCloneFile::eBlob: {
          MOZ_ASSERT(file.mBlob);
          MOZ_ASSERT(!file.mMutableFile);

          PBackgroundIDBDatabaseFileChild* fileActor =
              database->GetOrCreateFileActorForBlob(file.mBlob);
          if (NS_WARN_IF(!fileActor)) {
            IDB_REPORT_INTERNAL_ERR();
            aRv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
            return nullptr;
          }

          fileAddInfo->file() = fileActor;
          fileAddInfo->type() = StructuredCloneFile::eBlob;

          break;
        }

        case StructuredCloneFile::eMutableFile: {
          MOZ_ASSERT(file.mMutableFile);
          MOZ_ASSERT(!file.mBlob);

          PBackgroundMutableFileChild* mutableFileActor =
              file.mMutableFile->GetBackgroundActor();
          if (NS_WARN_IF(!mutableFileActor)) {
            IDB_REPORT_INTERNAL_ERR();
            aRv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
            return nullptr;
          }

          fileAddInfo->file() = mutableFileActor;
          fileAddInfo->type() = StructuredCloneFile::eMutableFile;

          break;
        }

        case StructuredCloneFile::eWasmBytecode:
        case StructuredCloneFile::eWasmCompiled: {
          MOZ_ASSERT(file.mBlob);
          MOZ_ASSERT(!file.mMutableFile);

          PBackgroundIDBDatabaseFileChild* fileActor =
              database->GetOrCreateFileActorForBlob(file.mBlob);
          if (NS_WARN_IF(!fileActor)) {
            IDB_REPORT_INTERNAL_ERR();
            aRv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
            return nullptr;
          }

          fileAddInfo->file() = fileActor;
          fileAddInfo->type() = file.mType;

          break;
        }

        default:
          MOZ_CRASH("Should never get here!");
      }
    }

    commonParams.fileAddInfos().SwapElements(fileAddInfos);
  }

  RequestParams params;
  if (aOverwrite) {
    params = ObjectStorePutParams(commonParams);
  } else {
    params = ObjectStoreAddParams(commonParams);
  }

  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
  MOZ_ASSERT(request);

  if (!aFromCursor) {
    if (aOverwrite) {
      IDB_LOG_MARK(
          "IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
          "database(%s).transaction(%s).objectStore(%s).put(%s)",
          "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.put()",
          IDB_LOG_ID_STRING(), mTransaction->LoggingSerialNumber(),
          request->LoggingSerialNumber(),
          IDB_LOG_STRINGIFY(mTransaction->Database()),
          IDB_LOG_STRINGIFY(mTransaction), IDB_LOG_STRINGIFY(this),
          IDB_LOG_STRINGIFY(key));
    } else {
      IDB_LOG_MARK(
          "IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
          "database(%s).transaction(%s).objectStore(%s).add(%s)",
          "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.add()",
          IDB_LOG_ID_STRING(), mTransaction->LoggingSerialNumber(),
          request->LoggingSerialNumber(),
          IDB_LOG_STRINGIFY(mTransaction->Database()),
          IDB_LOG_STRINGIFY(mTransaction), IDB_LOG_STRINGIFY(this),
          IDB_LOG_STRINGIFY(key));
    }
  }

  mTransaction->StartRequest(request, params);

  return request.forget();
}

already_AddRefed<IDBRequest> IDBObjectStore::GetAllInternal(
    bool aKeysOnly, JSContext* aCx, JS::Handle<JS::Value> aKey,
    const Optional<uint32_t>& aLimit, ErrorResult& aRv) {
  AssertIsOnOwningThread();

  if (mDeletedSpec) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return nullptr;
  }

  if (!mTransaction->IsOpen()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return nullptr;
  }

  RefPtr<IDBKeyRange> keyRange;
  IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange), aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  const int64_t id = Id();

  Maybe<SerializedKeyRange> optionalKeyRange;
  if (keyRange) {
    SerializedKeyRange serializedKeyRange;
    keyRange->ToSerialized(serializedKeyRange);
    optionalKeyRange.emplace(serializedKeyRange);
  }

  const uint32_t limit = aLimit.WasPassed() ? aLimit.Value() : 0;

  RequestParams params;
  if (aKeysOnly) {
    params = ObjectStoreGetAllKeysParams(id, optionalKeyRange, limit);
  } else {
    params = ObjectStoreGetAllParams(id, optionalKeyRange, limit);
  }

  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
  MOZ_ASSERT(request);

  if (aKeysOnly) {
    IDB_LOG_MARK(
        "IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
        "database(%s).transaction(%s).objectStore(%s)."
        "getAllKeys(%s, %s)",
        "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.getAllKeys()",
        IDB_LOG_ID_STRING(), mTransaction->LoggingSerialNumber(),
        request->LoggingSerialNumber(),
        IDB_LOG_STRINGIFY(mTransaction->Database()),
        IDB_LOG_STRINGIFY(mTransaction), IDB_LOG_STRINGIFY(this),
        IDB_LOG_STRINGIFY(keyRange), IDB_LOG_STRINGIFY(aLimit));
  } else {
    IDB_LOG_MARK(
        "IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
        "database(%s).transaction(%s).objectStore(%s)."
        "getAll(%s, %s)",
        "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.getAll()",
        IDB_LOG_ID_STRING(), mTransaction->LoggingSerialNumber(),
        request->LoggingSerialNumber(),
        IDB_LOG_STRINGIFY(mTransaction->Database()),
        IDB_LOG_STRINGIFY(mTransaction), IDB_LOG_STRINGIFY(this),
        IDB_LOG_STRINGIFY(keyRange), IDB_LOG_STRINGIFY(aLimit));
  }

  mTransaction->StartRequest(request, params);

  return request.forget();
}

already_AddRefed<IDBRequest> IDBObjectStore::Clear(JSContext* aCx,
                                                   ErrorResult& aRv) {
  AssertIsOnOwningThread();

  if (mDeletedSpec) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return nullptr;
  }

  if (!mTransaction->IsOpen()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return nullptr;
  }

  if (!mTransaction->IsWriteAllowed()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
    return nullptr;
  }

  ObjectStoreClearParams params;
  params.objectStoreId() = Id();

  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
  MOZ_ASSERT(request);

  IDB_LOG_MARK(
      "IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
      "database(%s).transaction(%s).objectStore(%s).clear()",
      "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.clear()",
      IDB_LOG_ID_STRING(), mTransaction->LoggingSerialNumber(),
      request->LoggingSerialNumber(),
      IDB_LOG_STRINGIFY(mTransaction->Database()),
      IDB_LOG_STRINGIFY(mTransaction), IDB_LOG_STRINGIFY(this));

  mTransaction->StartRequest(request, params);

  return request.forget();
}

already_AddRefed<IDBIndex> IDBObjectStore::Index(const nsAString& aName,
                                                 ErrorResult& aRv) {
  AssertIsOnOwningThread();

  if (mTransaction->IsCommittingOrDone() || mDeletedSpec) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return nullptr;
  }

  const nsTArray<IndexMetadata>& indexes = mSpec->indexes();

  const IndexMetadata* metadata = nullptr;

  for (uint32_t idxCount = indexes.Length(), idxIndex = 0; idxIndex < idxCount;
       idxIndex++) {
    const IndexMetadata& index = indexes[idxIndex];
    if (index.name() == aName) {
      metadata = &index;
      break;
    }
  }

  if (!metadata) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
    return nullptr;
  }

  const int64_t desiredId = metadata->id();

  RefPtr<IDBIndex> index;

  for (uint32_t idxCount = mIndexes.Length(), idxIndex = 0; idxIndex < idxCount;
       idxIndex++) {
    RefPtr<IDBIndex>& existingIndex = mIndexes[idxIndex];

    if (existingIndex->Id() == desiredId) {
      index = existingIndex;
      break;
    }
  }

  if (!index) {
    index = IDBIndex::Create(this, *metadata);
    MOZ_ASSERT(index);

    mIndexes.AppendElement(index);
  }

  return index.forget();
}

NS_IMPL_CYCLE_COLLECTION_CLASS(IDBObjectStore)

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(IDBObjectStore)
  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedKeyPath)
NS_IMPL_CYCLE_COLLECTION_TRACE_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IDBObjectStore)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexes)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeletedIndexes)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IDBObjectStore)
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER

  // Don't unlink mTransaction!

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexes)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDeletedIndexes)

  tmp->mCachedKeyPath.setUndefined();

  if (tmp->mRooted) {
    mozilla::DropJSObjects(tmp);
    tmp->mRooted = false;
  }
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBObjectStore)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(IDBObjectStore)
NS_IMPL_CYCLE_COLLECTING_RELEASE(IDBObjectStore)

JSObject* IDBObjectStore::WrapObject(JSContext* aCx,
                                     JS::Handle<JSObject*> aGivenProto) {
  return IDBObjectStore_Binding::Wrap(aCx, this, aGivenProto);
}

nsIGlobalObject* IDBObjectStore::GetParentObject() const {
  return mTransaction->GetParentObject();
}

void IDBObjectStore::GetKeyPath(JSContext* aCx,
                                JS::MutableHandle<JS::Value> aResult,
                                ErrorResult& aRv) {
  if (!mCachedKeyPath.isUndefined()) {
    aResult.set(mCachedKeyPath);
    return;
  }

  aRv = GetKeyPath().ToJSVal(aCx, mCachedKeyPath);
  if (NS_WARN_IF(aRv.Failed())) {
    return;
  }

  if (mCachedKeyPath.isGCThing()) {
    mozilla::HoldJSObjects(this);
    mRooted = true;
  }

  aResult.set(mCachedKeyPath);
}

already_AddRefed<DOMStringList> IDBObjectStore::IndexNames() {
  AssertIsOnOwningThread();

  const nsTArray<IndexMetadata>& indexes = mSpec->indexes();

  RefPtr<DOMStringList> list = new DOMStringList();

  if (!indexes.IsEmpty()) {
    nsTArray<nsString>& listNames = list->StringArray();
    listNames.SetCapacity(indexes.Length());

    for (uint32_t index = 0; index < indexes.Length(); index++) {
      listNames.InsertElementSorted(indexes[index].name());
    }
  }

  return list.forget();
}

already_AddRefed<IDBRequest> IDBObjectStore::GetInternal(
    bool aKeyOnly, JSContext* aCx, JS::Handle<JS::Value> aKey,
    ErrorResult& aRv) {
  AssertIsOnOwningThread();

  if (mDeletedSpec) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return nullptr;
  }

  if (!mTransaction->IsOpen()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return nullptr;
  }

  RefPtr<IDBKeyRange> keyRange;
  IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange), aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  if (!keyRange) {
    // Must specify a key or keyRange for get().
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_KEY_ERR);
    return nullptr;
  }

  const int64_t id = Id();

  SerializedKeyRange serializedKeyRange;
  keyRange->ToSerialized(serializedKeyRange);

  RequestParams params;
  if (aKeyOnly) {
    params = ObjectStoreGetKeyParams(id, serializedKeyRange);
  } else {
    params = ObjectStoreGetParams(id, serializedKeyRange);
  }

  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
  MOZ_ASSERT(request);

  IDB_LOG_MARK(
      "IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
      "database(%s).transaction(%s).objectStore(%s).get(%s)",
      "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.get()",
      IDB_LOG_ID_STRING(), mTransaction->LoggingSerialNumber(),
      request->LoggingSerialNumber(),
      IDB_LOG_STRINGIFY(mTransaction->Database()),
      IDB_LOG_STRINGIFY(mTransaction), IDB_LOG_STRINGIFY(this),
      IDB_LOG_STRINGIFY(keyRange));

  mTransaction->StartRequest(request, params);

  return request.forget();
}

already_AddRefed<IDBRequest> IDBObjectStore::DeleteInternal(
    JSContext* aCx, JS::Handle<JS::Value> aKey, bool aFromCursor,
    ErrorResult& aRv) {
  AssertIsOnOwningThread();

  if (mDeletedSpec) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return nullptr;
  }

  if (!mTransaction->IsOpen()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return nullptr;
  }

  if (!mTransaction->IsWriteAllowed()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
    return nullptr;
  }

  RefPtr<IDBKeyRange> keyRange;
  IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange), aRv);
  if (NS_WARN_IF((aRv.Failed()))) {
    return nullptr;
  }

  if (!keyRange) {
    // Must specify a key or keyRange for delete().
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_KEY_ERR);
    return nullptr;
  }

  ObjectStoreDeleteParams params;
  params.objectStoreId() = Id();
  keyRange->ToSerialized(params.keyRange());

  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
  MOZ_ASSERT(request);

  if (!aFromCursor) {
    IDB_LOG_MARK(
        "IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
        "database(%s).transaction(%s).objectStore(%s).delete(%s)",
        "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.delete()",
        IDB_LOG_ID_STRING(), mTransaction->LoggingSerialNumber(),
        request->LoggingSerialNumber(),
        IDB_LOG_STRINGIFY(mTransaction->Database()),
        IDB_LOG_STRINGIFY(mTransaction), IDB_LOG_STRINGIFY(this),
        IDB_LOG_STRINGIFY(keyRange));
  }

  mTransaction->StartRequest(request, params);

  return request.forget();
}

already_AddRefed<IDBIndex> IDBObjectStore::CreateIndex(
    const nsAString& aName, const StringOrStringSequence& aKeyPath,
    const IDBIndexParameters& aOptionalParameters, ErrorResult& aRv) {
  AssertIsOnOwningThread();

  if (mTransaction->GetMode() != IDBTransaction::VERSION_CHANGE ||
      mDeletedSpec) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return nullptr;
  }

  IDBTransaction* transaction = IDBTransaction::GetCurrent();
  if (!transaction || transaction != mTransaction || !transaction->IsOpen()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return nullptr;
  }

  auto& indexes = const_cast<nsTArray<IndexMetadata>&>(mSpec->indexes());
  for (uint32_t count = indexes.Length(), index = 0; index < count; index++) {
    if (aName == indexes[index].name()) {
      aRv.ThrowDOMException(
          NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR,
          nsPrintfCString("Index named '%s' already exists at index '%u'",
                          NS_ConvertUTF16toUTF8(aName).get(), index));
      return nullptr;
    }
  }

  KeyPath keyPath(0);
  if (aKeyPath.IsString()) {
    if (NS_FAILED(KeyPath::Parse(aKeyPath.GetAsString(), &keyPath)) ||
        !keyPath.IsValid()) {
      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
      return nullptr;
    }
  } else {
    MOZ_ASSERT(aKeyPath.IsStringSequence());
    if (aKeyPath.GetAsStringSequence().IsEmpty() ||
        NS_FAILED(KeyPath::Parse(aKeyPath.GetAsStringSequence(), &keyPath)) ||
        !keyPath.IsValid()) {
      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
      return nullptr;
    }
  }

  if (aOptionalParameters.mMultiEntry && keyPath.IsArray()) {
    aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
    return nullptr;
  }

#ifdef DEBUG
  for (uint32_t count = mIndexes.Length(), index = 0; index < count; index++) {
    MOZ_ASSERT(mIndexes[index]->Name() != aName);
  }
#endif

  const IndexMetadata* oldMetadataElements =
      indexes.IsEmpty() ? nullptr : indexes.Elements();

  // With this setup we only validate the passed in locale name by the time we
  // get to encoding Keys. Maybe we should do it here right away and error out.

  // Valid locale names are always ASCII as per BCP-47.
  nsCString locale = NS_LossyConvertUTF16toASCII(aOptionalParameters.mLocale);
  bool autoLocale = locale.EqualsASCII("auto");
  if (autoLocale) {
    locale = IndexedDatabaseManager::GetLocale();
  }

  IndexMetadata* metadata = indexes.AppendElement(
      IndexMetadata(transaction->NextIndexId(), nsString(aName), keyPath,
                    locale, aOptionalParameters.mUnique,
                    aOptionalParameters.mMultiEntry, autoLocale));

  if (oldMetadataElements && oldMetadataElements != indexes.Elements()) {
    MOZ_ASSERT(indexes.Length() > 1);

    // Array got moved, update the spec pointers for all live indexes.
    RefreshSpec(/* aMayDelete */ false);
  }

  transaction->CreateIndex(this, *metadata);

  RefPtr<IDBIndex> index = IDBIndex::Create(this, *metadata);
  MOZ_ASSERT(index);

  mIndexes.AppendElement(index);

  // Don't do this in the macro because we always need to increment the serial
  // number to keep in sync with the parent.
  const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();

  IDB_LOG_MARK(
      "IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
      "database(%s).transaction(%s).objectStore(%s).createIndex(%s)",
      "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.createIndex()",
      IDB_LOG_ID_STRING(), mTransaction->LoggingSerialNumber(),
      requestSerialNumber, IDB_LOG_STRINGIFY(mTransaction->Database()),
      IDB_LOG_STRINGIFY(mTransaction), IDB_LOG_STRINGIFY(this),
      IDB_LOG_STRINGIFY(index));

  return index.forget();
}

void IDBObjectStore::DeleteIndex(const nsAString& aName, ErrorResult& aRv) {
  AssertIsOnOwningThread();

  if (mTransaction->GetMode() != IDBTransaction::VERSION_CHANGE ||
      mDeletedSpec) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return;
  }

  IDBTransaction* transaction = IDBTransaction::GetCurrent();
  if (!transaction || transaction != mTransaction || !transaction->IsOpen()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return;
  }

  auto& metadataArray = const_cast<nsTArray<IndexMetadata>&>(mSpec->indexes());

  int64_t foundId = 0;

  for (uint32_t metadataCount = metadataArray.Length(), metadataIndex = 0;
       metadataIndex < metadataCount; metadataIndex++) {
    const IndexMetadata& metadata = metadataArray[metadataIndex];
    MOZ_ASSERT(metadata.id());

    if (aName == metadata.name()) {
      foundId = metadata.id();

      // Must do this before altering the metadata array!
      for (uint32_t indexCount = mIndexes.Length(), indexIndex = 0;
           indexIndex < indexCount; indexIndex++) {
        RefPtr<IDBIndex>& index = mIndexes[indexIndex];

        if (index->Id() == foundId) {
          index->NoteDeletion();

          RefPtr<IDBIndex>* deletedIndex = mDeletedIndexes.AppendElement();
          deletedIndex->swap(mIndexes[indexIndex]);

          mIndexes.RemoveElementAt(indexIndex);
          break;
        }
      }

      metadataArray.RemoveElementAt(metadataIndex);

      RefreshSpec(/* aMayDelete */ false);
      break;
    }
  }

  if (!foundId) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
    return;
  }

  // Don't do this in the macro because we always need to increment the serial
  // number to keep in sync with the parent.
  const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();

  IDB_LOG_MARK(
      "IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
      "database(%s).transaction(%s).objectStore(%s)."
      "deleteIndex(\"%s\")",
      "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.deleteIndex()",
      IDB_LOG_ID_STRING(), mTransaction->LoggingSerialNumber(),
      requestSerialNumber, IDB_LOG_STRINGIFY(mTransaction->Database()),
      IDB_LOG_STRINGIFY(mTransaction), IDB_LOG_STRINGIFY(this),
      NS_ConvertUTF16toUTF8(aName).get());

  transaction->DeleteIndex(this, foundId);
}

already_AddRefed<IDBRequest> IDBObjectStore::Count(JSContext* aCx,
                                                   JS::Handle<JS::Value> aKey,
                                                   ErrorResult& aRv) {
  AssertIsOnOwningThread();

  if (mDeletedSpec) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return nullptr;
  }

  if (!mTransaction->IsOpen()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return nullptr;
  }

  RefPtr<IDBKeyRange> keyRange;
  IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange), aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  ObjectStoreCountParams params;
  params.objectStoreId() = Id();

  if (keyRange) {
    SerializedKeyRange serializedKeyRange;
    keyRange->ToSerialized(serializedKeyRange);
    params.optionalKeyRange().emplace(serializedKeyRange);
  }

  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
  MOZ_ASSERT(request);

  IDB_LOG_MARK(
      "IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
      "database(%s).transaction(%s).objectStore(%s).count(%s)",
      "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.count()",
      IDB_LOG_ID_STRING(), mTransaction->LoggingSerialNumber(),
      request->LoggingSerialNumber(),
      IDB_LOG_STRINGIFY(mTransaction->Database()),
      IDB_LOG_STRINGIFY(mTransaction), IDB_LOG_STRINGIFY(this),
      IDB_LOG_STRINGIFY(keyRange));

  mTransaction->StartRequest(request, params);

  return request.forget();
}

already_AddRefed<IDBRequest> IDBObjectStore::OpenCursorInternal(
    bool aKeysOnly, JSContext* aCx, JS::Handle<JS::Value> aRange,
    IDBCursorDirection aDirection, ErrorResult& aRv) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aCx);

  if (mDeletedSpec) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return nullptr;
  }

  if (!mTransaction->IsOpen()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return nullptr;
  }

  RefPtr<IDBKeyRange> keyRange;
  IDBKeyRange::FromJSVal(aCx, aRange, getter_AddRefs(keyRange), aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  int64_t objectStoreId = Id();

  Maybe<SerializedKeyRange> optionalKeyRange;

  if (keyRange) {
    SerializedKeyRange serializedKeyRange;
    keyRange->ToSerialized(serializedKeyRange);

    optionalKeyRange.emplace(std::move(serializedKeyRange));
  }

  IDBCursor::Direction direction = IDBCursor::ConvertDirection(aDirection);

  OpenCursorParams params;
  if (aKeysOnly) {
    ObjectStoreOpenKeyCursorParams openParams;
    openParams.objectStoreId() = objectStoreId;
    openParams.optionalKeyRange() = std::move(optionalKeyRange);
    openParams.direction() = direction;

    params = std::move(openParams);
  } else {
    ObjectStoreOpenCursorParams openParams;
    openParams.objectStoreId() = objectStoreId;
    openParams.optionalKeyRange() = std::move(optionalKeyRange);
    openParams.direction() = direction;

    params = std::move(openParams);
  }

  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
  MOZ_ASSERT(request);

  if (aKeysOnly) {
    IDB_LOG_MARK(
        "IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
        "database(%s).transaction(%s).objectStore(%s)."
        "openKeyCursor(%s, %s)",
        "IndexedDB %s: C T[%lld] R[%llu]: "
        "IDBObjectStore.openKeyCursor()",
        IDB_LOG_ID_STRING(), mTransaction->LoggingSerialNumber(),
        request->LoggingSerialNumber(),
        IDB_LOG_STRINGIFY(mTransaction->Database()),
        IDB_LOG_STRINGIFY(mTransaction), IDB_LOG_STRINGIFY(this),
        IDB_LOG_STRINGIFY(keyRange), IDB_LOG_STRINGIFY(direction));
  } else {
    IDB_LOG_MARK(
        "IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
        "database(%s).transaction(%s).objectStore(%s)."
        "openCursor(%s, %s)",
        "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.openCursor()",
        IDB_LOG_ID_STRING(), mTransaction->LoggingSerialNumber(),
        request->LoggingSerialNumber(),
        IDB_LOG_STRINGIFY(mTransaction->Database()),
        IDB_LOG_STRINGIFY(mTransaction), IDB_LOG_STRINGIFY(this),
        IDB_LOG_STRINGIFY(keyRange), IDB_LOG_STRINGIFY(direction));
  }

  BackgroundCursorChild* actor =
      new BackgroundCursorChild(request, this, direction);

  mTransaction->OpenCursor(actor, params);

  return request.forget();
}

void IDBObjectStore::RefreshSpec(bool aMayDelete) {
  AssertIsOnOwningThread();
  MOZ_ASSERT_IF(mDeletedSpec, mSpec == mDeletedSpec);

  const DatabaseSpec* dbSpec = mTransaction->Database()->Spec();
  MOZ_ASSERT(dbSpec);

  const nsTArray<ObjectStoreSpec>& objectStores = dbSpec->objectStores();

  bool found = false;

  for (uint32_t objCount = objectStores.Length(), objIndex = 0;
       objIndex < objCount; objIndex++) {
    const ObjectStoreSpec& objSpec = objectStores[objIndex];

    if (objSpec.metadata().id() == Id()) {
      mSpec = &objSpec;

      for (uint32_t idxCount = mIndexes.Length(), idxIndex = 0;
           idxIndex < idxCount; idxIndex++) {
        mIndexes[idxIndex]->RefreshMetadata(aMayDelete);
      }

      for (uint32_t idxCount = mDeletedIndexes.Length(), idxIndex = 0;
           idxIndex < idxCount; idxIndex++) {
        mDeletedIndexes[idxIndex]->RefreshMetadata(false);
      }

      found = true;
      break;
    }
  }

  MOZ_ASSERT_IF(!aMayDelete && !mDeletedSpec, found);

  if (found) {
    MOZ_ASSERT(mSpec != mDeletedSpec);
    mDeletedSpec = nullptr;
  } else {
    NoteDeletion();
  }
}

const ObjectStoreSpec& IDBObjectStore::Spec() const {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mSpec);

  return *mSpec;
}

void IDBObjectStore::NoteDeletion() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mSpec);
  MOZ_ASSERT(Id() == mSpec->metadata().id());

  if (mDeletedSpec) {
    MOZ_ASSERT(mDeletedSpec == mSpec);
    return;
  }

  // Copy the spec here.
  mDeletedSpec = new ObjectStoreSpec(*mSpec);
  mDeletedSpec->indexes().Clear();

  mSpec = mDeletedSpec;

  if (!mIndexes.IsEmpty()) {
    for (uint32_t count = mIndexes.Length(), index = 0; index < count;
         index++) {
      mIndexes[index]->NoteDeletion();
    }
  }
}

const nsString& IDBObjectStore::Name() const {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mSpec);

  return mSpec->metadata().name();
}

void IDBObjectStore::SetName(const nsAString& aName, ErrorResult& aRv) {
  AssertIsOnOwningThread();

  if (mTransaction->GetMode() != IDBTransaction::VERSION_CHANGE ||
      mDeletedSpec) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return;
  }

  IDBTransaction* transaction = IDBTransaction::GetCurrent();
  if (!transaction || transaction != mTransaction || !transaction->IsOpen()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return;
  }

  if (aName == mSpec->metadata().name()) {
    return;
  }

  // Cache logging string of this object store before renaming.
  const LoggingString loggingOldObjectStore(this);

  nsresult rv =
      transaction->Database()->RenameObjectStore(mSpec->metadata().id(), aName);

  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
    return;
  }

  // Don't do this in the macro because we always need to increment the serial
  // number to keep in sync with the parent.
  const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();

  IDB_LOG_MARK(
      "IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
      "database(%s).transaction(%s).objectStore(%s).rename(%s)",
      "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.rename()",
      IDB_LOG_ID_STRING(), mTransaction->LoggingSerialNumber(),
      requestSerialNumber, IDB_LOG_STRINGIFY(mTransaction->Database()),
      IDB_LOG_STRINGIFY(mTransaction), loggingOldObjectStore.get(),
      IDB_LOG_STRINGIFY(this));

  transaction->RenameObjectStore(mSpec->metadata().id(), aName);
}

bool IDBObjectStore::AutoIncrement() const {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mSpec);

  return mSpec->metadata().autoIncrement();
}

const indexedDB::KeyPath& IDBObjectStore::GetKeyPath() const {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mSpec);

  return mSpec->metadata().keyPath();
}

bool IDBObjectStore::HasValidKeyPath() const {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mSpec);

  return GetKeyPath().IsValid();
}

bool IDBObjectStore::ValueWrapper::Clone(JSContext* aCx) {
  if (mCloned) {
    return true;
  }

  static const JSStructuredCloneCallbacks callbacks = {
      CopyingStructuredCloneReadCallback /* read */,
      CopyingStructuredCloneWriteCallback /* write */,
      nullptr /* reportError */,
      nullptr /* readTransfer */,
      nullptr /* writeTransfer */,
      nullptr /* freeTransfer */,
      nullptr /* canTransfer */
  };

  StructuredCloneInfo cloneInfo;

  JS::Rooted<JS::Value> clonedValue(aCx);
  if (!JS_StructuredClone(aCx, mValue, &clonedValue, &callbacks, &cloneInfo)) {
    return false;
  }

  mValue = clonedValue;

  mCloned = true;

  return true;
}

}  // namespace dom
}  // namespace mozilla