diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/indexedDB/IDBTransaction.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/indexedDB/IDBTransaction.cpp')
-rw-r--r-- | dom/indexedDB/IDBTransaction.cpp | 1049 |
1 files changed, 1049 insertions, 0 deletions
diff --git a/dom/indexedDB/IDBTransaction.cpp b/dom/indexedDB/IDBTransaction.cpp new file mode 100644 index 000000000..a50489898 --- /dev/null +++ b/dom/indexedDB/IDBTransaction.cpp @@ -0,0 +1,1049 @@ +/* -*- 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 "IDBTransaction.h" + +#include "BackgroundChildImpl.h" +#include "IDBDatabase.h" +#include "IDBEvents.h" +#include "IDBObjectStore.h" +#include "IDBRequest.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/dom/DOMError.h" +#include "mozilla/dom/DOMStringList.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "nsAutoPtr.h" +#include "nsPIDOMWindow.h" +#include "nsServiceManagerUtils.h" +#include "nsTHashtable.h" +#include "ProfilerHelpers.h" +#include "ReportInternalError.h" +#include "WorkerHolder.h" +#include "WorkerPrivate.h" + +// Include this last to avoid path problems on Windows. +#include "ActorsChild.h" + +namespace mozilla { +namespace dom { + +using namespace mozilla::dom::indexedDB; +using namespace mozilla::dom::workers; +using namespace mozilla::ipc; + +class IDBTransaction::WorkerHolder final + : public mozilla::dom::workers::WorkerHolder +{ + WorkerPrivate* mWorkerPrivate; + + // The IDBTransaction owns this object so we only need a weak reference back + // to it. + IDBTransaction* mTransaction; + +public: + WorkerHolder(WorkerPrivate* aWorkerPrivate, IDBTransaction* aTransaction) + : mWorkerPrivate(aWorkerPrivate) + , mTransaction(aTransaction) + { + MOZ_ASSERT(aWorkerPrivate); + MOZ_ASSERT(aTransaction); + aWorkerPrivate->AssertIsOnWorkerThread(); + aTransaction->AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(IDBTransaction::WorkerHolder); + } + + ~WorkerHolder() + { + mWorkerPrivate->AssertIsOnWorkerThread(); + + MOZ_COUNT_DTOR(IDBTransaction::WorkerHolder); + } + +private: + virtual bool + Notify(Status aStatus) override; +}; + +IDBTransaction::IDBTransaction(IDBDatabase* aDatabase, + const nsTArray<nsString>& aObjectStoreNames, + Mode aMode) + : IDBWrapperCache(aDatabase) + , mDatabase(aDatabase) + , mObjectStoreNames(aObjectStoreNames) + , mLoggingSerialNumber(0) + , mNextObjectStoreId(0) + , mNextIndexId(0) + , mAbortCode(NS_OK) + , mPendingRequestCount(0) + , mLineNo(0) + , mColumn(0) + , mReadyState(IDBTransaction::INITIAL) + , mMode(aMode) + , mCreating(false) + , mRegistered(false) + , mAbortedByScript(false) +#ifdef DEBUG + , mSentCommitOrAbort(false) + , mFiredCompleteOrAbort(false) +#endif +{ + MOZ_ASSERT(aDatabase); + aDatabase->AssertIsOnOwningThread(); + + mBackgroundActor.mNormalBackgroundActor = nullptr; + + BackgroundChildImpl::ThreadLocal* threadLocal = + BackgroundChildImpl::GetThreadLocalForCurrentThread(); + MOZ_ASSERT(threadLocal); + + ThreadLocal* idbThreadLocal = threadLocal->mIndexedDBThreadLocal; + MOZ_ASSERT(idbThreadLocal); + + const_cast<int64_t&>(mLoggingSerialNumber) = + idbThreadLocal->NextTransactionSN(aMode); + +#ifdef DEBUG + if (!aObjectStoreNames.IsEmpty()) { + nsTArray<nsString> sortedNames(aObjectStoreNames); + sortedNames.Sort(); + + const uint32_t count = sortedNames.Length(); + MOZ_ASSERT(count == aObjectStoreNames.Length()); + + // Make sure the array is properly sorted. + for (uint32_t index = 0; index < count; index++) { + MOZ_ASSERT(aObjectStoreNames[index] == sortedNames[index]); + } + + // Make sure there are no duplicates in our objectStore names. + for (uint32_t index = 0; index < count - 1; index++) { + MOZ_ASSERT(sortedNames[index] != sortedNames[index + 1]); + } + } +#endif +} + +IDBTransaction::~IDBTransaction() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(!mPendingRequestCount); + MOZ_ASSERT(!mCreating); + MOZ_ASSERT(mSentCommitOrAbort); + MOZ_ASSERT_IF(mMode == VERSION_CHANGE && + mBackgroundActor.mVersionChangeBackgroundActor, + mFiredCompleteOrAbort); + MOZ_ASSERT_IF(mMode != VERSION_CHANGE && + mBackgroundActor.mNormalBackgroundActor, + mFiredCompleteOrAbort); + + if (mRegistered) { + mDatabase->UnregisterTransaction(this); +#ifdef DEBUG + mRegistered = false; +#endif + } + + if (mMode == VERSION_CHANGE) { + if (auto* actor = mBackgroundActor.mVersionChangeBackgroundActor) { + actor->SendDeleteMeInternal(/* aFailedConstructor */ false); + + MOZ_ASSERT(!mBackgroundActor.mVersionChangeBackgroundActor, + "SendDeleteMeInternal should have cleared!"); + } + } else if (auto* actor = mBackgroundActor.mNormalBackgroundActor) { + actor->SendDeleteMeInternal(); + + MOZ_ASSERT(!mBackgroundActor.mNormalBackgroundActor, + "SendDeleteMeInternal should have cleared!"); + } +} + +// static +already_AddRefed<IDBTransaction> +IDBTransaction::CreateVersionChange( + IDBDatabase* aDatabase, + BackgroundVersionChangeTransactionChild* aActor, + IDBOpenDBRequest* aOpenRequest, + int64_t aNextObjectStoreId, + int64_t aNextIndexId) +{ + MOZ_ASSERT(aDatabase); + aDatabase->AssertIsOnOwningThread(); + MOZ_ASSERT(aActor); + MOZ_ASSERT(aOpenRequest); + MOZ_ASSERT(aNextObjectStoreId > 0); + MOZ_ASSERT(aNextIndexId > 0); + + nsTArray<nsString> emptyObjectStoreNames; + + RefPtr<IDBTransaction> transaction = + new IDBTransaction(aDatabase, + emptyObjectStoreNames, + VERSION_CHANGE); + aOpenRequest->GetCallerLocation(transaction->mFilename, + &transaction->mLineNo, &transaction->mColumn); + + transaction->SetScriptOwner(aDatabase->GetScriptOwner()); + + nsCOMPtr<nsIRunnable> runnable = do_QueryObject(transaction); + nsContentUtils::RunInMetastableState(runnable.forget()); + + transaction->mBackgroundActor.mVersionChangeBackgroundActor = aActor; + transaction->mNextObjectStoreId = aNextObjectStoreId; + transaction->mNextIndexId = aNextIndexId; + transaction->mCreating = true; + + aDatabase->RegisterTransaction(transaction); + transaction->mRegistered = true; + + return transaction.forget(); +} + +// static +already_AddRefed<IDBTransaction> +IDBTransaction::Create(JSContext* aCx, IDBDatabase* aDatabase, + const nsTArray<nsString>& aObjectStoreNames, + Mode aMode) +{ + MOZ_ASSERT(aDatabase); + aDatabase->AssertIsOnOwningThread(); + MOZ_ASSERT(!aObjectStoreNames.IsEmpty()); + MOZ_ASSERT(aMode == READ_ONLY || + aMode == READ_WRITE || + aMode == READ_WRITE_FLUSH || + aMode == CLEANUP); + + RefPtr<IDBTransaction> transaction = + new IDBTransaction(aDatabase, aObjectStoreNames, aMode); + IDBRequest::CaptureCaller(aCx, transaction->mFilename, &transaction->mLineNo, + &transaction->mColumn); + + transaction->SetScriptOwner(aDatabase->GetScriptOwner()); + + nsCOMPtr<nsIRunnable> runnable = do_QueryObject(transaction); + nsContentUtils::RunInMetastableState(runnable.forget()); + + transaction->mCreating = true; + + aDatabase->RegisterTransaction(transaction); + transaction->mRegistered = true; + + if (!NS_IsMainThread()) { + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + + workerPrivate->AssertIsOnWorkerThread(); + + transaction->mWorkerHolder = new WorkerHolder(workerPrivate, transaction); + MOZ_ALWAYS_TRUE(transaction->mWorkerHolder->HoldWorker(workerPrivate, Canceling)); + } + + return transaction.forget(); +} + +// static +IDBTransaction* +IDBTransaction::GetCurrent() +{ + using namespace mozilla::ipc; + + MOZ_ASSERT(BackgroundChild::GetForCurrentThread()); + + BackgroundChildImpl::ThreadLocal* threadLocal = + BackgroundChildImpl::GetThreadLocalForCurrentThread(); + MOZ_ASSERT(threadLocal); + + ThreadLocal* idbThreadLocal = threadLocal->mIndexedDBThreadLocal; + MOZ_ASSERT(idbThreadLocal); + + return idbThreadLocal->GetCurrentTransaction(); +} + +#ifdef DEBUG + +void +IDBTransaction::AssertIsOnOwningThread() const +{ + MOZ_ASSERT(mDatabase); + mDatabase->AssertIsOnOwningThread(); +} + +#endif // DEBUG + +void +IDBTransaction::SetBackgroundActor(indexedDB::BackgroundTransactionChild* aBackgroundActor) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aBackgroundActor); + MOZ_ASSERT(!mBackgroundActor.mNormalBackgroundActor); + MOZ_ASSERT(mMode != VERSION_CHANGE); + + mBackgroundActor.mNormalBackgroundActor = aBackgroundActor; +} + +BackgroundRequestChild* +IDBTransaction::StartRequest(IDBRequest* aRequest, const RequestParams& aParams) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aRequest); + MOZ_ASSERT(aParams.type() != RequestParams::T__None); + + BackgroundRequestChild* actor = new BackgroundRequestChild(aRequest); + + if (mMode == VERSION_CHANGE) { + MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor); + + mBackgroundActor.mVersionChangeBackgroundActor-> + SendPBackgroundIDBRequestConstructor(actor, aParams); + } else { + MOZ_ASSERT(mBackgroundActor.mNormalBackgroundActor); + + mBackgroundActor.mNormalBackgroundActor-> + SendPBackgroundIDBRequestConstructor(actor, aParams); + } + + // Balanced in BackgroundRequestChild::Recv__delete__(). + OnNewRequest(); + + return actor; +} + +void +IDBTransaction::OpenCursor(BackgroundCursorChild* aBackgroundActor, + const OpenCursorParams& aParams) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aBackgroundActor); + MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None); + + if (mMode == VERSION_CHANGE) { + MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor); + + mBackgroundActor.mVersionChangeBackgroundActor-> + SendPBackgroundIDBCursorConstructor(aBackgroundActor, aParams); + } else { + MOZ_ASSERT(mBackgroundActor.mNormalBackgroundActor); + + mBackgroundActor.mNormalBackgroundActor-> + SendPBackgroundIDBCursorConstructor(aBackgroundActor, aParams); + } + + // Balanced in BackgroundCursorChild::RecvResponse(). + OnNewRequest(); +} + +void +IDBTransaction::RefreshSpec(bool aMayDelete) +{ + AssertIsOnOwningThread(); + + for (uint32_t count = mObjectStores.Length(), index = 0; + index < count; + index++) { + mObjectStores[index]->RefreshSpec(aMayDelete); + } + + for (uint32_t count = mDeletedObjectStores.Length(), index = 0; + index < count; + index++) { + mDeletedObjectStores[index]->RefreshSpec(false); + } +} + +void +IDBTransaction::OnNewRequest() +{ + AssertIsOnOwningThread(); + + if (!mPendingRequestCount) { + MOZ_ASSERT(INITIAL == mReadyState); + mReadyState = LOADING; + } + + ++mPendingRequestCount; +} + +void +IDBTransaction::OnRequestFinished(bool aActorDestroyedNormally) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mPendingRequestCount); + + --mPendingRequestCount; + + if (!mPendingRequestCount) { + mReadyState = COMMITTING; + + if (aActorDestroyedNormally) { + if (NS_SUCCEEDED(mAbortCode)) { + SendCommit(); + } else { + SendAbort(mAbortCode); + } + } else { + // Don't try to send any more messages to the parent if the request actor + // was killed. +#ifdef DEBUG + MOZ_ASSERT(!mSentCommitOrAbort); + mSentCommitOrAbort = true; +#endif + IDB_LOG_MARK("IndexedDB %s: Child Transaction[%lld]: " + "Request actor was killed, transaction will be aborted", + "IndexedDB %s: C T[%lld]: IDBTransaction abort", + IDB_LOG_ID_STRING(), + LoggingSerialNumber()); + } + } +} + +void +IDBTransaction::SendCommit() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(NS_SUCCEEDED(mAbortCode)); + MOZ_ASSERT(IsCommittingOrDone()); + MOZ_ASSERT(!mSentCommitOrAbort); + MOZ_ASSERT(!mPendingRequestCount); + + // 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]: " + "All requests complete, committing transaction", + "IndexedDB %s: C T[%lld] R[%llu]: IDBTransaction commit", + IDB_LOG_ID_STRING(), + LoggingSerialNumber(), + requestSerialNumber); + + if (mMode == VERSION_CHANGE) { + MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor); + mBackgroundActor.mVersionChangeBackgroundActor->SendCommit(); + } else { + MOZ_ASSERT(mBackgroundActor.mNormalBackgroundActor); + mBackgroundActor.mNormalBackgroundActor->SendCommit(); + } + +#ifdef DEBUG + mSentCommitOrAbort = true; +#endif +} + +void +IDBTransaction::SendAbort(nsresult aResultCode) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(NS_FAILED(aResultCode)); + MOZ_ASSERT(IsCommittingOrDone()); + MOZ_ASSERT(!mSentCommitOrAbort); + + // 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]: " + "Aborting transaction with result 0x%x", + "IndexedDB %s: C T[%lld] R[%llu]: IDBTransaction abort (0x%x)", + IDB_LOG_ID_STRING(), + LoggingSerialNumber(), + requestSerialNumber, + aResultCode); + + if (mMode == VERSION_CHANGE) { + MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor); + mBackgroundActor.mVersionChangeBackgroundActor->SendAbort(aResultCode); + } else { + MOZ_ASSERT(mBackgroundActor.mNormalBackgroundActor); + mBackgroundActor.mNormalBackgroundActor->SendAbort(aResultCode); + } + +#ifdef DEBUG + mSentCommitOrAbort = true; +#endif +} + +bool +IDBTransaction::IsOpen() const +{ + AssertIsOnOwningThread(); + + // If we haven't started anything then we're open. + if (mReadyState == IDBTransaction::INITIAL) { + return true; + } + + // If we've already started then we need to check to see if we still have the + // mCreating flag set. If we do (i.e. we haven't returned to the event loop + // from the time we were created) then we are open. Otherwise check the + // currently running transaction to see if it's the same. We only allow other + // requests to be made if this transaction is currently running. + if (mReadyState == IDBTransaction::LOADING && + (mCreating || GetCurrent() == this)) { + return true; + } + + return false; +} + +void +IDBTransaction::GetCallerLocation(nsAString& aFilename, uint32_t* aLineNo, + uint32_t* aColumn) const +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aLineNo); + MOZ_ASSERT(aColumn); + + aFilename = mFilename; + *aLineNo = mLineNo; + *aColumn = mColumn; +} + +already_AddRefed<IDBObjectStore> +IDBTransaction::CreateObjectStore(const ObjectStoreSpec& aSpec) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aSpec.metadata().id()); + MOZ_ASSERT(VERSION_CHANGE == mMode); + MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor); + MOZ_ASSERT(IsOpen()); + +#ifdef DEBUG + { + const nsString& name = aSpec.metadata().name(); + + for (uint32_t count = mObjectStores.Length(), index = 0; + index < count; + index++) { + MOZ_ASSERT(mObjectStores[index]->Name() != name); + } + } +#endif + + MOZ_ALWAYS_TRUE(mBackgroundActor.mVersionChangeBackgroundActor-> + SendCreateObjectStore(aSpec.metadata())); + + RefPtr<IDBObjectStore> objectStore = IDBObjectStore::Create(this, aSpec); + MOZ_ASSERT(objectStore); + + mObjectStores.AppendElement(objectStore); + + return objectStore.forget(); +} + +void +IDBTransaction::DeleteObjectStore(int64_t aObjectStoreId) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aObjectStoreId); + MOZ_ASSERT(VERSION_CHANGE == mMode); + MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor); + MOZ_ASSERT(IsOpen()); + + MOZ_ALWAYS_TRUE(mBackgroundActor.mVersionChangeBackgroundActor-> + SendDeleteObjectStore(aObjectStoreId)); + + for (uint32_t count = mObjectStores.Length(), index = 0; + index < count; + index++) { + RefPtr<IDBObjectStore>& objectStore = mObjectStores[index]; + + if (objectStore->Id() == aObjectStoreId) { + objectStore->NoteDeletion(); + + RefPtr<IDBObjectStore>* deletedObjectStore = + mDeletedObjectStores.AppendElement(); + deletedObjectStore->swap(mObjectStores[index]); + + mObjectStores.RemoveElementAt(index); + break; + } + } +} + +void +IDBTransaction::RenameObjectStore(int64_t aObjectStoreId, + const nsAString& aName) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aObjectStoreId); + MOZ_ASSERT(VERSION_CHANGE == mMode); + MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor); + MOZ_ASSERT(IsOpen()); + + MOZ_ALWAYS_TRUE(mBackgroundActor.mVersionChangeBackgroundActor-> + SendRenameObjectStore(aObjectStoreId, nsString(aName))); +} + +void +IDBTransaction::CreateIndex(IDBObjectStore* aObjectStore, + const indexedDB::IndexMetadata& aMetadata) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aObjectStore); + MOZ_ASSERT(aMetadata.id()); + MOZ_ASSERT(VERSION_CHANGE == mMode); + MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor); + MOZ_ASSERT(IsOpen()); + + MOZ_ALWAYS_TRUE(mBackgroundActor.mVersionChangeBackgroundActor-> + SendCreateIndex(aObjectStore->Id(), aMetadata)); +} + +void +IDBTransaction::DeleteIndex(IDBObjectStore* aObjectStore, + int64_t aIndexId) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aObjectStore); + MOZ_ASSERT(aIndexId); + MOZ_ASSERT(VERSION_CHANGE == mMode); + MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor); + MOZ_ASSERT(IsOpen()); + + MOZ_ALWAYS_TRUE(mBackgroundActor.mVersionChangeBackgroundActor-> + SendDeleteIndex(aObjectStore->Id(), aIndexId)); +} + +void +IDBTransaction::RenameIndex(IDBObjectStore* aObjectStore, + int64_t aIndexId, + const nsAString& aName) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aObjectStore); + MOZ_ASSERT(aIndexId); + MOZ_ASSERT(VERSION_CHANGE == mMode); + MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor); + MOZ_ASSERT(IsOpen()); + + MOZ_ALWAYS_TRUE(mBackgroundActor.mVersionChangeBackgroundActor-> + SendRenameIndex(aObjectStore->Id(), + aIndexId, + nsString(aName))); +} + +void +IDBTransaction::AbortInternal(nsresult aAbortCode, + already_AddRefed<DOMError> aError) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(NS_FAILED(aAbortCode)); + MOZ_ASSERT(!IsCommittingOrDone()); + + RefPtr<DOMError> error = aError; + + const bool isVersionChange = mMode == VERSION_CHANGE; + const bool isInvalidated = mDatabase->IsInvalidated(); + bool needToSendAbort = mReadyState == INITIAL; + + mAbortCode = aAbortCode; + mReadyState = DONE; + mError = error.forget(); + + if (isVersionChange) { + // If a version change transaction is aborted, we must revert the world + // back to its previous state unless we're being invalidated after the + // transaction already completed. + if (!isInvalidated) { + mDatabase->RevertToPreviousState(); + } + + // We do the reversion only for the mObjectStores/mDeletedObjectStores but + // not for the mIndexes/mDeletedIndexes of each IDBObjectStore because it's + // time-consuming(O(m*n)) and mIndexes/mDeletedIndexes won't be used anymore + // in IDBObjectStore::(Create|Delete)Index() and IDBObjectStore::Index() in + // which all the executions are returned earlier by !transaction->IsOpen(). + + const nsTArray<ObjectStoreSpec>& specArray = + mDatabase->Spec()->objectStores(); + + if (specArray.IsEmpty()) { + mObjectStores.Clear(); + mDeletedObjectStores.Clear(); + } else { + nsTHashtable<nsUint64HashKey> validIds(specArray.Length()); + + for (uint32_t specCount = specArray.Length(), specIndex = 0; + specIndex < specCount; + specIndex++) { + const int64_t objectStoreId = specArray[specIndex].metadata().id(); + MOZ_ASSERT(objectStoreId); + + validIds.PutEntry(uint64_t(objectStoreId)); + } + + for (uint32_t objCount = mObjectStores.Length(), objIndex = 0; + objIndex < objCount; + /* incremented conditionally */) { + const int64_t objectStoreId = mObjectStores[objIndex]->Id(); + MOZ_ASSERT(objectStoreId); + + if (validIds.Contains(uint64_t(objectStoreId))) { + objIndex++; + } else { + mObjectStores.RemoveElementAt(objIndex); + objCount--; + } + } + + if (!mDeletedObjectStores.IsEmpty()) { + for (uint32_t objCount = mDeletedObjectStores.Length(), objIndex = 0; + objIndex < objCount; + objIndex++) { + const int64_t objectStoreId = mDeletedObjectStores[objIndex]->Id(); + MOZ_ASSERT(objectStoreId); + + if (validIds.Contains(uint64_t(objectStoreId))) { + RefPtr<IDBObjectStore>* objectStore = + mObjectStores.AppendElement(); + objectStore->swap(mDeletedObjectStores[objIndex]); + } + } + mDeletedObjectStores.Clear(); + } + } + } + + // Fire the abort event if there are no outstanding requests. Otherwise the + // abort event will be fired when all outstanding requests finish. + if (needToSendAbort) { + SendAbort(aAbortCode); + } + + if (isVersionChange) { + mDatabase->Close(); + } +} + +void +IDBTransaction::Abort(IDBRequest* aRequest) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aRequest); + + if (IsCommittingOrDone()) { + // Already started (and maybe finished) the commit or abort so there is + // nothing to do here. + return; + } + + ErrorResult rv; + RefPtr<DOMError> error = aRequest->GetError(rv); + + AbortInternal(aRequest->GetErrorCode(), error.forget()); +} + +void +IDBTransaction::Abort(nsresult aErrorCode) +{ + AssertIsOnOwningThread(); + + if (IsCommittingOrDone()) { + // Already started (and maybe finished) the commit or abort so there is + // nothing to do here. + return; + } + + RefPtr<DOMError> error = new DOMError(GetOwner(), aErrorCode); + AbortInternal(aErrorCode, error.forget()); +} + +void +IDBTransaction::Abort(ErrorResult& aRv) +{ + AssertIsOnOwningThread(); + + if (IsCommittingOrDone()) { + aRv = NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR; + return; + } + + AbortInternal(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR, nullptr); + + MOZ_ASSERT(!mAbortedByScript); + mAbortedByScript = true; +} + +void +IDBTransaction::FireCompleteOrAbortEvents(nsresult aResult) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(!mFiredCompleteOrAbort); + + mReadyState = DONE; + +#ifdef DEBUG + mFiredCompleteOrAbort = true; +#endif + + // Make sure we drop the WorkerHolder when this function completes. + nsAutoPtr<WorkerHolder> workerHolder = Move(mWorkerHolder); + + nsCOMPtr<nsIDOMEvent> event; + if (NS_SUCCEEDED(aResult)) { + event = CreateGenericEvent(this, + nsDependentString(kCompleteEventType), + eDoesNotBubble, + eNotCancelable); + MOZ_ASSERT(event); + } else { + if (aResult == NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR) { + mDatabase->SetQuotaExceeded(); + } + + if (!mError && !mAbortedByScript) { + mError = new DOMError(GetOwner(), aResult); + } + + event = CreateGenericEvent(this, + nsDependentString(kAbortEventType), + eDoesBubble, + eNotCancelable); + MOZ_ASSERT(event); + } + + if (NS_SUCCEEDED(mAbortCode)) { + IDB_LOG_MARK("IndexedDB %s: Child Transaction[%lld]: " + "Firing 'complete' event", + "IndexedDB %s: C T[%lld]: IDBTransaction 'complete' event", + IDB_LOG_ID_STRING(), + mLoggingSerialNumber); + } else { + IDB_LOG_MARK("IndexedDB %s: Child Transaction[%lld]: " + "Firing 'abort' event with error 0x%x", + "IndexedDB %s: C T[%lld]: IDBTransaction 'abort' event (0x%x)", + IDB_LOG_ID_STRING(), + mLoggingSerialNumber, + mAbortCode); + } + + bool dummy; + if (NS_FAILED(DispatchEvent(event, &dummy))) { + NS_WARNING("DispatchEvent failed!"); + } + + mDatabase->DelayedMaybeExpireFileActors(); +} + +int64_t +IDBTransaction::NextObjectStoreId() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(VERSION_CHANGE == mMode); + + return mNextObjectStoreId++; +} + +int64_t +IDBTransaction::NextIndexId() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(VERSION_CHANGE == mMode); + + return mNextIndexId++; +} + +nsPIDOMWindowInner* +IDBTransaction::GetParentObject() const +{ + AssertIsOnOwningThread(); + + return mDatabase->GetParentObject(); +} + +IDBTransactionMode +IDBTransaction::GetMode(ErrorResult& aRv) const +{ + AssertIsOnOwningThread(); + + switch (mMode) { + case READ_ONLY: + return IDBTransactionMode::Readonly; + + case READ_WRITE: + return IDBTransactionMode::Readwrite; + + case READ_WRITE_FLUSH: + return IDBTransactionMode::Readwriteflush; + + case CLEANUP: + return IDBTransactionMode::Cleanup; + + case VERSION_CHANGE: + return IDBTransactionMode::Versionchange; + + case MODE_INVALID: + default: + MOZ_CRASH("Bad mode!"); + } +} + +DOMError* +IDBTransaction::GetError() const +{ + AssertIsOnOwningThread(); + + return mError; +} + +already_AddRefed<DOMStringList> +IDBTransaction::ObjectStoreNames() const +{ + AssertIsOnOwningThread(); + + if (mMode == IDBTransaction::VERSION_CHANGE) { + return mDatabase->ObjectStoreNames(); + } + + RefPtr<DOMStringList> list = new DOMStringList(); + list->StringArray() = mObjectStoreNames; + return list.forget(); +} + +already_AddRefed<IDBObjectStore> +IDBTransaction::ObjectStore(const nsAString& aName, ErrorResult& aRv) +{ + AssertIsOnOwningThread(); + + if (IsCommittingOrDone()) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + const ObjectStoreSpec* spec = nullptr; + + if (IDBTransaction::VERSION_CHANGE == mMode || + mObjectStoreNames.Contains(aName)) { + const nsTArray<ObjectStoreSpec>& objectStores = + mDatabase->Spec()->objectStores(); + + for (uint32_t count = objectStores.Length(), index = 0; + index < count; + index++) { + const ObjectStoreSpec& objectStore = objectStores[index]; + if (objectStore.metadata().name() == aName) { + spec = &objectStore; + break; + } + } + } + + if (!spec) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR); + return nullptr; + } + + const int64_t desiredId = spec->metadata().id(); + + RefPtr<IDBObjectStore> objectStore; + + for (uint32_t count = mObjectStores.Length(), index = 0; + index < count; + index++) { + RefPtr<IDBObjectStore>& existingObjectStore = mObjectStores[index]; + + if (existingObjectStore->Id() == desiredId) { + objectStore = existingObjectStore; + break; + } + } + + if (!objectStore) { + objectStore = IDBObjectStore::Create(this, *spec); + MOZ_ASSERT(objectStore); + + mObjectStores.AppendElement(objectStore); + } + + return objectStore.forget(); +} + +NS_IMPL_ADDREF_INHERITED(IDBTransaction, IDBWrapperCache) +NS_IMPL_RELEASE_INHERITED(IDBTransaction, IDBWrapperCache) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(IDBTransaction) + NS_INTERFACE_MAP_ENTRY(nsIRunnable) +NS_INTERFACE_MAP_END_INHERITING(IDBWrapperCache) + +NS_IMPL_CYCLE_COLLECTION_CLASS(IDBTransaction) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBTransaction, + IDBWrapperCache) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDatabase) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObjectStores) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeletedObjectStores) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBTransaction, IDBWrapperCache) + // Don't unlink mDatabase! + NS_IMPL_CYCLE_COLLECTION_UNLINK(mError) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mObjectStores) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDeletedObjectStores) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +JSObject* +IDBTransaction::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + AssertIsOnOwningThread(); + + return IDBTransactionBinding::Wrap(aCx, this, aGivenProto); +} + +nsresult +IDBTransaction::PreHandleEvent(EventChainPreVisitor& aVisitor) +{ + AssertIsOnOwningThread(); + + aVisitor.mCanHandle = true; + aVisitor.mParentTarget = mDatabase; + return NS_OK; +} + +NS_IMETHODIMP +IDBTransaction::Run() +{ + AssertIsOnOwningThread(); + + // We're back at the event loop, no longer newborn. + mCreating = false; + + // Maybe commit if there were no requests generated. + if (mReadyState == IDBTransaction::INITIAL) { + mReadyState = DONE; + + SendCommit(); + } + + return NS_OK; +} + +bool +IDBTransaction:: +WorkerHolder::Notify(Status aStatus) +{ + MOZ_ASSERT(mWorkerPrivate); + mWorkerPrivate->AssertIsOnWorkerThread(); + MOZ_ASSERT(aStatus > Running); + + if (mTransaction && aStatus > Terminating) { + mTransaction->AssertIsOnOwningThread(); + + RefPtr<IDBTransaction> transaction = Move(mTransaction); + + if (!transaction->IsCommittingOrDone()) { + IDB_REPORT_INTERNAL_ERR(); + transaction->AbortInternal(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, nullptr); + } + } + + return true; +} + +} // namespace dom +} // namespace mozilla |