/* -*- 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 GenerateRequest(JSContext* aCx, IDBIndex* aIndex) { MOZ_ASSERT(aIndex); aIndex->AssertIsOnOwningThread(); IDBTransaction* const transaction = aIndex->ObjectStore()->Transaction(); RefPtr 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::Create(IDBObjectStore* aObjectStore, const IndexMetadata& aMetadata) { MOZ_ASSERT(aObjectStore); aObjectStore->AssertIsOnOwningThread(); RefPtr 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 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); 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* const transaction = mObjectStore->Transaction(); if (transaction->GetMode() != IDBTransaction::Mode::VersionChange || mDeletedMetadata) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } if (!transaction->CanAcceptRequests()) { 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()", 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 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 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; } IDBTransaction* transaction = mObjectStore->Transaction(); if (!transaction->CanAcceptRequests()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } RefPtr 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 request = GenerateRequest(aCx, this); MOZ_ASSERT(request); if (aKeyOnly) { IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( "database(%s).transaction(%s).objectStore(%s).index(%s)." "getKey(%s)", "IDBIndex.getKey()", 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()", 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)); } // 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). transaction->InvalidateCursorCaches(); transaction->StartRequest(request, params); return request.forget(); } already_AddRefed 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; } IDBTransaction* transaction = mObjectStore->Transaction(); if (!transaction->CanAcceptRequests()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } RefPtr 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 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)}; RefPtr request = GenerateRequest(aCx, this); MOZ_ASSERT(request); if (aKeysOnly) { IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( "database(%s).transaction(%s).objectStore(%s).index(%s)." "getAllKeys(%s, %s)", "IDBIndex.getAllKeys()", 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()", 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)); } // 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). transaction->InvalidateCursorCaches(); transaction->StartRequest(request, params); return request.forget(); } already_AddRefed 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; } IDBTransaction* transaction = mObjectStore->Transaction(); if (!transaction->CanAcceptRequests()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } RefPtr keyRange; IDBKeyRange::FromJSVal(aCx, aRange, getter_AddRefs(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 IDBCursor::Direction direction = IDBCursor::ConvertDirection(aDirection); const CommonIndexOpenCursorParams commonIndexParams = { {objectStoreId, std::move(optionalKeyRange), direction}, indexId}; const auto params = aKeysOnly ? OpenCursorParams{IndexOpenKeyCursorParams{commonIndexParams}} : OpenCursorParams{IndexOpenCursorParams{commonIndexParams}}; RefPtr request = GenerateRequest(aCx, this); MOZ_ASSERT(request); if (aKeysOnly) { IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( "database(%s).transaction(%s).objectStore(%s).index(%s)." "openKeyCursor(%s, %s)", "IDBIndex.openKeyCursor()", 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_CHILD_TRANSACTION_REQUEST( "database(%s).transaction(%s).objectStore(%s).index(%s)." "openCursor(%s, %s)", "IDBIndex.openCursor()", 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* const actor = new BackgroundCursorChild(request, this, direction); // 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). transaction->InvalidateCursorCaches(); mObjectStore->Transaction()->OpenCursor(actor, params); return request.forget(); } already_AddRefed IDBIndex::Count(JSContext* aCx, JS::Handle aKey, ErrorResult& aRv) { AssertIsOnOwningThread(); if (mDeletedMetadata) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); return nullptr; } IDBTransaction* const transaction = mObjectStore->Transaction(); if (!transaction->CanAcceptRequests()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } RefPtr 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 request = GenerateRequest(aCx, this); MOZ_ASSERT(request); IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( "database(%s).transaction(%s).objectStore(%s).index(%s)." "count(%s)", "IDBIndex.count()", 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)); // 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). transaction->InvalidateCursorCaches(); 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 aGivenProto) { return IDBIndex_Binding::Wrap(aCx, this, aGivenProto); } } // namespace dom } // namespace mozilla