diff options
Diffstat (limited to 'dom/indexedDB/IDBDatabase.cpp')
-rw-r--r-- | dom/indexedDB/IDBDatabase.cpp | 1435 |
1 files changed, 1435 insertions, 0 deletions
diff --git a/dom/indexedDB/IDBDatabase.cpp b/dom/indexedDB/IDBDatabase.cpp new file mode 100644 index 000000000..5592e7f93 --- /dev/null +++ b/dom/indexedDB/IDBDatabase.cpp @@ -0,0 +1,1435 @@ +/* -*- 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 "FileInfo.h" +#include "IDBEvents.h" +#include "IDBFactory.h" +#include "IDBIndex.h" +#include "IDBMutableFile.h" +#include "IDBObjectStore.h" +#include "IDBRequest.h" +#include "IDBTransaction.h" +#include "IDBFactory.h" +#include "IndexedDatabaseManager.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/EventDispatcher.h" +#include "MainThreadUtils.h" +#include "mozilla/Services.h" +#include "mozilla/storage.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/DOMStringList.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/ipc/BackgroundChild.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/dom/ipc/BlobChild.h" +#include "mozilla/dom/ipc/nsIRemoteBlob.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/ipc/FileDescriptor.h" +#include "mozilla/ipc/InputStreamParams.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsIDocument.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIScriptError.h" +#include "nsISupportsPrimitives.h" +#include "nsThreadUtils.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 { +namespace dom { + +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<nsIRunnable> mRunnable; + +public: + explicit + CancelableRunnableWrapper(nsIRunnable* aRunnable) + : mRunnable(aRunnable) + { + MOZ_ASSERT(aRunnable); + } + +private: + ~CancelableRunnableWrapper() + { } + + 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<IDBDatabase> 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, + IDBFactory* aFactory, + BackgroundDatabaseChild* aActor, + DatabaseSpec* aSpec) + : IDBWrapperCache(aRequest) + , mFactory(aFactory) + , mSpec(aSpec) + , mBackgroundActor(aActor) + , mFileHandleDisabled(aRequest->IsFileHandleDisabled()) + , mClosed(false) + , mInvalidated(false) + , mQuotaExceeded(false) +{ + MOZ_ASSERT(aRequest); + MOZ_ASSERT(aFactory); + aFactory->AssertIsOnOwningThread(); + MOZ_ASSERT(aActor); + MOZ_ASSERT(aSpec); +} + +IDBDatabase::~IDBDatabase() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(!mBackgroundActor); +} + +// static +already_AddRefed<IDBDatabase> +IDBDatabase::Create(IDBOpenDBRequest* aRequest, + IDBFactory* aFactory, + BackgroundDatabaseChild* aActor, + DatabaseSpec* aSpec) +{ + MOZ_ASSERT(aRequest); + MOZ_ASSERT(aFactory); + aFactory->AssertIsOnOwningThread(); + MOZ_ASSERT(aActor); + MOZ_ASSERT(aSpec); + + RefPtr<IDBDatabase> db = + new IDBDatabase(aRequest, aFactory, aActor, aSpec); + + db->SetScriptOwner(aRequest->GetScriptOwner()); + + if (NS_IsMainThread()) { + if (nsPIDOMWindowInner* window = aFactory->GetParentObject()) { + uint64_t windowId = window->WindowID(); + + RefPtr<Observer> observer = new Observer(db, windowId); + + nsCOMPtr<nsIObserverService> obsSvc = GetObserverService(); + MOZ_ASSERT(obsSvc); + + // This topic must be successfully registered. + if (NS_WARN_IF(NS_FAILED( + obsSvc->AddObserver(observer, kWindowObserverTopic, false)))) { + observer->Revoke(); + return nullptr; + } + + // These topics are not crucial. + if (NS_FAILED(obsSvc->AddObserver(observer, + kCycleCollectionObserverTopic, + false)) || + NS_FAILED(obsSvc->AddObserver(observer, + kMemoryPressureObserverTopic, + false))) { + NS_WARNING("Failed to add additional memory observers!"); + } + + db->mObserver.swap(observer); + } + } + + return db.forget(); +} + +#ifdef DEBUG + +void +IDBDatabase::AssertIsOnOwningThread() const +{ + MOZ_ASSERT(mFactory); + mFactory->AssertIsOnOwningThread(); +} + +PRThread* +IDBDatabase::OwningThread() const +{ + MOZ_ASSERT(mFactory); + return mFactory->OwningThread(); +} + +#endif // DEBUG + +void +IDBDatabase::CloseInternal() +{ + AssertIsOnOwningThread(); + + if (!mClosed) { + mClosed = true; + + ExpireFileActors(/* aExpireAll */ true); + + if (mObserver) { + mObserver->Revoke(); + + nsCOMPtr<nsIObserverService> 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(); + } + } +} + +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 = new DatabaseSpec(*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! + nsAutoPtr<DatabaseSpec> currentSpec(mSpec.forget()); + + mSpec = mPreviousSpec.forget(); + + RefreshSpec(/* aMayDelete */ true); +} + +void +IDBDatabase::RefreshSpec(bool aMayDelete) +{ + AssertIsOnOwningThread(); + + for (auto iter = mTransactions.Iter(); !iter.Done(); iter.Next()) { + RefPtr<IDBTransaction> transaction = iter.Get()->GetKey(); + MOZ_ASSERT(transaction); + transaction->AssertIsOnOwningThread(); + transaction->RefreshSpec(aMayDelete); + } +} + +nsPIDOMWindowInner* +IDBDatabase::GetParentObject() const +{ + return mFactory->GetParentObject(); +} + +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(); +} + +already_AddRefed<DOMStringList> +IDBDatabase::ObjectStoreNames() const +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mSpec); + + const nsTArray<ObjectStoreSpec>& objectStores = mSpec->objectStores(); + + RefPtr<DOMStringList> list = new DOMStringList(); + + if (!objectStores.IsEmpty()) { + nsTArray<nsString>& listNames = list->StringArray(); + listNames.SetCapacity(objectStores.Length()); + + for (uint32_t index = 0; index < objectStores.Length(); index++) { + listNames.InsertElementSorted(objectStores[index].metadata().name()); + } + } + + return list.forget(); +} + +already_AddRefed<nsIDocument> +IDBDatabase::GetOwnerDocument() const +{ + if (nsPIDOMWindowInner* window = GetOwner()) { + nsCOMPtr<nsIDocument> doc = window->GetExtantDoc(); + return doc.forget(); + } + return nullptr; +} + +already_AddRefed<IDBObjectStore> +IDBDatabase::CreateObjectStore( + const nsAString& aName, + const IDBObjectStoreParameters& aOptionalParameters, + ErrorResult& aRv) +{ + AssertIsOnOwningThread(); + + IDBTransaction* transaction = IDBTransaction::GetCurrent(); + if (!transaction || + transaction->Database() != this || + transaction->GetMode() != IDBTransaction::VERSION_CHANGE) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return nullptr; + } + + if (!transaction->IsOpen()) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); + return nullptr; + } + + KeyPath keyPath(0); + if (NS_FAILED(KeyPath::Parse(aOptionalParameters.mKeyPath, &keyPath))) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return nullptr; + } + + nsTArray<ObjectStoreSpec>& objectStores = mSpec->objectStores(); + for (uint32_t count = objectStores.Length(), index = 0; + index < count; + index++) { + if (aName == objectStores[index].metadata().name()) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR); + 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); + } + + RefPtr<IDBObjectStore> 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("IndexedDB %s: Child Transaction[%lld] Request[%llu]: " + "database(%s).transaction(%s).createObjectStore(%s)", + "IndexedDB %s: C T[%lld] R[%llu]: " + "IDBDatabase.createObjectStore()", + IDB_LOG_ID_STRING(), + transaction->LoggingSerialNumber(), + requestSerialNumber, + IDB_LOG_STRINGIFY(this), + IDB_LOG_STRINGIFY(transaction), + IDB_LOG_STRINGIFY(objectStore)); + + return objectStore.forget(); +} + +void +IDBDatabase::DeleteObjectStore(const nsAString& aName, ErrorResult& aRv) +{ + AssertIsOnOwningThread(); + + IDBTransaction* transaction = IDBTransaction::GetCurrent(); + if (!transaction || + transaction->Database() != this || + transaction->GetMode() != IDBTransaction::VERSION_CHANGE) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return; + } + + if (!transaction->IsOpen()) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); + return; + } + + nsTArray<ObjectStoreSpec>& specArray = mSpec->objectStores(); + + int64_t objectStoreId = 0; + + for (uint32_t specCount = specArray.Length(), specIndex = 0; + specIndex < specCount; + specIndex++) { + const ObjectStoreMetadata& metadata = specArray[specIndex].metadata(); + MOZ_ASSERT(metadata.id()); + + if (aName == metadata.name()) { + objectStoreId = metadata.id(); + + // Must do this before altering the metadata array! + transaction->DeleteObjectStore(objectStoreId); + + specArray.RemoveElementAt(specIndex); + + RefreshSpec(/* aMayDelete */ false); + break; + } + } + + if (!objectStoreId) { + 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).deleteObjectStore(\"%s\")", + "IndexedDB %s: C T[%lld] R[%llu]: " + "IDBDatabase.deleteObjectStore()", + IDB_LOG_ID_STRING(), + transaction->LoggingSerialNumber(), + requestSerialNumber, + IDB_LOG_STRINGIFY(this), + IDB_LOG_STRINGIFY(transaction), + NS_ConvertUTF16toUTF8(aName).get()); +} + +already_AddRefed<IDBTransaction> +IDBDatabase::Transaction(JSContext* aCx, + const StringOrStringSequence& aStoreNames, + IDBTransactionMode aMode, + ErrorResult& aRv) +{ + AssertIsOnOwningThread(); + + if ((aMode == IDBTransactionMode::Readwriteflush || + aMode == IDBTransactionMode::Cleanup) && + !IndexedDatabaseManager::ExperimentalFeaturesEnabled()) { + // 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<MSG_INVALID_ENUM_VALUE>( + NS_LITERAL_STRING("Argument 2 of IDBDatabase.transaction"), + NS_LITERAL_STRING("readwriteflush"), + NS_LITERAL_STRING("IDBTransactionMode")); + return nullptr; + } + + RefPtr<IDBTransaction> transaction; + aRv = Transaction(aCx, aStoreNames, aMode, getter_AddRefs(transaction)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return transaction.forget(); +} + +nsresult +IDBDatabase::Transaction(JSContext* aCx, + const StringOrStringSequence& aStoreNames, + IDBTransactionMode aMode, + IDBTransaction** aTransaction) +{ + AssertIsOnOwningThread(); + + if (NS_WARN_IF((aMode == IDBTransactionMode::Readwriteflush || + aMode == IDBTransactionMode::Cleanup) && + !IndexedDatabaseManager::ExperimentalFeaturesEnabled())) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + if (QuotaManager::IsShuttingDown()) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + if (mClosed || RunningVersionChangeTransaction()) { + return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR; + } + + AutoTArray<nsString, 1> stackSequence; + + if (aStoreNames.IsString()) { + stackSequence.AppendElement(aStoreNames.GetAsString()); + } else { + MOZ_ASSERT(aStoreNames.IsStringSequence()); + if (aStoreNames.GetAsStringSequence().IsEmpty()) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } + } + + const nsTArray<nsString>& storeNames = + aStoreNames.IsString() ? + stackSequence : + static_cast<const nsTArray<nsString>&>(aStoreNames.GetAsStringSequence()); + MOZ_ASSERT(!storeNames.IsEmpty()); + + const nsTArray<ObjectStoreSpec>& objectStores = mSpec->objectStores(); + const uint32_t nameCount = storeNames.Length(); + + nsTArray<nsString> sortedStoreNames; + sortedStoreNames.SetCapacity(nameCount); + + // Check to make sure the object store names we collected actually exist. + for (uint32_t nameIndex = 0; nameIndex < nameCount; nameIndex++) { + const nsString& name = storeNames[nameIndex]; + + bool found = false; + + for (uint32_t objCount = objectStores.Length(), objIndex = 0; + objIndex < objCount; + objIndex++) { + if (objectStores[objIndex].metadata().name() == name) { + found = true; + break; + } + } + + if (!found) { + return NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR; + } + + sortedStoreNames.InsertElementSorted(name); + } + + // Remove any duplicates. + for (uint32_t nameIndex = nameCount - 1; nameIndex > 0; nameIndex--) { + if (sortedStoreNames[nameIndex] == sortedStoreNames[nameIndex - 1]) { + sortedStoreNames.RemoveElementAt(nameIndex); + } + } + + IDBTransaction::Mode mode; + switch (aMode) { + case IDBTransactionMode::Readonly: + mode = IDBTransaction::READ_ONLY; + break; + case IDBTransactionMode::Readwrite: + if (mQuotaExceeded) { + mode = IDBTransaction::CLEANUP; + mQuotaExceeded = false; + } else { + mode = IDBTransaction::READ_WRITE; + } + break; + case IDBTransactionMode::Readwriteflush: + mode = IDBTransaction::READ_WRITE_FLUSH; + break; + case IDBTransactionMode::Cleanup: + mode = IDBTransaction::CLEANUP; + mQuotaExceeded = false; + break; + case IDBTransactionMode::Versionchange: + return NS_ERROR_DOM_TYPE_ERR; + + default: + MOZ_CRASH("Unknown mode!"); + } + + RefPtr<IDBTransaction> transaction = + IDBTransaction::Create(aCx, this, sortedStoreNames, mode); + if (NS_WARN_IF(!transaction)) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + BackgroundTransactionChild* actor = + new BackgroundTransactionChild(transaction); + + IDB_LOG_MARK("IndexedDB %s: Child Transaction[%lld]: " + "database(%s).transaction(%s)", + "IndexedDB %s: C T[%lld]: IDBDatabase.transaction()", + IDB_LOG_ID_STRING(), + transaction->LoggingSerialNumber(), + IDB_LOG_STRINGIFY(this), + IDB_LOG_STRINGIFY(transaction)); + + MOZ_ALWAYS_TRUE( + mBackgroundActor->SendPBackgroundIDBTransactionConstructor(actor, + sortedStoreNames, + mode)); + + transaction->SetBackgroundActor(actor); + + if (mode == IDBTransaction::CLEANUP) { + ExpireFileActors(/* aExpireAll */ true); + } + + transaction.forget(aTransaction); + return NS_OK; +} + +StorageType +IDBDatabase::Storage() const +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mSpec); + + return PersistenceTypeToStorage(mSpec->metadata().persistenceType()); +} + +already_AddRefed<IDBRequest> +IDBDatabase::CreateMutableFile(JSContext* aCx, + const nsAString& aName, + const Optional<nsAString>& aType, + ErrorResult& aRv) +{ + AssertIsOnOwningThread(); + + 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); + + RefPtr<IDBRequest> request = IDBRequest::Create(aCx, this, nullptr); + MOZ_ASSERT(request); + + BackgroundDatabaseRequestChild* actor = + new BackgroundDatabaseRequestChild(this, request); + + IDB_LOG_MARK("IndexedDB %s: Child Request[%llu]: " + "database(%s).createMutableFile(%s)", + "IndexedDB %s: C R[%llu]: IDBDatabase.createMutableFile()", + IDB_LOG_ID_STRING(), + request->LoggingSerialNumber(), + IDB_LOG_STRINGIFY(this), + NS_ConvertUTF16toUTF8(aName).get()); + + mBackgroundActor->SendPBackgroundIDBDatabaseRequestConstructor(actor, params); + + return request.forget(); +} + +void +IDBDatabase::RegisterTransaction(IDBTransaction* aTransaction) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aTransaction); + aTransaction->AssertIsOnOwningThread(); + MOZ_ASSERT(!mTransactions.Contains(aTransaction)); + + mTransactions.PutEntry(aTransaction); +} + +void +IDBDatabase::UnregisterTransaction(IDBTransaction* aTransaction) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aTransaction); + aTransaction->AssertIsOnOwningThread(); + MOZ_ASSERT(mTransactions.Contains(aTransaction)); + + mTransactions.RemoveEntry(aTransaction); +} + +void +IDBDatabase::AbortTransactions(bool aShouldWarn) +{ + AssertIsOnOwningThread(); + + class MOZ_STACK_CLASS Helper final + { + typedef AutoTArray<RefPtr<IDBTransaction>, 20> StrongTransactionArray; + typedef AutoTArray<IDBTransaction*, 20> WeakTransactionArray; + + public: + static void + AbortTransactions(IDBDatabase* aDatabase, const bool aShouldWarn) + { + MOZ_ASSERT(aDatabase); + aDatabase->AssertIsOnOwningThread(); + + nsTHashtable<nsPtrHashKey<IDBTransaction>>& transactionTable = + aDatabase->mTransactions; + + if (!transactionTable.Count()) { + return; + } + + StrongTransactionArray transactionsToAbort; + transactionsToAbort.SetCapacity(transactionTable.Count()); + + for (auto iter = transactionTable.Iter(); !iter.Done(); iter.Next()) { + IDBTransaction* transaction = iter.Get()->GetKey(); + 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->IsDone()) { + transactionsToAbort.AppendElement(transaction); + } + } + MOZ_ASSERT(transactionsToAbort.Length() <= transactionTable.Count()); + + if (transactionsToAbort.IsEmpty()) { + 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 (RefPtr<IDBTransaction>& transaction : transactionsToAbort) { + MOZ_ASSERT(transaction); + MOZ_ASSERT(!transaction->IsDone()); + + if (aShouldWarn) { + switch (transaction->GetMode()) { + // We ignore transactions that could not have written any data. + case IDBTransaction::READ_ONLY: + break; + + // We warn for any transactions that could have written data. + case IDBTransaction::READ_WRITE: + case IDBTransaction::READ_WRITE_FLUSH: + case IDBTransaction::CLEANUP: + case IDBTransaction::VERSION_CHANGE: + transactionsThatNeedWarning.AppendElement(transaction); + break; + + default: + MOZ_CRASH("Unknown mode!"); + } + } + + 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); + + aDatabase->LogWarning(kWarningMessage, filename, lineNo, column); + } + } + }; + + Helper::AbortTransactions(this, aShouldWarn); +} + +PBackgroundIDBDatabaseFileChild* +IDBDatabase::GetOrCreateFileActorForBlob(Blob* aBlob) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aBlob); + 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. + nsCOMPtr<nsIDOMBlob> blob = aBlob; + nsCOMPtr<nsIWeakReference> weakRef = do_GetWeakReference(blob); + MOZ_ASSERT(weakRef); + + PBackgroundIDBDatabaseFileChild* actor = nullptr; + + if (!mFileActors.Get(weakRef, &actor)) { + BlobImpl* blobImpl = aBlob->Impl(); + MOZ_ASSERT(blobImpl); + + if (mReceivedBlobs.GetEntry(weakRef)) { + // This blob was previously retrieved from the database. + nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryObject(blobImpl); + MOZ_ASSERT(remoteBlob); + + BlobChild* blobChild = remoteBlob->GetBlobChild(); + MOZ_ASSERT(blobChild); + +#ifdef DEBUG + { + PBackgroundChild* backgroundManager = blobChild->GetBackgroundManager(); + MOZ_ASSERT(backgroundManager); + + PBackgroundChild* thisManager = mBackgroundActor->Manager()->Manager(); + MOZ_ASSERT(thisManager); + + MOZ_ASSERT(thisManager == backgroundManager); + } +#endif + auto* dbFile = new DatabaseFile(this); + + actor = + mBackgroundActor->SendPBackgroundIDBDatabaseFileConstructor(dbFile, + blobChild); + if (NS_WARN_IF(!actor)) { + return nullptr; + } + } else { + // Make sure that the input stream we get here is one that can actually be + // serialized to PBackground. + PBackgroundChild* backgroundManager = + mBackgroundActor->Manager()->Manager(); + MOZ_ASSERT(backgroundManager); + + auto* blobChild = + static_cast<BlobChild*>( + BackgroundChild::GetOrCreateActorForBlob(backgroundManager, aBlob)); + MOZ_ASSERT(blobChild); + + auto* dbFile = new DatabaseFile(this); + + actor = + mBackgroundActor->SendPBackgroundIDBDatabaseFileConstructor(dbFile, + blobChild); + if (NS_WARN_IF(!actor)) { + return nullptr; + } + } + + MOZ_ASSERT(actor); + + mFileActors.Put(weakRef, actor); + } + + MOZ_ASSERT(actor); + + return actor; +} + +void +IDBDatabase::NoteFinishedFileActor(PBackgroundIDBDatabaseFileChild* aFileActor) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aFileActor); + + for (auto iter = mFileActors.Iter(); !iter.Done(); iter.Next()) { + MOZ_ASSERT(iter.Key()); + PBackgroundIDBDatabaseFileChild* actor = iter.Data(); + MOZ_ASSERT(actor); + + if (actor == aFileActor) { + iter.Remove(); + } + } +} + +void +IDBDatabase::NoteReceivedBlob(Blob* aBlob) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aBlob); + MOZ_ASSERT(mBackgroundActor); + +#ifdef DEBUG + { + RefPtr<BlobImpl> blobImpl = aBlob->Impl(); + MOZ_ASSERT(blobImpl); + + nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryObject(blobImpl); + MOZ_ASSERT(remoteBlob); + + BlobChild* blobChild = remoteBlob->GetBlobChild(); + MOZ_ASSERT(blobChild); + + PBackgroundChild* backgroundManager = blobChild->GetBackgroundManager(); + MOZ_ASSERT(backgroundManager); + + PBackgroundChild* thisManager = mBackgroundActor->Manager()->Manager(); + MOZ_ASSERT(thisManager); + + MOZ_ASSERT(thisManager == backgroundManager); + } +#endif + + nsCOMPtr<nsIDOMBlob> blob = aBlob; + nsCOMPtr<nsIWeakReference> weakRef = do_GetWeakReference(blob); + MOZ_ASSERT(weakRef); + + // It's ok if this entry already exists in the table. + mReceivedBlobs.PutEntry(weakRef); +} + +void +IDBDatabase::DelayedMaybeExpireFileActors() +{ + AssertIsOnOwningThread(); + + if (!mBackgroundActor || !mFileActors.Count()) { + return; + } + + nsCOMPtr<nsIRunnable> runnable = + NewRunnableMethod<bool>(this, + &IDBDatabase::ExpireFileActors, + /* aExpireAll */ false); + MOZ_ASSERT(runnable); + + if (!NS_IsMainThread()) { + // Wrap as a nsICancelableRunnable to make workers happy. + nsCOMPtr<nsIRunnable> cancelable = new CancelableRunnableWrapper(runnable); + cancelable.swap(runnable); + } + + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(runnable)); +} + +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: + QuotaManager::GetInfoForChrome(nullptr, nullptr, &aOrigin, nullptr); + return NS_OK; + + case PrincipalInfo::TContentPrincipalInfo: { + nsresult rv; + nsCOMPtr<nsIPrincipal> principal = + PrincipalInfoToPrincipal(*principalInfo, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = QuotaManager::GetInfoFromPrincipal(principal, + nullptr, + nullptr, + &aOrigin, + nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + 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) { + nsCOMPtr<nsIWeakReference> weakRef = do_QueryInterface(key); + MOZ_ASSERT(weakRef); + + nsCOMPtr<nsISupports> 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()); + } + + if (mReceivedBlobs.Count()) { + if (aExpireAll) { + mReceivedBlobs.Clear(); + } else { + for (auto iter = mReceivedBlobs.Iter(); !iter.Done(); iter.Next()) { + nsISupports* key = iter.Get()->GetKey(); + MOZ_ASSERT(key); + + nsCOMPtr<nsIWeakReference> weakRef = do_QueryInterface(key); + MOZ_ASSERT(weakRef); + + nsCOMPtr<nsISupports> referent = do_QueryReferent(weakRef); + if (!referent) { + iter.Remove(); + } + } + } + } +} + +void +IDBDatabase::NoteLiveMutableFile(IDBMutableFile* aMutableFile) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aMutableFile); + aMutableFile->AssertIsOnOwningThread(); + MOZ_ASSERT(!mLiveMutableFiles.Contains(aMutableFile)); + + mLiveMutableFiles.AppendElement(aMutableFile); +} + +void +IDBDatabase::NoteFinishedMutableFile(IDBMutableFile* aMutableFile) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aMutableFile); + 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::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, IDBWrapperCache) +NS_IMPL_RELEASE_INHERITED(IDBDatabase, IDBWrapperCache) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(IDBDatabase) +NS_INTERFACE_MAP_END_INHERITING(IDBWrapperCache) + +NS_IMPL_CYCLE_COLLECTION_CLASS(IDBDatabase) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBDatabase, IDBWrapperCache) + tmp->AssertIsOnOwningThread(); + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFactory) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBDatabase, IDBWrapperCache) + 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::LastRelease() +{ + AssertIsOnOwningThread(); + + CloseInternal(); + + if (mBackgroundActor) { + mBackgroundActor->SendDeleteMeInternal(); + MOZ_ASSERT(!mBackgroundActor, "SendDeleteMeInternal should have cleared!"); + } +} + +nsresult +IDBDatabase::PostHandleEvent(EventChainPostVisitor& aVisitor) +{ + nsresult rv = + IndexedDatabaseManager::CommonPostHandleEvent(aVisitor, mFactory); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +JSObject* +IDBDatabase::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return IDBDatabaseBinding::Wrap(aCx, this, aGivenProto); +} + +NS_IMETHODIMP +CancelableRunnableWrapper::Run() +{ + nsCOMPtr<nsIRunnable> runnable; + mRunnable.swap(runnable); + + 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<nsISupportsPRUint64> supportsInt = do_QueryInterface(aSubject); + MOZ_ASSERT(supportsInt); + + uint64_t windowId; + MOZ_ALWAYS_SUCCEEDS(supportsInt->GetData(&windowId)); + + if (windowId == mWindowId) { + RefPtr<IDBDatabase> database = mWeakDatabase; + mWeakDatabase = nullptr; + + database->InvalidateInternal(); + } + } + + return NS_OK; + } + + if (!strcmp(aTopic, kCycleCollectionObserverTopic) || + !strcmp(aTopic, kMemoryPressureObserverTopic)) { + if (mWeakDatabase) { + RefPtr<IDBDatabase> 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<ObjectStoreSpec>& objectStores = mSpec->objectStores(); + + ObjectStoreSpec* foundObjectStoreSpec = nullptr; + // Find the matched object store spec and check if 'aName' is already used by + // another object store. + for (uint32_t objCount = objectStores.Length(), objIndex = 0; + objIndex < objCount; + objIndex++) { + const ObjectStoreSpec& objSpec = objectStores[objIndex]; + if (objSpec.metadata().id() == aObjectStoreId) { + MOZ_ASSERT(!foundObjectStoreSpec); + foundObjectStoreSpec = &objectStores[objIndex]; + continue; + } + if (aName == objSpec.metadata().name()) { + return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR; + } + } + + MOZ_ASSERT(foundObjectStoreSpec); + + // Update the name of the matched object store. + foundObjectStoreSpec->metadata().name() = nsString(aName); + + return NS_OK; +} + +nsresult +IDBDatabase::RenameIndex(int64_t aObjectStoreId, + int64_t aIndexId, + const nsAString& aName) +{ + MOZ_ASSERT(mSpec); + + nsTArray<ObjectStoreSpec>& 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<IndexMetadata>& 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_CONSTRAINT_ERR; + } + } + + MOZ_ASSERT(foundIndexMetadata); + + // Update the name of the matched object store. + foundIndexMetadata->name() = nsString(aName); + + return NS_OK; +} + +} // namespace dom +} // namespace mozilla |