/* -*- 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 "IDBDatabase.h" #include "IDBEvents.h" #include "IDBFactory.h" #include "IDBIndex.h" #include "IDBMutableFile.h" #include "IDBObjectStore.h" #include "IDBRequest.h" #include "IDBTransaction.h" #include "IndexedDatabaseInlines.h" #include "IndexedDatabaseManager.h" #include "IndexedDBCommon.h" #include "mozilla/ErrorResult.h" #include "mozilla/EventDispatcher.h" #include "MainThreadUtils.h" #include "mozilla/ResultExtensions.h" #include "mozilla/Services.h" #include "mozilla/storage.h" #include "mozilla/Telemetry.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/DOMStringListBinding.h" #include "mozilla/dom/Exceptions.h" #include "mozilla/dom/File.h" #include "mozilla/dom/IDBDatabaseBinding.h" #include "mozilla/dom/IDBObjectStoreBinding.h" #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileChild.h" #include "mozilla/dom/indexedDB/PBackgroundIDBSharedTypes.h" #include "mozilla/dom/IPCBlobUtils.h" #include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/dom/quota/ResultExtensions.h" #include "mozilla/ipc/BackgroundChild.h" #include "mozilla/ipc/BackgroundUtils.h" #include "mozilla/ipc/FileDescriptor.h" #include "mozilla/ipc/InputStreamParams.h" #include "mozilla/ipc/InputStreamUtils.h" #include "nsCOMPtr.h" #include "mozilla/dom/Document.h" #include "nsIObserver.h" #include "nsIObserverService.h" #include "nsIScriptError.h" #include "nsISupportsPrimitives.h" #include "nsThreadUtils.h" #include "nsIWeakReferenceUtils.h" #include "ProfilerHelpers.h" #include "ReportInternalError.h" #include "ScriptErrorHelper.h" #include "nsQueryObject.h" // Include this last to avoid path problems on Windows. #include "ActorsChild.h" namespace mozilla::dom { using namespace mozilla::dom::indexedDB; using namespace mozilla::dom::quota; using namespace mozilla::ipc; using namespace mozilla::services; namespace { const char kCycleCollectionObserverTopic[] = "cycle-collector-end"; const char kMemoryPressureObserverTopic[] = "memory-pressure"; const char kWindowObserverTopic[] = "inner-window-destroyed"; class CancelableRunnableWrapper final : public CancelableRunnable { nsCOMPtr mRunnable; public: explicit CancelableRunnableWrapper(nsCOMPtr aRunnable) : CancelableRunnable("dom::CancelableRunnableWrapper"), mRunnable(std::move(aRunnable)) { MOZ_ASSERT(mRunnable); } private: ~CancelableRunnableWrapper() = default; NS_DECL_NSIRUNNABLE nsresult Cancel() override; }; class DatabaseFile final : public PBackgroundIDBDatabaseFileChild { IDBDatabase* mDatabase; public: explicit DatabaseFile(IDBDatabase* aDatabase) : mDatabase(aDatabase) { MOZ_ASSERT(aDatabase); aDatabase->AssertIsOnOwningThread(); MOZ_COUNT_CTOR(DatabaseFile); } private: ~DatabaseFile() { MOZ_ASSERT(!mDatabase); MOZ_COUNT_DTOR(DatabaseFile); } virtual void ActorDestroy(ActorDestroyReason aWhy) override { MOZ_ASSERT(mDatabase); mDatabase->AssertIsOnOwningThread(); if (aWhy != Deletion) { RefPtr database = mDatabase; database->NoteFinishedFileActor(this); } #ifdef DEBUG mDatabase = nullptr; #endif } }; } // namespace class IDBDatabase::Observer final : public nsIObserver { IDBDatabase* mWeakDatabase; const uint64_t mWindowId; public: Observer(IDBDatabase* aDatabase, uint64_t aWindowId) : mWeakDatabase(aDatabase), mWindowId(aWindowId) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aDatabase); } void Revoke() { MOZ_ASSERT(NS_IsMainThread()); mWeakDatabase = nullptr; } NS_DECL_ISUPPORTS private: ~Observer() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!mWeakDatabase); } NS_DECL_NSIOBSERVER }; IDBDatabase::IDBDatabase(IDBOpenDBRequest* aRequest, SafeRefPtr aFactory, BackgroundDatabaseChild* aActor, UniquePtr aSpec) : DOMEventTargetHelper(aRequest), mFactory(std::move(aFactory)), mSpec(std::move(aSpec)), mBackgroundActor(aActor), mFileHandleDisabled(aRequest->IsFileHandleDisabled()), mClosed(false), mInvalidated(false), mQuotaExceeded(false), mIncreasedActiveDatabaseCount(false) { MOZ_ASSERT(aRequest); MOZ_ASSERT(mFactory); mFactory->AssertIsOnOwningThread(); MOZ_ASSERT(aActor); MOZ_ASSERT(mSpec); } IDBDatabase::~IDBDatabase() { AssertIsOnOwningThread(); MOZ_ASSERT(!mBackgroundActor); MOZ_ASSERT(!mIncreasedActiveDatabaseCount); } // static RefPtr IDBDatabase::Create(IDBOpenDBRequest* aRequest, SafeRefPtr aFactory, BackgroundDatabaseChild* aActor, UniquePtr aSpec) { MOZ_ASSERT(aRequest); MOZ_ASSERT(aFactory); aFactory->AssertIsOnOwningThread(); MOZ_ASSERT(aActor); MOZ_ASSERT(aSpec); RefPtr db = new IDBDatabase(aRequest, aFactory.clonePtr(), aActor, std::move(aSpec)); if (NS_IsMainThread()) { nsCOMPtr window = do_QueryInterface(aFactory->GetParentObject()); if (window) { uint64_t windowId = window->WindowID(); RefPtr observer = new Observer(db, windowId); nsCOMPtr obsSvc = GetObserverService(); MOZ_ASSERT(obsSvc); // This topic must be successfully registered. MOZ_ALWAYS_SUCCEEDS( obsSvc->AddObserver(observer, kWindowObserverTopic, false)); // These topics are not crucial. QM_WARNONLY_TRY(QM_TO_RESULT( obsSvc->AddObserver(observer, kCycleCollectionObserverTopic, false))); QM_WARNONLY_TRY(QM_TO_RESULT( obsSvc->AddObserver(observer, kMemoryPressureObserverTopic, false))); db->mObserver = std::move(observer); } } db->IncreaseActiveDatabaseCount(); return db; } #ifdef DEBUG void IDBDatabase::AssertIsOnOwningThread() const { MOZ_ASSERT(mFactory); mFactory->AssertIsOnOwningThread(); } #endif // DEBUG nsIEventTarget* IDBDatabase::EventTarget() const { AssertIsOnOwningThread(); return mFactory->EventTarget(); } void IDBDatabase::CloseInternal() { AssertIsOnOwningThread(); if (!mClosed) { mClosed = true; ExpireFileActors(/* aExpireAll */ true); if (mObserver) { mObserver->Revoke(); nsCOMPtr obsSvc = GetObserverService(); if (obsSvc) { // These might not have been registered. obsSvc->RemoveObserver(mObserver, kCycleCollectionObserverTopic); obsSvc->RemoveObserver(mObserver, kMemoryPressureObserverTopic); MOZ_ALWAYS_SUCCEEDS( obsSvc->RemoveObserver(mObserver, kWindowObserverTopic)); } mObserver = nullptr; } if (mBackgroundActor && !mInvalidated) { mBackgroundActor->SendClose(); } // Decrease the number of active databases right after the database is // closed. MaybeDecreaseActiveDatabaseCount(); } } void IDBDatabase::InvalidateInternal() { AssertIsOnOwningThread(); InvalidateMutableFiles(); AbortTransactions(/* aShouldWarn */ true); CloseInternal(); } void IDBDatabase::EnterSetVersionTransaction(uint64_t aNewVersion) { AssertIsOnOwningThread(); MOZ_ASSERT(aNewVersion); MOZ_ASSERT(!RunningVersionChangeTransaction()); MOZ_ASSERT(mSpec); MOZ_ASSERT(!mPreviousSpec); mPreviousSpec = MakeUnique(*mSpec); mSpec->metadata().version() = aNewVersion; } void IDBDatabase::ExitSetVersionTransaction() { AssertIsOnOwningThread(); if (mPreviousSpec) { mPreviousSpec = nullptr; } } void IDBDatabase::RevertToPreviousState() { AssertIsOnOwningThread(); MOZ_ASSERT(RunningVersionChangeTransaction()); MOZ_ASSERT(mPreviousSpec); // Hold the current spec alive until RefreshTransactionsSpecEnumerator has // finished! auto currentSpec = std::move(mSpec); mSpec = std::move(mPreviousSpec); RefreshSpec(/* aMayDelete */ true); } void IDBDatabase::RefreshSpec(bool aMayDelete) { AssertIsOnOwningThread(); for (auto* weakTransaction : mTransactions) { const auto transaction = SafeRefPtr{weakTransaction, AcquireStrongRefFromRawPtr{}}; MOZ_ASSERT(transaction); transaction->AssertIsOnOwningThread(); transaction->RefreshSpec(aMayDelete); } } const nsString& IDBDatabase::Name() const { AssertIsOnOwningThread(); MOZ_ASSERT(mSpec); return mSpec->metadata().name(); } uint64_t IDBDatabase::Version() const { AssertIsOnOwningThread(); MOZ_ASSERT(mSpec); return mSpec->metadata().version(); } RefPtr IDBDatabase::ObjectStoreNames() const { AssertIsOnOwningThread(); MOZ_ASSERT(mSpec); return CreateSortedDOMStringList( mSpec->objectStores(), [](const auto& objectStore) { return objectStore.metadata().name(); }); } RefPtr IDBDatabase::GetOwnerDocument() const { if (nsPIDOMWindowInner* window = GetOwner()) { return window->GetExtantDoc(); } return nullptr; } RefPtr IDBDatabase::CreateObjectStore( const nsAString& aName, const IDBObjectStoreParameters& aOptionalParameters, ErrorResult& aRv) { AssertIsOnOwningThread(); const auto transaction = IDBTransaction::MaybeCurrent(); if (!transaction || transaction->Database() != this || transaction->GetMode() != IDBTransaction::Mode::VersionChange) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); return nullptr; } if (!transaction->IsActive()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } QM_INFOONLY_TRY_UNWRAP(const auto maybeKeyPath, KeyPath::Parse(aOptionalParameters.mKeyPath)); if (!maybeKeyPath) { aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); return nullptr; } const auto& keyPath = maybeKeyPath.ref(); auto& objectStores = mSpec->objectStores(); const auto end = objectStores.cend(); const auto foundIt = std::find_if( objectStores.cbegin(), end, [&aName](const auto& objectStore) { return aName == objectStore.metadata().name(); }); if (foundIt != end) { aRv.ThrowConstraintError(nsPrintfCString( "Object store named '%s' already exists at index '%zu'", NS_ConvertUTF16toUTF8(aName).get(), foundIt.GetIndex())); return nullptr; } if (!keyPath.IsAllowedForObjectStore(aOptionalParameters.mAutoIncrement)) { aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); return nullptr; } const ObjectStoreSpec* oldSpecElements = objectStores.IsEmpty() ? nullptr : objectStores.Elements(); ObjectStoreSpec* newSpec = objectStores.AppendElement(); newSpec->metadata() = ObjectStoreMetadata(transaction->NextObjectStoreId(), nsString(aName), keyPath, aOptionalParameters.mAutoIncrement); if (oldSpecElements && oldSpecElements != objectStores.Elements()) { MOZ_ASSERT(objectStores.Length() > 1); // Array got moved, update the spec pointers for all live objectStores and // indexes. RefreshSpec(/* aMayDelete */ false); } auto objectStore = transaction->CreateObjectStore(*newSpec); MOZ_ASSERT(objectStore); // 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).createObjectStore(%s)", "IDBDatabase.createObjectStore(%.0s%.0s%.0s)", transaction->LoggingSerialNumber(), requestSerialNumber, IDB_LOG_STRINGIFY(this), IDB_LOG_STRINGIFY(*transaction), IDB_LOG_STRINGIFY(objectStore)); return objectStore; } void IDBDatabase::DeleteObjectStore(const nsAString& aName, ErrorResult& aRv) { AssertIsOnOwningThread(); const auto transaction = IDBTransaction::MaybeCurrent(); if (!transaction || transaction->Database() != this || transaction->GetMode() != IDBTransaction::Mode::VersionChange) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); return; } if (!transaction->IsActive()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return; } auto& specArray = mSpec->objectStores(); const auto end = specArray.end(); const auto foundIt = std::find_if(specArray.begin(), end, [&aName](const auto& objectStore) { const ObjectStoreMetadata& metadata = objectStore.metadata(); MOZ_ASSERT(metadata.id()); return aName == metadata.name(); }); if (foundIt == end) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR); return; } // Must do this before altering the metadata array! transaction->DeleteObjectStore(foundIt->metadata().id()); specArray.RemoveElementAt(foundIt); RefreshSpec(/* aMayDelete */ false); // 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).deleteObjectStore(\"%s\")", "IDBDatabase.deleteObjectStore(%.0s%.0s%.0s)", transaction->LoggingSerialNumber(), requestSerialNumber, IDB_LOG_STRINGIFY(this), IDB_LOG_STRINGIFY(*transaction), NS_ConvertUTF16toUTF8(aName).get()); } RefPtr IDBDatabase::Transaction( JSContext* aCx, const StringOrStringSequence& aStoreNames, IDBTransactionMode aMode, ErrorResult& aRv) { AssertIsOnOwningThread(); if ((aMode == IDBTransactionMode::Readwriteflush || aMode == IDBTransactionMode::Cleanup) && !StaticPrefs::dom_indexedDB_experimental()) { // Pretend that this mode doesn't exist. We don't have a way to annotate // certain enum values as depending on preferences so we just duplicate the // normal exception generation here. aRv.ThrowTypeError("argument 2", "readwriteflush", "IDBTransactionMode"); return nullptr; } if (QuotaManager::IsShuttingDown()) { IDB_REPORT_INTERNAL_ERR(); aRv.ThrowUnknownError("Can't start IndexedDB transaction during shutdown"); return nullptr; } // https://w3c.github.io/IndexedDB/#dom-idbdatabase-transaction // Step 1. if (RunningVersionChangeTransaction()) { aRv.ThrowInvalidStateError( "Can't start a transaction while running an upgrade transaction"); return nullptr; } // Step 2. if (mClosed) { aRv.ThrowInvalidStateError( "Can't start a transaction on a closed database"); return nullptr; } // Step 3. AutoTArray stackSequence; if (aStoreNames.IsString()) { stackSequence.AppendElement(aStoreNames.GetAsString()); } else { MOZ_ASSERT(aStoreNames.IsStringSequence()); // Step 5, but it can be done before step 4 because those steps // can't both throw. if (aStoreNames.GetAsStringSequence().IsEmpty()) { aRv.ThrowInvalidAccessError("Empty scope passed in"); return nullptr; } } // Step 4. const nsTArray& storeNames = aStoreNames.IsString() ? stackSequence : static_cast&>( aStoreNames.GetAsStringSequence()); MOZ_ASSERT(!storeNames.IsEmpty()); const nsTArray& objectStores = mSpec->objectStores(); const uint32_t nameCount = storeNames.Length(); nsTArray sortedStoreNames; sortedStoreNames.SetCapacity(nameCount); // While collecting object store names, check if the corresponding object // stores actually exist. const auto begin = objectStores.cbegin(); const auto end = objectStores.cend(); for (const auto& name : storeNames) { const auto foundIt = std::find_if(begin, end, [&name](const auto& objectStore) { return objectStore.metadata().name() == name; }); if (foundIt == end) { Telemetry::ScalarAdd( storeNames.IsEmpty() ? Telemetry::ScalarID:: IDB_FAILURE_UNKNOWN_OBJECTSTORE_EMPTY_DATABASE : Telemetry::ScalarID:: IDB_FAILURE_UNKNOWN_OBJECTSTORE_NON_EMPTY_DATABASE, 1); // Not using nsPrintfCString in case "name" has embedded nulls. aRv.ThrowNotFoundError("'"_ns + NS_ConvertUTF16toUTF8(name) + "' is not a known object store name"_ns); return nullptr; } sortedStoreNames.EmplaceBack(name); } sortedStoreNames.Sort(); // Remove any duplicates. sortedStoreNames.SetLength( std::unique(sortedStoreNames.begin(), sortedStoreNames.end()).GetIndex()); IDBTransaction::Mode mode; switch (aMode) { case IDBTransactionMode::Readonly: mode = IDBTransaction::Mode::ReadOnly; break; case IDBTransactionMode::Readwrite: if (mQuotaExceeded) { mode = IDBTransaction::Mode::Cleanup; mQuotaExceeded = false; } else { mode = IDBTransaction::Mode::ReadWrite; } break; case IDBTransactionMode::Readwriteflush: mode = IDBTransaction::Mode::ReadWriteFlush; break; case IDBTransactionMode::Cleanup: mode = IDBTransaction::Mode::Cleanup; mQuotaExceeded = false; break; case IDBTransactionMode::Versionchange: // Step 6. aRv.ThrowTypeError("Invalid transaction mode"); return nullptr; default: MOZ_CRASH("Unknown mode!"); } SafeRefPtr transaction = IDBTransaction::Create(aCx, this, sortedStoreNames, mode); if (NS_WARN_IF(!transaction)) { IDB_REPORT_INTERNAL_ERR(); MOZ_ASSERT(!NS_IsMainThread(), "Transaction creation can only fail on workers"); aRv.ThrowUnknownError("Failed to create IndexedDB transaction on worker"); return nullptr; } BackgroundTransactionChild* actor = new BackgroundTransactionChild(transaction.clonePtr()); IDB_LOG_MARK_CHILD_TRANSACTION( "database(%s).transaction(%s)", "IDBDatabase.transaction(%.0s%.0s)", transaction->LoggingSerialNumber(), IDB_LOG_STRINGIFY(this), IDB_LOG_STRINGIFY(*transaction)); MOZ_ALWAYS_TRUE(mBackgroundActor->SendPBackgroundIDBTransactionConstructor( actor, sortedStoreNames, mode)); transaction->SetBackgroundActor(actor); if (mode == IDBTransaction::Mode::Cleanup) { ExpireFileActors(/* aExpireAll */ true); } return AsRefPtr(std::move(transaction)); } RefPtr IDBDatabase::CreateMutableFile( JSContext* aCx, const nsAString& aName, const Optional& aType, ErrorResult& aRv) { AssertIsOnOwningThread(); if (aName.IsEmpty()) { aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); return nullptr; } if (QuotaManager::IsShuttingDown()) { IDB_REPORT_INTERNAL_ERR(); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } if (mClosed || mFileHandleDisabled) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); return nullptr; } nsString type; if (aType.WasPassed()) { type = aType.Value(); } CreateFileParams params(nsString(aName), type); auto request = IDBRequest::Create(aCx, this, nullptr).unwrap(); BackgroundDatabaseRequestChild* actor = new BackgroundDatabaseRequestChild(this, request); IDB_LOG_MARK_CHILD_REQUEST( "database(%s).createMutableFile(%s)", "IDBDatabase.createMutableFile(%.0s%.0s)", request->LoggingSerialNumber(), IDB_LOG_STRINGIFY(this), NS_ConvertUTF16toUTF8(aName).get()); mBackgroundActor->SendPBackgroundIDBDatabaseRequestConstructor(actor, params); return request; } void IDBDatabase::RegisterTransaction(IDBTransaction& aTransaction) { AssertIsOnOwningThread(); aTransaction.AssertIsOnOwningThread(); MOZ_ASSERT(!mTransactions.Contains(&aTransaction)); mTransactions.Insert(&aTransaction); } void IDBDatabase::UnregisterTransaction(IDBTransaction& aTransaction) { AssertIsOnOwningThread(); aTransaction.AssertIsOnOwningThread(); MOZ_ASSERT(mTransactions.Contains(&aTransaction)); mTransactions.Remove(&aTransaction); } void IDBDatabase::AbortTransactions(bool aShouldWarn) { AssertIsOnOwningThread(); constexpr size_t StackExceptionLimit = 20; using StrongTransactionArray = AutoTArray, StackExceptionLimit>; using WeakTransactionArray = AutoTArray; if (!mTransactions.Count()) { // Return early as an optimization, the remainder is a no-op in this // case. return; } // XXX TransformIfIntoNewArray might be generalized to allow specifying the // type of nsTArray to create, so that it can create an AutoTArray as well; an // TransformIf (without AbortOnErr) might be added, which could be used here. StrongTransactionArray transactionsToAbort; transactionsToAbort.SetCapacity(mTransactions.Count()); for (IDBTransaction* const transaction : mTransactions) { MOZ_ASSERT(transaction); transaction->AssertIsOnOwningThread(); // Transactions that are already done can simply be ignored. Otherwise // there is a race here and it's possible that the transaction has not // been successfully committed yet so we will warn the user. if (!transaction->IsFinished()) { transactionsToAbort.EmplaceBack(transaction, AcquireStrongRefFromRawPtr{}); } } MOZ_ASSERT(transactionsToAbort.Length() <= mTransactions.Count()); if (transactionsToAbort.IsEmpty()) { // Return early as an optimization, the remainder is a no-op in this // case. return; } // We want to abort transactions as soon as possible so we iterate the // transactions once and abort them all first, collecting the transactions // that need to have a warning issued along the way. Those that need a // warning will be a subset of those that are aborted, so we don't need // additional strong references here. WeakTransactionArray transactionsThatNeedWarning; for (const auto& transaction : transactionsToAbort) { MOZ_ASSERT(transaction); MOZ_ASSERT(!transaction->IsFinished()); // We warn for any transactions that could have written data, but // ignore read-only transactions. if (aShouldWarn && transaction->IsWriteAllowed()) { transactionsThatNeedWarning.AppendElement(transaction.unsafeGetRawPtr()); } transaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR); } static const char kWarningMessage[] = "IndexedDBTransactionAbortNavigation"; for (IDBTransaction* transaction : transactionsThatNeedWarning) { MOZ_ASSERT(transaction); nsString filename; uint32_t lineNo, column; transaction->GetCallerLocation(filename, &lineNo, &column); LogWarning(kWarningMessage, filename, lineNo, column); } } PBackgroundIDBDatabaseFileChild* IDBDatabase::GetOrCreateFileActorForBlob( Blob& aBlob) { AssertIsOnOwningThread(); MOZ_ASSERT(mBackgroundActor); // We use the File's nsIWeakReference as the key to the table because // a) it is unique per blob, b) it is reference-counted so that we can // guarantee that it stays alive, and c) it doesn't hold the actual File // alive. nsWeakPtr weakRef = do_GetWeakReference(&aBlob); MOZ_ASSERT(weakRef); PBackgroundIDBDatabaseFileChild* actor = nullptr; if (!mFileActors.Get(weakRef, &actor)) { BlobImpl* blobImpl = aBlob.Impl(); MOZ_ASSERT(blobImpl); IPCBlob ipcBlob; nsresult rv = IPCBlobUtils::Serialize(blobImpl, ipcBlob); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } auto* dbFile = new DatabaseFile(this); actor = mBackgroundActor->SendPBackgroundIDBDatabaseFileConstructor( dbFile, ipcBlob); if (NS_WARN_IF(!actor)) { return nullptr; } mFileActors.InsertOrUpdate(weakRef, actor); } MOZ_ASSERT(actor); return actor; } void IDBDatabase::NoteFinishedFileActor( PBackgroundIDBDatabaseFileChild* aFileActor) { AssertIsOnOwningThread(); MOZ_ASSERT(aFileActor); mFileActors.RemoveIf([aFileActor](const auto& iter) { MOZ_ASSERT(iter.Key()); PBackgroundIDBDatabaseFileChild* actor = iter.Data(); MOZ_ASSERT(actor); return actor == aFileActor; }); } void IDBDatabase::NoteActiveTransaction() { AssertIsOnOwningThread(); MOZ_ASSERT(mFactory); // Increase the number of active transactions. mFactory->UpdateActiveTransactionCount(1); } void IDBDatabase::NoteInactiveTransaction() { AssertIsOnOwningThread(); if (!mBackgroundActor || !mFileActors.Count()) { MOZ_ASSERT(mFactory); mFactory->UpdateActiveTransactionCount(-1); return; } RefPtr runnable = NewRunnableMethod("IDBDatabase::NoteInactiveTransactionDelayed", this, &IDBDatabase::NoteInactiveTransactionDelayed); MOZ_ASSERT(runnable); if (!NS_IsMainThread()) { // Wrap as a nsICancelableRunnable to make workers happy. runnable = MakeRefPtr(runnable.forget()); } MOZ_ALWAYS_SUCCEEDS( EventTarget()->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL)); } nsresult IDBDatabase::GetQuotaInfo(nsACString& aOrigin, PersistenceType* aPersistenceType) { using mozilla::dom::quota::QuotaManager; MOZ_ASSERT(NS_IsMainThread(), "This can't work off the main thread!"); if (aPersistenceType) { *aPersistenceType = mSpec->metadata().persistenceType(); MOZ_ASSERT(*aPersistenceType != PERSISTENCE_TYPE_INVALID); } PrincipalInfo* principalInfo = mFactory->GetPrincipalInfo(); MOZ_ASSERT(principalInfo); switch (principalInfo->type()) { case PrincipalInfo::TNullPrincipalInfo: MOZ_CRASH("Is this needed?!"); case PrincipalInfo::TSystemPrincipalInfo: aOrigin = QuotaManager::GetOriginForChrome(); return NS_OK; case PrincipalInfo::TContentPrincipalInfo: { QM_TRY_UNWRAP(auto principal, PrincipalInfoToPrincipal(*principalInfo)); QM_TRY_UNWRAP(aOrigin, QuotaManager::GetOriginFromPrincipal(principal)); return NS_OK; } default: MOZ_CRASH("Unknown PrincipalInfo type!"); } MOZ_CRASH("Should never get here!"); } void IDBDatabase::ExpireFileActors(bool aExpireAll) { AssertIsOnOwningThread(); if (mBackgroundActor && mFileActors.Count()) { for (auto iter = mFileActors.Iter(); !iter.Done(); iter.Next()) { nsISupports* key = iter.Key(); PBackgroundIDBDatabaseFileChild* actor = iter.Data(); MOZ_ASSERT(key); MOZ_ASSERT(actor); bool shouldExpire = aExpireAll; if (!shouldExpire) { nsWeakPtr weakRef = do_QueryInterface(key); MOZ_ASSERT(weakRef); nsCOMPtr referent = do_QueryReferent(weakRef); shouldExpire = !referent; } if (shouldExpire) { PBackgroundIDBDatabaseFileChild::Send__delete__(actor); if (!aExpireAll) { iter.Remove(); } } } if (aExpireAll) { mFileActors.Clear(); } } else { MOZ_ASSERT(!mFileActors.Count()); } } void IDBDatabase::NoteLiveMutableFile(IDBMutableFile& aMutableFile) { AssertIsOnOwningThread(); aMutableFile.AssertIsOnOwningThread(); MOZ_ASSERT(!mLiveMutableFiles.Contains(&aMutableFile)); mLiveMutableFiles.AppendElement(WrapNotNullUnchecked(&aMutableFile)); } void IDBDatabase::NoteFinishedMutableFile(IDBMutableFile& aMutableFile) { AssertIsOnOwningThread(); aMutableFile.AssertIsOnOwningThread(); // It's ok if this is called after we cleared the array, so don't assert that // aMutableFile is in the list. mLiveMutableFiles.RemoveElement(&aMutableFile); } void IDBDatabase::InvalidateMutableFiles() { AssertIsOnOwningThread(); if (!mLiveMutableFiles.IsEmpty()) { for (uint32_t count = mLiveMutableFiles.Length(), index = 0; index < count; index++) { mLiveMutableFiles[index]->Invalidate(); } mLiveMutableFiles.Clear(); } } void IDBDatabase::Invalidate() { AssertIsOnOwningThread(); if (!mInvalidated) { mInvalidated = true; InvalidateInternal(); } } void IDBDatabase::NoteInactiveTransactionDelayed() { ExpireFileActors(/* aExpireAll */ false); MOZ_ASSERT(mFactory); mFactory->UpdateActiveTransactionCount(-1); } void IDBDatabase::LogWarning(const char* aMessageName, const nsAString& aFilename, uint32_t aLineNumber, uint32_t aColumnNumber) { AssertIsOnOwningThread(); MOZ_ASSERT(aMessageName); ScriptErrorHelper::DumpLocalizedMessage( nsDependentCString(aMessageName), aFilename, aLineNumber, aColumnNumber, nsIScriptError::warningFlag, mFactory->IsChrome(), mFactory->InnerWindowID()); } NS_IMPL_ADDREF_INHERITED(IDBDatabase, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(IDBDatabase, DOMEventTargetHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBDatabase) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_CLASS(IDBDatabase) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBDatabase, DOMEventTargetHelper) tmp->AssertIsOnOwningThread(); NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFactory) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBDatabase, DOMEventTargetHelper) tmp->AssertIsOnOwningThread(); // Don't unlink mFactory! // We've been unlinked, at the very least we should be able to prevent further // transactions from starting and unblock any other SetVersion callers. tmp->CloseInternal(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END void IDBDatabase::DisconnectFromOwner() { InvalidateInternal(); DOMEventTargetHelper::DisconnectFromOwner(); } void IDBDatabase::LastRelease() { AssertIsOnOwningThread(); CloseInternal(); ExpireFileActors(/* aExpireAll */ true); if (mBackgroundActor) { mBackgroundActor->SendDeleteMeInternal(); MOZ_ASSERT(!mBackgroundActor, "SendDeleteMeInternal should have cleared!"); } } JSObject* IDBDatabase::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return IDBDatabase_Binding::Wrap(aCx, this, aGivenProto); } NS_IMETHODIMP CancelableRunnableWrapper::Run() { const nsCOMPtr runnable = std::move(mRunnable); if (runnable) { return runnable->Run(); } return NS_OK; } nsresult CancelableRunnableWrapper::Cancel() { if (mRunnable) { mRunnable = nullptr; return NS_OK; } return NS_ERROR_UNEXPECTED; } NS_IMPL_ISUPPORTS(IDBDatabase::Observer, nsIObserver) NS_IMETHODIMP IDBDatabase::Observer::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aTopic); if (!strcmp(aTopic, kWindowObserverTopic)) { if (mWeakDatabase) { nsCOMPtr supportsInt = do_QueryInterface(aSubject); MOZ_ASSERT(supportsInt); uint64_t windowId; MOZ_ALWAYS_SUCCEEDS(supportsInt->GetData(&windowId)); if (windowId == mWindowId) { RefPtr database = mWeakDatabase; mWeakDatabase = nullptr; database->InvalidateInternal(); } } return NS_OK; } if (!strcmp(aTopic, kCycleCollectionObserverTopic) || !strcmp(aTopic, kMemoryPressureObserverTopic)) { if (mWeakDatabase) { RefPtr database = mWeakDatabase; database->ExpireFileActors(/* aExpireAll */ false); } return NS_OK; } NS_WARNING("Unknown observer topic!"); return NS_OK; } nsresult IDBDatabase::RenameObjectStore(int64_t aObjectStoreId, const nsAString& aName) { MOZ_ASSERT(mSpec); nsTArray& objectStores = mSpec->objectStores(); ObjectStoreSpec* foundObjectStoreSpec = nullptr; // Find the matched object store spec and check if 'aName' is already used by // another object store. for (auto& objSpec : objectStores) { const bool idIsCurrent = objSpec.metadata().id() == aObjectStoreId; if (idIsCurrent) { MOZ_ASSERT(!foundObjectStoreSpec); foundObjectStoreSpec = &objSpec; } if (objSpec.metadata().name() == aName) { if (idIsCurrent) { return NS_OK; } return NS_ERROR_DOM_INDEXEDDB_RENAME_OBJECT_STORE_ERR; } } MOZ_ASSERT(foundObjectStoreSpec); // Update the name of the matched object store. foundObjectStoreSpec->metadata().name().Assign(aName); return NS_OK; } nsresult IDBDatabase::RenameIndex(int64_t aObjectStoreId, int64_t aIndexId, const nsAString& aName) { MOZ_ASSERT(mSpec); nsTArray& objectStores = mSpec->objectStores(); ObjectStoreSpec* foundObjectStoreSpec = nullptr; // Find the matched index metadata and check if 'aName' is already used by // another index. for (uint32_t objCount = objectStores.Length(), objIndex = 0; objIndex < objCount; objIndex++) { const ObjectStoreSpec& objSpec = objectStores[objIndex]; if (objSpec.metadata().id() == aObjectStoreId) { foundObjectStoreSpec = &objectStores[objIndex]; break; } } MOZ_ASSERT(foundObjectStoreSpec); nsTArray& indexes = foundObjectStoreSpec->indexes(); IndexMetadata* foundIndexMetadata = nullptr; for (uint32_t idxCount = indexes.Length(), idxIndex = 0; idxIndex < idxCount; idxIndex++) { const IndexMetadata& metadata = indexes[idxIndex]; if (metadata.id() == aIndexId) { MOZ_ASSERT(!foundIndexMetadata); foundIndexMetadata = &indexes[idxIndex]; continue; } if (aName == metadata.name()) { return NS_ERROR_DOM_INDEXEDDB_RENAME_INDEX_ERR; } } MOZ_ASSERT(foundIndexMetadata); // Update the name of the matched object store. foundIndexMetadata->name() = nsString(aName); return NS_OK; } void IDBDatabase::IncreaseActiveDatabaseCount() { AssertIsOnOwningThread(); MOZ_ASSERT(mFactory); MOZ_ASSERT(!mIncreasedActiveDatabaseCount); mFactory->UpdateActiveDatabaseCount(1); mIncreasedActiveDatabaseCount = true; } void IDBDatabase::MaybeDecreaseActiveDatabaseCount() { AssertIsOnOwningThread(); if (mIncreasedActiveDatabaseCount) { // Decrease the number of active databases. MOZ_ASSERT(mFactory); mFactory->UpdateActiveDatabaseCount(-1); mIncreasedActiveDatabaseCount = false; } } } // namespace mozilla::dom