/* -*- 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 "IDBCursorType.h" #include "IDBDatabase.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::dom { using namespace mozilla::dom::indexedDB; namespace { MovingNotNull> GenerateRequest(JSContext* aCx, IDBIndex* aIndex) { MOZ_ASSERT(aIndex); aIndex->AssertIsOnOwningThread(); auto transaction = aIndex->ObjectStore()->AcquireTransaction(); auto* const database = transaction->Database(); return IDBRequest::Create(aCx, aIndex, database, std::move(transaction)); } } // 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) { mozilla::DropJSObjects(this); } } RefPtr IDBIndex::Create(IDBObjectStore* aObjectStore, const IndexMetadata& aMetadata) { MOZ_ASSERT(aObjectStore); aObjectStore->AssertIsOnOwningThread(); return new IDBIndex(aObjectStore, &aMetadata); } #ifdef DEBUG void IDBIndex::AssertIsOnOwningThread() const { MOZ_ASSERT(mObjectStore); mObjectStore->AssertIsOnOwningThread(); } #endif // DEBUG RefPtr IDBIndex::OpenCursor(JSContext* aCx, JS::Handle aRange, IDBCursorDirection aDirection, ErrorResult& aRv) { AssertIsOnOwningThread(); return OpenCursorInternal(/* aKeysOnly */ false, aCx, aRange, aDirection, aRv); } RefPtr IDBIndex::OpenKeyCursor(JSContext* aCx, JS::Handle aRange, IDBCursorDirection aDirection, ErrorResult& aRv) { AssertIsOnOwningThread(); return OpenCursorInternal(/* aKeysOnly */ true, aCx, aRange, aDirection, aRv); } RefPtr IDBIndex::Get(JSContext* aCx, JS::Handle aKey, ErrorResult& aRv) { AssertIsOnOwningThread(); return GetInternal(/* aKeyOnly */ false, aCx, aKey, aRv); } RefPtr IDBIndex::GetKey(JSContext* aCx, JS::Handle aKey, ErrorResult& aRv) { AssertIsOnOwningThread(); return GetInternal(/* aKeyOnly */ true, aCx, aKey, aRv); } RefPtr IDBIndex::GetAll(JSContext* aCx, JS::Handle aKey, const Optional& aLimit, ErrorResult& aRv) { AssertIsOnOwningThread(); return GetAllInternal(/* aKeysOnly */ false, aCx, aKey, aLimit, aRv); } RefPtr IDBIndex::GetAllKeys(JSContext* aCx, JS::Handle aKey, const Optional& aLimit, ErrorResult& aRv) { AssertIsOnOwningThread(); return GetAllInternal(/* aKeysOnly */ true, aCx, aKey, aLimit, aRv); } void IDBIndex::RefreshMetadata(bool aMayDelete) { AssertIsOnOwningThread(); MOZ_ASSERT_IF(mDeletedMetadata, mMetadata == mDeletedMetadata.get()); const auto& indexes = mObjectStore->Spec().indexes(); const auto foundIt = std::find_if( indexes.cbegin(), indexes.cend(), [id = Id()](const auto& metadata) { return metadata.id() == id; }); const bool found = foundIt != indexes.cend(); MOZ_ASSERT_IF(!aMayDelete && !mDeletedMetadata, found); if (found) { mMetadata = &*foundIt; MOZ_ASSERT(mMetadata != mDeletedMetadata.get()); mDeletedMetadata = nullptr; } else { NoteDeletion(); } } void IDBIndex::NoteDeletion() { AssertIsOnOwningThread(); MOZ_ASSERT(mMetadata); MOZ_ASSERT(Id() == mMetadata->id()); if (mDeletedMetadata) { MOZ_ASSERT(mMetadata == mDeletedMetadata.get()); return; } mDeletedMetadata = MakeUnique(*mMetadata); mMetadata = mDeletedMetadata.get(); } const nsString& IDBIndex::Name() const { AssertIsOnOwningThread(); MOZ_ASSERT(mMetadata); return mMetadata->name(); } void IDBIndex::SetName(const nsAString& aName, ErrorResult& aRv) { AssertIsOnOwningThread(); const auto& transaction = mObjectStore->TransactionRef(); if (transaction.GetMode() != IDBTransaction::Mode::VersionChange || mDeletedMetadata) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } if (!transaction.IsActive()) { 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_CHILD_TRANSACTION_REQUEST( "database(%s).transaction(%s).objectStore(%s).index(%s)." "rename(%s)", "IDBIndex.rename(%.0s%.0s%.0s%.0s%.0s)", transaction.LoggingSerialNumber(), requestSerialNumber, IDB_LOG_STRINGIFY(transaction.Database()), IDB_LOG_STRINGIFY(transaction), IDB_LOG_STRINGIFY(mObjectStore), loggingOldIndex.get(), IDB_LOG_STRINGIFY(this)); mObjectStore->MutableTransactionRef().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 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); } RefPtr IDBIndex::GetInternal(bool aKeyOnly, JSContext* aCx, JS::Handle aKey, ErrorResult& aRv) { AssertIsOnOwningThread(); if (mDeletedMetadata) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); return nullptr; } const auto& transaction = mObjectStore->TransactionRef(); if (!transaction.IsActive()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } RefPtr keyRange; IDBKeyRange::FromJSVal(aCx, aKey, &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); } auto request = GenerateRequest(aCx, this).unwrap(); if (aKeyOnly) { IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( "database(%s).transaction(%s).objectStore(%s).index(%s)." "getKey(%s)", "IDBIndex.getKey(%.0s%.0s%.0s%.0s%.0s)", 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_CHILD_TRANSACTION_REQUEST( "database(%s).transaction(%s).objectStore(%s).index(%s)." "get(%s)", "IDBIndex.get(%.0s%.0s%.0s%.0s%.0s)", 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)); } auto& mutableTransaction = mObjectStore->MutableTransactionRef(); // TODO: This is necessary to preserve request ordering only. Proper // sequencing of requests should be done in a more sophisticated manner that // doesn't require invalidating cursor caches (Bug 1580499). mutableTransaction.InvalidateCursorCaches(); mutableTransaction.StartRequest(request, params); return request; } RefPtr IDBIndex::GetAllInternal(bool aKeysOnly, JSContext* aCx, JS::Handle aKey, const Optional& aLimit, ErrorResult& aRv) { AssertIsOnOwningThread(); if (mDeletedMetadata) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); return nullptr; } const auto& transaction = mObjectStore->TransactionRef(); if (!transaction.IsActive()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } RefPtr keyRange; IDBKeyRange::FromJSVal(aCx, aKey, &keyRange, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } const int64_t objectStoreId = mObjectStore->Id(); const int64_t indexId = Id(); Maybe optionalKeyRange; if (keyRange) { SerializedKeyRange serializedKeyRange; keyRange->ToSerialized(serializedKeyRange); optionalKeyRange.emplace(serializedKeyRange); } const uint32_t limit = aLimit.WasPassed() ? aLimit.Value() : 0; const auto& params = aKeysOnly ? RequestParams{IndexGetAllKeysParams(objectStoreId, indexId, optionalKeyRange, limit)} : RequestParams{IndexGetAllParams(objectStoreId, indexId, optionalKeyRange, limit)}; auto request = GenerateRequest(aCx, this).unwrap(); if (aKeysOnly) { IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( "database(%s).transaction(%s).objectStore(%s).index(%s)." "getAllKeys(%s, %s)", "IDBIndex.getAllKeys(%.0s%.0s%.0s%.0s%.0s%.0s)", 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_CHILD_TRANSACTION_REQUEST( "database(%s).transaction(%s).objectStore(%s).index(%s)." "getAll(%s, %s)", "IDBIndex.getAll(%.0s%.0s%.0s%.0s%.0s%.0s)", 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)); } auto& mutableTransaction = mObjectStore->MutableTransactionRef(); // TODO: This is necessary to preserve request ordering only. Proper // sequencing of requests should be done in a more sophisticated manner that // doesn't require invalidating cursor caches (Bug 1580499). mutableTransaction.InvalidateCursorCaches(); mutableTransaction.StartRequest(request, params); return request; } RefPtr IDBIndex::OpenCursorInternal(bool aKeysOnly, JSContext* aCx, JS::Handle aRange, IDBCursorDirection aDirection, ErrorResult& aRv) { AssertIsOnOwningThread(); if (mDeletedMetadata) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); return nullptr; } const auto& transaction = mObjectStore->TransactionRef(); if (!transaction.IsActive()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } RefPtr keyRange; IDBKeyRange::FromJSVal(aCx, aRange, &keyRange, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } const int64_t objectStoreId = mObjectStore->Id(); const int64_t indexId = Id(); Maybe optionalKeyRange; if (keyRange) { SerializedKeyRange serializedKeyRange; keyRange->ToSerialized(serializedKeyRange); optionalKeyRange.emplace(std::move(serializedKeyRange)); } const CommonIndexOpenCursorParams commonIndexParams = { {objectStoreId, std::move(optionalKeyRange), aDirection}, indexId}; const auto params = aKeysOnly ? OpenCursorParams{IndexOpenKeyCursorParams{commonIndexParams}} : OpenCursorParams{IndexOpenCursorParams{commonIndexParams}}; auto request = GenerateRequest(aCx, this).unwrap(); if (aKeysOnly) { IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( "database(%s).transaction(%s).objectStore(%s).index(%s)." "openKeyCursor(%s, %s)", "IDBIndex.openKeyCursor(%.0s%.0s%.0s%.0s%.0s%.0s)", 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(aDirection)); } else { IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( "database(%s).transaction(%s).objectStore(%s).index(%s)." "openCursor(%s, %s)", "IDBIndex.openCursor(%.0s%.0s%.0s%.0s%.0s%.0s)", 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(aDirection)); } const auto actor = aKeysOnly ? static_cast>( MakeSafeRefPtr>( request, this, aDirection)) : MakeSafeRefPtr>( request, this, aDirection); auto& mutableTransaction = mObjectStore->MutableTransactionRef(); // TODO: This is necessary to preserve request ordering only. Proper // sequencing of requests should be done in a more sophisticated manner that // doesn't require invalidating cursor caches (Bug 1580499). mutableTransaction.InvalidateCursorCaches(); mutableTransaction.OpenCursor(*actor, params); return request; } RefPtr IDBIndex::Count(JSContext* aCx, JS::Handle aKey, ErrorResult& aRv) { AssertIsOnOwningThread(); if (mDeletedMetadata) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); return nullptr; } const auto& transaction = mObjectStore->TransactionRef(); if (!transaction.IsActive()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } RefPtr keyRange; IDBKeyRange::FromJSVal(aCx, aKey, &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); } auto request = GenerateRequest(aCx, this).unwrap(); IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( "database(%s).transaction(%s).objectStore(%s).index(%s)." "count(%s)", "IDBIndex.count(%.0s%.0s%.0s%.0s%.0s)", 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)); auto& mutableTransaction = mObjectStore->MutableTransactionRef(); // TODO: This is necessary to preserve request ordering only. Proper // sequencing of requests should be done in a more sophisticated manner that // doesn't require invalidating cursor caches (Bug 1580499). mutableTransaction.InvalidateCursorCaches(); mutableTransaction.StartRequest(request, params); return request; } 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 aGivenProto) { return IDBIndex_Binding::Wrap(aCx, this, aGivenProto); } } // namespace mozilla::dom