dom/indexedDB/IDBIndex.cpp
author Rob Wood <rwood@mozilla.com>
Tue, 25 Jun 2019 08:28:58 +0000
changeset 480057 d5b60a2a9cd9ebebe089503474ace5dd2c2eaf9a
parent 471541 e8c59393c80952adfffd0be0de34c3ae7e48e36a
child 489142 4f3b348afe01880d512d9fa5ad65caba8c199309
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 "IDBIndex.h"

#include "FileInfo.h"
#include "IDBCursor.h"
#include "IDBEvents.h"
#include "IDBKeyRange.h"
#include "IDBObjectStore.h"
#include "IDBRequest.h"
#include "IDBTransaction.h"
#include "IndexedDatabase.h"
#include "IndexedDatabaseInlines.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBSharedTypes.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;

namespace {

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

  IDBTransaction* transaction = aIndex->ObjectStore()->Transaction();

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

  return request.forget();
}

}  // namespace

IDBIndex::IDBIndex(IDBObjectStore* aObjectStore, const IndexMetadata* aMetadata)
    : mObjectStore(aObjectStore),
      mCachedKeyPath(JS::UndefinedValue()),
      mMetadata(aMetadata),
      mId(aMetadata->id()),
      mRooted(false) {
  MOZ_ASSERT(aObjectStore);
  aObjectStore->AssertIsOnOwningThread();
  MOZ_ASSERT(aMetadata);
}

IDBIndex::~IDBIndex() {
  AssertIsOnOwningThread();

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

already_AddRefed<IDBIndex> IDBIndex::Create(IDBObjectStore* aObjectStore,
                                            const IndexMetadata& aMetadata) {
  MOZ_ASSERT(aObjectStore);
  aObjectStore->AssertIsOnOwningThread();

  RefPtr<IDBIndex> index = new IDBIndex(aObjectStore, &aMetadata);

  return index.forget();
}

#ifdef DEBUG

void IDBIndex::AssertIsOnOwningThread() const {
  MOZ_ASSERT(mObjectStore);
  mObjectStore->AssertIsOnOwningThread();
}

#endif  // DEBUG

void IDBIndex::RefreshMetadata(bool aMayDelete) {
  AssertIsOnOwningThread();
  MOZ_ASSERT_IF(mDeletedMetadata, mMetadata == mDeletedMetadata);

  const nsTArray<IndexMetadata>& indexes = mObjectStore->Spec().indexes();

  bool found = false;

  for (uint32_t count = indexes.Length(), index = 0; index < count; index++) {
    const IndexMetadata& metadata = indexes[index];

    if (metadata.id() == Id()) {
      mMetadata = &metadata;

      found = true;
      break;
    }
  }

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

  if (found) {
    MOZ_ASSERT(mMetadata != mDeletedMetadata);
    mDeletedMetadata = nullptr;
  } else {
    NoteDeletion();
  }
}

void IDBIndex::NoteDeletion() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mMetadata);
  MOZ_ASSERT(Id() == mMetadata->id());

  if (mDeletedMetadata) {
    MOZ_ASSERT(mMetadata == mDeletedMetadata);
    return;
  }

  mDeletedMetadata = new IndexMetadata(*mMetadata);

  mMetadata = mDeletedMetadata;
}

const nsString& IDBIndex::Name() const {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mMetadata);

  return mMetadata->name();
}

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

  IDBTransaction* transaction = mObjectStore->Transaction();

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

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

  if (aName == mMetadata->name()) {
    return;
  }

  // Cache logging string of this index before renaming.
  const LoggingString loggingOldIndex(this);

  const int64_t indexId = Id();

  nsresult rv =
      transaction->Database()->RenameIndex(mObjectStore->Id(), indexId, 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).index(%s)."
      "rename(%s)",
      "IndexedDB %s: C T[%lld] R[%llu]: IDBIndex.rename()", IDB_LOG_ID_STRING(),
      transaction->LoggingSerialNumber(), requestSerialNumber,
      IDB_LOG_STRINGIFY(transaction->Database()),
      IDB_LOG_STRINGIFY(transaction), IDB_LOG_STRINGIFY(mObjectStore),
      loggingOldIndex.get(), IDB_LOG_STRINGIFY(this));

  transaction->RenameIndex(mObjectStore, indexId, aName);
}

bool IDBIndex::Unique() const {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mMetadata);

  return mMetadata->unique();
}

bool IDBIndex::MultiEntry() const {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mMetadata);

  return mMetadata->multiEntry();
}

bool IDBIndex::LocaleAware() const {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mMetadata);

  return mMetadata->locale().IsEmpty();
}

const indexedDB::KeyPath& IDBIndex::GetKeyPath() const {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mMetadata);

  return mMetadata->keyPath();
}

void IDBIndex::GetLocale(nsString& aLocale) const {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mMetadata);

  if (mMetadata->locale().IsEmpty()) {
    SetDOMStringToNull(aLocale);
  } else {
    CopyASCIItoUTF16(mMetadata->locale(), aLocale);
  }
}

const nsCString& IDBIndex::Locale() const {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mMetadata);

  return mMetadata->locale();
}

bool IDBIndex::IsAutoLocale() const {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mMetadata);

  return mMetadata->autoLocale();
}

nsIGlobalObject* IDBIndex::GetParentObject() const {
  AssertIsOnOwningThread();

  return mObjectStore->GetParentObject();
}

void IDBIndex::GetKeyPath(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
                          ErrorResult& aRv) {
  AssertIsOnOwningThread();

  if (!mCachedKeyPath.isUndefined()) {
    MOZ_ASSERT(mRooted);
    aResult.set(mCachedKeyPath);
    return;
  }

  MOZ_ASSERT(!mRooted);

  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<IDBRequest> IDBIndex::GetInternal(bool aKeyOnly,
                                                   JSContext* aCx,
                                                   JS::Handle<JS::Value> aKey,
                                                   ErrorResult& aRv) {
  AssertIsOnOwningThread();

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

  IDBTransaction* transaction = mObjectStore->Transaction();
  if (!transaction->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;
  }

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

  const int64_t objectStoreId = mObjectStore->Id();
  const int64_t indexId = Id();

  SerializedKeyRange serializedKeyRange;
  keyRange->ToSerialized(serializedKeyRange);

  RequestParams params;

  if (aKeyOnly) {
    params = IndexGetKeyParams(objectStoreId, indexId, serializedKeyRange);
  } else {
    params = IndexGetParams(objectStoreId, indexId, serializedKeyRange);
  }

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

  if (aKeyOnly) {
    IDB_LOG_MARK(
        "IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
        "database(%s).transaction(%s).objectStore(%s).index(%s)."
        "getKey(%s)",
        "IndexedDB %s: C T[%lld] R[%llu]: IDBIndex.getKey()",
        IDB_LOG_ID_STRING(), transaction->LoggingSerialNumber(),
        request->LoggingSerialNumber(),
        IDB_LOG_STRINGIFY(transaction->Database()),
        IDB_LOG_STRINGIFY(transaction), IDB_LOG_STRINGIFY(mObjectStore),
        IDB_LOG_STRINGIFY(this), IDB_LOG_STRINGIFY(keyRange));
  } else {
    IDB_LOG_MARK(
        "IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
        "database(%s).transaction(%s).objectStore(%s).index(%s)."
        "get(%s)",
        "IndexedDB %s: C T[%lld] R[%llu]: IDBIndex.get()", IDB_LOG_ID_STRING(),
        transaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
        IDB_LOG_STRINGIFY(transaction->Database()),
        IDB_LOG_STRINGIFY(transaction), IDB_LOG_STRINGIFY(mObjectStore),
        IDB_LOG_STRINGIFY(this), IDB_LOG_STRINGIFY(keyRange));
  }

  transaction->StartRequest(request, params);

  return request.forget();
}

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

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

  IDBTransaction* transaction = mObjectStore->Transaction();
  if (!transaction->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 objectStoreId = mObjectStore->Id();
  const int64_t indexId = 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 =
        IndexGetAllKeysParams(objectStoreId, indexId, optionalKeyRange, limit);
  } else {
    params = IndexGetAllParams(objectStoreId, indexId, 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).index(%s)."
        "getAllKeys(%s, %s)",
        "IndexedDB %s: C T[%lld] R[%llu]: IDBIndex.getAllKeys()",
        IDB_LOG_ID_STRING(), transaction->LoggingSerialNumber(),
        request->LoggingSerialNumber(),
        IDB_LOG_STRINGIFY(transaction->Database()),
        IDB_LOG_STRINGIFY(transaction), IDB_LOG_STRINGIFY(mObjectStore),
        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).index(%s)."
        "getAll(%s, %s)",
        "IndexedDB %s: C T[%lld] R[%llu]: IDBIndex.getAll()",
        IDB_LOG_ID_STRING(), transaction->LoggingSerialNumber(),
        request->LoggingSerialNumber(),
        IDB_LOG_STRINGIFY(transaction->Database()),
        IDB_LOG_STRINGIFY(transaction), IDB_LOG_STRINGIFY(mObjectStore),
        IDB_LOG_STRINGIFY(this), IDB_LOG_STRINGIFY(keyRange),
        IDB_LOG_STRINGIFY(aLimit));
  }

  transaction->StartRequest(request, params);

  return request.forget();
}

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

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

  IDBTransaction* transaction = mObjectStore->Transaction();
  if (!transaction->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 = mObjectStore->Id();
  int64_t indexId = 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) {
    IndexOpenKeyCursorParams openParams;
    openParams.objectStoreId() = objectStoreId;
    openParams.indexId() = indexId;
    openParams.optionalKeyRange() = std::move(optionalKeyRange);
    openParams.direction() = direction;

    params = std::move(openParams);
  } else {
    IndexOpenCursorParams openParams;
    openParams.objectStoreId() = objectStoreId;
    openParams.indexId() = indexId;
    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).index(%s)."
        "openKeyCursor(%s, %s)",
        "IndexedDB %s: C T[%lld] R[%llu]: IDBIndex.openKeyCursor()",
        IDB_LOG_ID_STRING(), transaction->LoggingSerialNumber(),
        request->LoggingSerialNumber(),
        IDB_LOG_STRINGIFY(transaction->Database()),
        IDB_LOG_STRINGIFY(transaction), IDB_LOG_STRINGIFY(mObjectStore),
        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).index(%s)."
        "openCursor(%s, %s)",
        "IndexedDB %s: C T[%lld] R[%llu]: "
        "IDBObjectStore.openKeyCursor()",
        IDB_LOG_ID_STRING(), transaction->LoggingSerialNumber(),
        request->LoggingSerialNumber(),
        IDB_LOG_STRINGIFY(transaction->Database()),
        IDB_LOG_STRINGIFY(transaction), IDB_LOG_STRINGIFY(mObjectStore),
        IDB_LOG_STRINGIFY(this), IDB_LOG_STRINGIFY(keyRange),
        IDB_LOG_STRINGIFY(direction));
  }

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

  mObjectStore->Transaction()->OpenCursor(actor, params);

  return request.forget();
}

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

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

  IDBTransaction* transaction = mObjectStore->Transaction();
  if (!transaction->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;
  }

  IndexCountParams params;
  params.objectStoreId() = mObjectStore->Id();
  params.indexId() = 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).index(%s)."
      "count(%s)",
      "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.count()",
      IDB_LOG_ID_STRING(), transaction->LoggingSerialNumber(),
      request->LoggingSerialNumber(),
      IDB_LOG_STRINGIFY(transaction->Database()),
      IDB_LOG_STRINGIFY(transaction), IDB_LOG_STRINGIFY(mObjectStore),
      IDB_LOG_STRINGIFY(this), IDB_LOG_STRINGIFY(keyRange));

  transaction->StartRequest(request, params);

  return request.forget();
}

NS_IMPL_CYCLE_COLLECTING_ADDREF(IDBIndex)
NS_IMPL_CYCLE_COLLECTING_RELEASE(IDBIndex)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBIndex)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTION_CLASS(IDBIndex)

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(IDBIndex)
  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(IDBIndex)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObjectStore)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IDBIndex)
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER

  // Don't unlink mObjectStore!

  tmp->mCachedKeyPath.setUndefined();

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

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

}  // namespace dom
}  // namespace mozilla