summaryrefslogtreecommitdiffstats
path: root/dom/indexedDB/IDBTransaction.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/indexedDB/IDBTransaction.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/indexedDB/IDBTransaction.cpp')
-rw-r--r--dom/indexedDB/IDBTransaction.cpp1019
1 files changed, 1019 insertions, 0 deletions
diff --git a/dom/indexedDB/IDBTransaction.cpp b/dom/indexedDB/IDBTransaction.cpp
new file mode 100644
index 0000000000..d984bcacdf
--- /dev/null
+++ b/dom/indexedDB/IDBTransaction.cpp
@@ -0,0 +1,1019 @@
+/* -*- 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/HoldDropJSObjects.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/DOMStringList.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ScopeExit.h"
+#include "nsPIDOMWindow.h"
+#include "nsQueryObject.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTHashtable.h"
+#include "ProfilerHelpers.h"
+#include "ReportInternalError.h"
+#include "ThreadLocal.h"
+
+// Include this last to avoid path problems on Windows.
+#include "ActorsChild.h"
+
+namespace {
+using namespace mozilla::dom::indexedDB;
+using namespace mozilla::ipc;
+
+// TODO: Move this to xpcom/ds.
+template <typename T, typename Range, typename Transformation>
+nsTHashtable<T> TransformToHashtable(const Range& aRange,
+ const Transformation& aTransformation) {
+ // TODO: Determining the size of the range is not syntactically necessary (and
+ // requires random access iterators if expressed this way). It is a
+ // performance optimization. We could resort to std::distance to support any
+ // iterator category, but this would lead to a double iteration of the range
+ // in case of non-random-access iterators. It is hard to determine in general
+ // if double iteration or reallocation is worse.
+ auto res = nsTHashtable<T>(aRange.cend() - aRange.cbegin());
+ // TOOD: std::transform could be used if nsTHashtable had an insert_iterator,
+ // and this would also allow a more generic version not depending on
+ // nsTHashtable at all.
+ for (const auto& item : aRange) {
+ res.PutEntry(aTransformation(item));
+ }
+ return res;
+}
+
+ThreadLocal* GetIndexedDBThreadLocal() {
+ BackgroundChildImpl::ThreadLocal* const threadLocal =
+ BackgroundChildImpl::GetThreadLocalForCurrentThread();
+ MOZ_ASSERT(threadLocal);
+
+ ThreadLocal* idbThreadLocal = threadLocal->mIndexedDBThreadLocal.get();
+ MOZ_ASSERT(idbThreadLocal);
+
+ return idbThreadLocal;
+}
+} // namespace
+
+namespace mozilla::dom {
+
+using namespace mozilla::dom::indexedDB;
+using namespace mozilla::ipc;
+
+bool IDBTransaction::HasTransactionChild() const {
+ return (mMode == Mode::VersionChange
+ ? static_cast<void*>(
+ mBackgroundActor.mVersionChangeBackgroundActor)
+ : mBackgroundActor.mNormalBackgroundActor) != nullptr;
+}
+
+template <typename Func>
+auto IDBTransaction::DoWithTransactionChild(const Func& aFunc) const {
+ MOZ_ASSERT(HasTransactionChild());
+ return mMode == Mode::VersionChange
+ ? aFunc(*mBackgroundActor.mVersionChangeBackgroundActor)
+ : aFunc(*mBackgroundActor.mNormalBackgroundActor);
+}
+
+IDBTransaction::IDBTransaction(IDBDatabase* const aDatabase,
+ const nsTArray<nsString>& aObjectStoreNames,
+ const Mode aMode, nsString aFilename,
+ const uint32_t aLineNo, const uint32_t aColumn,
+ CreatedFromFactoryFunction /*aDummy*/)
+ : DOMEventTargetHelper(aDatabase),
+ mDatabase(aDatabase),
+ mObjectStoreNames(aObjectStoreNames.Clone()),
+ mLoggingSerialNumber(GetIndexedDBThreadLocal()->NextTransactionSN(aMode)),
+ mNextObjectStoreId(0),
+ mNextIndexId(0),
+ mAbortCode(NS_OK),
+ mPendingRequestCount(0),
+ mFilename(std::move(aFilename)),
+ mLineNo(aLineNo),
+ mColumn(aColumn),
+ mMode(aMode),
+ mRegistered(false),
+ mNotedActiveTransaction(false) {
+ MOZ_ASSERT(aDatabase);
+ aDatabase->AssertIsOnOwningThread();
+
+ // This also nulls mBackgroundActor.mVersionChangeBackgroundActor, so this is
+ // valid also for mMode == Mode::VersionChange.
+ mBackgroundActor.mNormalBackgroundActor = nullptr;
+
+#ifdef DEBUG
+ if (!aObjectStoreNames.IsEmpty()) {
+ // Make sure the array is properly sorted.
+ MOZ_ASSERT(
+ std::is_sorted(aObjectStoreNames.cbegin(), aObjectStoreNames.cend()));
+
+ // Make sure there are no duplicates in our objectStore names.
+ MOZ_ASSERT(aObjectStoreNames.cend() ==
+ std::adjacent_find(aObjectStoreNames.cbegin(),
+ aObjectStoreNames.cend()));
+ }
+#endif
+
+ mozilla::HoldJSObjects(this);
+}
+
+IDBTransaction::~IDBTransaction() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!mPendingRequestCount);
+ MOZ_ASSERT(mReadyState != ReadyState::Active);
+ MOZ_ASSERT(mReadyState != ReadyState::Inactive);
+ MOZ_ASSERT(mReadyState != ReadyState::Committing);
+ MOZ_ASSERT(!mNotedActiveTransaction);
+ MOZ_ASSERT(mSentCommitOrAbort);
+ MOZ_ASSERT_IF(HasTransactionChild(), mFiredCompleteOrAbort);
+
+ if (mRegistered) {
+ mDatabase->UnregisterTransaction(*this);
+#ifdef DEBUG
+ mRegistered = false;
+#endif
+ }
+
+ if (HasTransactionChild()) {
+ if (mMode == Mode::VersionChange) {
+ mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteMeInternal(
+ /* aFailedConstructor */ false);
+ } else {
+ mBackgroundActor.mNormalBackgroundActor->SendDeleteMeInternal();
+ }
+ }
+ MOZ_ASSERT(!HasTransactionChild(),
+ "SendDeleteMeInternal should have cleared!");
+
+ mozilla::DropJSObjects(this);
+}
+
+// static
+SafeRefPtr<IDBTransaction> IDBTransaction::CreateVersionChange(
+ IDBDatabase* const aDatabase,
+ BackgroundVersionChangeTransactionChild* const aActor,
+ const NotNull<IDBOpenDBRequest*> aOpenRequest,
+ const int64_t aNextObjectStoreId, const int64_t aNextIndexId) {
+ MOZ_ASSERT(aDatabase);
+ aDatabase->AssertIsOnOwningThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aNextObjectStoreId > 0);
+ MOZ_ASSERT(aNextIndexId > 0);
+
+ const nsTArray<nsString> emptyObjectStoreNames;
+
+ nsString filename;
+ uint32_t lineNo, column;
+ aOpenRequest->GetCallerLocation(filename, &lineNo, &column);
+ auto transaction = MakeSafeRefPtr<IDBTransaction>(
+ aDatabase, emptyObjectStoreNames, Mode::VersionChange,
+ std::move(filename), lineNo, column, CreatedFromFactoryFunction{});
+
+ transaction->NoteActiveTransaction();
+
+ transaction->mBackgroundActor.mVersionChangeBackgroundActor = aActor;
+ transaction->mNextObjectStoreId = aNextObjectStoreId;
+ transaction->mNextIndexId = aNextIndexId;
+
+ aDatabase->RegisterTransaction(*transaction);
+ transaction->mRegistered = true;
+
+ return transaction;
+}
+
+// static
+SafeRefPtr<IDBTransaction> IDBTransaction::Create(
+ JSContext* const aCx, IDBDatabase* const aDatabase,
+ const nsTArray<nsString>& aObjectStoreNames, const Mode aMode) {
+ MOZ_ASSERT(aDatabase);
+ aDatabase->AssertIsOnOwningThread();
+ MOZ_ASSERT(!aObjectStoreNames.IsEmpty());
+ MOZ_ASSERT(aMode == Mode::ReadOnly || aMode == Mode::ReadWrite ||
+ aMode == Mode::ReadWriteFlush || aMode == Mode::Cleanup);
+
+ nsString filename;
+ uint32_t lineNo, column;
+ IDBRequest::CaptureCaller(aCx, filename, &lineNo, &column);
+ auto transaction = MakeSafeRefPtr<IDBTransaction>(
+ aDatabase, aObjectStoreNames, aMode, std::move(filename), lineNo, column,
+ CreatedFromFactoryFunction{});
+
+ if (!NS_IsMainThread()) {
+ WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ workerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
+ workerPrivate, "IDBTransaction",
+ [transaction = AsRefPtr(transaction.clonePtr())]() {
+ transaction->AssertIsOnOwningThread();
+ if (!transaction->IsCommittingOrFinished()) {
+ IDB_REPORT_INTERNAL_ERR();
+ transaction->AbortInternal(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
+ nullptr);
+ }
+ });
+ if (NS_WARN_IF(!workerRef)) {
+#ifdef DEBUG
+ // Silence the destructor assertions if we never made this object live.
+ transaction->mReadyState = ReadyState::Finished;
+ transaction->mSentCommitOrAbort.Flip();
+#endif
+ return nullptr;
+ }
+
+ transaction->mWorkerRef = std::move(workerRef);
+ }
+
+ nsCOMPtr<nsIRunnable> runnable =
+ do_QueryObject(transaction.unsafeGetRawPtr());
+ nsContentUtils::AddPendingIDBTransaction(runnable.forget());
+
+ aDatabase->RegisterTransaction(*transaction);
+ transaction->mRegistered = true;
+
+ return transaction;
+}
+
+// static
+Maybe<IDBTransaction&> IDBTransaction::MaybeCurrent() {
+ using namespace mozilla::ipc;
+
+ MOZ_ASSERT(BackgroundChild::GetForCurrentThread());
+
+ return GetIndexedDBThreadLocal()->MaybeCurrentTransactionRef();
+}
+
+#ifdef DEBUG
+
+void IDBTransaction::AssertIsOnOwningThread() const {
+ MOZ_ASSERT(mDatabase);
+ mDatabase->AssertIsOnOwningThread();
+}
+
+#endif // DEBUG
+
+void IDBTransaction::SetBackgroundActor(
+ indexedDB::BackgroundTransactionChild* const aBackgroundActor) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aBackgroundActor);
+ MOZ_ASSERT(!mBackgroundActor.mNormalBackgroundActor);
+ MOZ_ASSERT(mMode != Mode::VersionChange);
+
+ NoteActiveTransaction();
+
+ mBackgroundActor.mNormalBackgroundActor = aBackgroundActor;
+}
+
+BackgroundRequestChild* IDBTransaction::StartRequest(
+ MovingNotNull<RefPtr<mozilla::dom::IDBRequest> > aRequest,
+ const RequestParams& aParams) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aParams.type() != RequestParams::T__None);
+
+ BackgroundRequestChild* const actor =
+ new BackgroundRequestChild(std::move(aRequest));
+
+ DoWithTransactionChild([actor, &aParams](auto& transactionChild) {
+ transactionChild.SendPBackgroundIDBRequestConstructor(actor, aParams);
+ });
+
+ // Balanced in BackgroundRequestChild::Recv__delete__().
+ OnNewRequest();
+
+ return actor;
+}
+
+void IDBTransaction::OpenCursor(PBackgroundIDBCursorChild& aBackgroundActor,
+ const OpenCursorParams& aParams) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
+
+ DoWithTransactionChild([&aBackgroundActor, &aParams](auto& actor) {
+ actor.SendPBackgroundIDBCursorConstructor(&aBackgroundActor, aParams);
+ });
+
+ // Balanced in BackgroundCursorChild::RecvResponse().
+ OnNewRequest();
+}
+
+void IDBTransaction::RefreshSpec(const bool aMayDelete) {
+ AssertIsOnOwningThread();
+
+ for (auto& objectStore : mObjectStores) {
+ objectStore->RefreshSpec(aMayDelete);
+ }
+
+ for (auto& objectStore : mDeletedObjectStores) {
+ objectStore->RefreshSpec(false);
+ }
+}
+
+void IDBTransaction::OnNewRequest() {
+ AssertIsOnOwningThread();
+
+ if (!mPendingRequestCount) {
+ MOZ_ASSERT(ReadyState::Active == mReadyState);
+ mStarted.Flip();
+ }
+
+ ++mPendingRequestCount;
+}
+
+void IDBTransaction::OnRequestFinished(
+ const bool aRequestCompletedSuccessfully) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mReadyState != ReadyState::Active);
+ MOZ_ASSERT_IF(mReadyState == ReadyState::Finished, !NS_SUCCEEDED(mAbortCode));
+ MOZ_ASSERT(mPendingRequestCount);
+
+ --mPendingRequestCount;
+
+ if (!mPendingRequestCount) {
+ if (mSentCommitOrAbort) {
+ return;
+ }
+
+ if (aRequestCompletedSuccessfully) {
+ if (mReadyState == ReadyState::Inactive) {
+ mReadyState = ReadyState::Committing;
+ }
+
+ if (NS_SUCCEEDED(mAbortCode)) {
+ SendCommit(true);
+ } else {
+ SendAbort(mAbortCode);
+ }
+ } else {
+ // Don't try to send any more messages to the parent if the request actor
+ // was killed. Set our state accordingly to Finished.
+ mReadyState = ReadyState::Finished;
+ mSentCommitOrAbort.Flip();
+ IDB_LOG_MARK_CHILD_TRANSACTION(
+ "Request actor was killed, transaction will be aborted",
+ "IDBTransaction abort", LoggingSerialNumber());
+ }
+ }
+}
+
+void IDBTransaction::SendCommit(const bool aAutoCommit) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(NS_SUCCEEDED(mAbortCode));
+ MOZ_ASSERT(IsCommittingOrFinished());
+
+ // 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(
+ "Committing transaction (%s)", "IDBTransaction commit (%s)",
+ LoggingSerialNumber(), requestSerialNumber,
+ aAutoCommit ? "automatically" : "explicitly");
+
+ const auto lastRequestSerialNumber =
+ [this, aAutoCommit,
+ requestSerialNumber]() -> Maybe<decltype(requestSerialNumber)> {
+ if (aAutoCommit) {
+ return Nothing();
+ }
+
+ // In case of an explicit commit, we need to note the serial number of the
+ // last request to check if a request submitted before the commit request
+ // failed. If we are currently in an event handler for a request on this
+ // transaction, ignore this request. This is used to synchronize the
+ // transaction's committing state with the parent side, to abort the
+ // transaction in case of a request resulting in an error (see
+ // https://w3c.github.io/IndexedDB/#async-execute-request, step 5.3.). With
+ // automatic commit, this is not necessary, as the transaction's state will
+ // only be set to committing after the last request completed.
+ const auto maybeCurrentTransaction =
+ BackgroundChildImpl::GetThreadLocalForCurrentThread()
+ ->mIndexedDBThreadLocal->MaybeCurrentTransactionRef();
+ const bool dispatchingEventForThisTransaction =
+ maybeCurrentTransaction && &maybeCurrentTransaction.ref() == this;
+
+ return Some(requestSerialNumber
+ ? (requestSerialNumber -
+ (dispatchingEventForThisTransaction ? 0 : 1))
+ : 0);
+ }();
+
+ DoWithTransactionChild([lastRequestSerialNumber](auto& actor) {
+ actor.SendCommit(lastRequestSerialNumber);
+ });
+
+ mSentCommitOrAbort.Flip();
+}
+
+void IDBTransaction::SendAbort(const nsresult aResultCode) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(NS_FAILED(aResultCode));
+ MOZ_ASSERT(IsCommittingOrFinished());
+
+ // 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(
+ "Aborting transaction with result 0x%" PRIx32,
+ "IDBTransaction abort (0x%" PRIx32 ")", LoggingSerialNumber(),
+ requestSerialNumber, static_cast<uint32_t>(aResultCode));
+
+ DoWithTransactionChild(
+ [aResultCode](auto& actor) { actor.SendAbort(aResultCode); });
+
+ mSentCommitOrAbort.Flip();
+}
+
+void IDBTransaction::NoteActiveTransaction() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!mNotedActiveTransaction);
+
+ mDatabase->NoteActiveTransaction();
+ mNotedActiveTransaction = true;
+}
+
+void IDBTransaction::MaybeNoteInactiveTransaction() {
+ AssertIsOnOwningThread();
+
+ if (mNotedActiveTransaction) {
+ mDatabase->NoteInactiveTransaction();
+ mNotedActiveTransaction = false;
+ }
+}
+
+void IDBTransaction::GetCallerLocation(nsAString& aFilename,
+ uint32_t* const aLineNo,
+ uint32_t* const aColumn) const {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aLineNo);
+ MOZ_ASSERT(aColumn);
+
+ aFilename = mFilename;
+ *aLineNo = mLineNo;
+ *aColumn = mColumn;
+}
+
+RefPtr<IDBObjectStore> IDBTransaction::CreateObjectStore(
+ ObjectStoreSpec& aSpec) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aSpec.metadata().id());
+ MOZ_ASSERT(Mode::VersionChange == mMode);
+ MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
+ MOZ_ASSERT(IsActive());
+
+#ifdef DEBUG
+ {
+ // TODO: Bind name outside of lambda capture as a workaround for GCC 7 bug
+ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66735.
+ const auto& name = aSpec.metadata().name();
+ // TODO: Use #ifdef and local variable as a workaround for Bug 1583449.
+ const bool objectStoreNameDoesNotYetExist =
+ std::all_of(mObjectStores.cbegin(), mObjectStores.cend(),
+ [&name](const auto& objectStore) {
+ return objectStore->Name() != name;
+ });
+ MOZ_ASSERT(objectStoreNameDoesNotYetExist);
+ }
+#endif
+
+ MOZ_ALWAYS_TRUE(
+ mBackgroundActor.mVersionChangeBackgroundActor->SendCreateObjectStore(
+ aSpec.metadata()));
+
+ RefPtr<IDBObjectStore> objectStore = IDBObjectStore::Create(
+ SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, aSpec);
+ MOZ_ASSERT(objectStore);
+
+ mObjectStores.AppendElement(objectStore);
+
+ return objectStore;
+}
+
+void IDBTransaction::DeleteObjectStore(const int64_t aObjectStoreId) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aObjectStoreId);
+ MOZ_ASSERT(Mode::VersionChange == mMode);
+ MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
+ MOZ_ASSERT(IsActive());
+
+ MOZ_ALWAYS_TRUE(
+ mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteObjectStore(
+ aObjectStoreId));
+
+ const auto foundIt =
+ std::find_if(mObjectStores.begin(), mObjectStores.end(),
+ [aObjectStoreId](const auto& objectStore) {
+ return objectStore->Id() == aObjectStoreId;
+ });
+ if (foundIt != mObjectStores.end()) {
+ auto& objectStore = *foundIt;
+ objectStore->NoteDeletion();
+
+ RefPtr<IDBObjectStore>* deletedObjectStore =
+ mDeletedObjectStores.AppendElement();
+ deletedObjectStore->swap(objectStore);
+
+ mObjectStores.RemoveElementAt(foundIt);
+ }
+}
+
+void IDBTransaction::RenameObjectStore(const int64_t aObjectStoreId,
+ const nsAString& aName) const {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aObjectStoreId);
+ MOZ_ASSERT(Mode::VersionChange == mMode);
+ MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
+ MOZ_ASSERT(IsActive());
+
+ MOZ_ALWAYS_TRUE(
+ mBackgroundActor.mVersionChangeBackgroundActor->SendRenameObjectStore(
+ aObjectStoreId, nsString(aName)));
+}
+
+void IDBTransaction::CreateIndex(
+ IDBObjectStore* const aObjectStore,
+ const indexedDB::IndexMetadata& aMetadata) const {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aObjectStore);
+ MOZ_ASSERT(aMetadata.id());
+ MOZ_ASSERT(Mode::VersionChange == mMode);
+ MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
+ MOZ_ASSERT(IsActive());
+
+ MOZ_ALWAYS_TRUE(
+ mBackgroundActor.mVersionChangeBackgroundActor->SendCreateIndex(
+ aObjectStore->Id(), aMetadata));
+}
+
+void IDBTransaction::DeleteIndex(IDBObjectStore* const aObjectStore,
+ const int64_t aIndexId) const {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aObjectStore);
+ MOZ_ASSERT(aIndexId);
+ MOZ_ASSERT(Mode::VersionChange == mMode);
+ MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
+ MOZ_ASSERT(IsActive());
+
+ MOZ_ALWAYS_TRUE(
+ mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteIndex(
+ aObjectStore->Id(), aIndexId));
+}
+
+void IDBTransaction::RenameIndex(IDBObjectStore* const aObjectStore,
+ const int64_t aIndexId,
+ const nsAString& aName) const {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aObjectStore);
+ MOZ_ASSERT(aIndexId);
+ MOZ_ASSERT(Mode::VersionChange == mMode);
+ MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
+ MOZ_ASSERT(IsActive());
+
+ MOZ_ALWAYS_TRUE(
+ mBackgroundActor.mVersionChangeBackgroundActor->SendRenameIndex(
+ aObjectStore->Id(), aIndexId, nsString(aName)));
+}
+
+void IDBTransaction::AbortInternal(const nsresult aAbortCode,
+ RefPtr<DOMException> aError) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(NS_FAILED(aAbortCode));
+ MOZ_ASSERT(!IsCommittingOrFinished());
+
+ const bool isVersionChange = mMode == Mode::VersionChange;
+ const bool needToSendAbort = !mStarted;
+
+ mAbortCode = aAbortCode;
+ mReadyState = ReadyState::Finished;
+ mError = std::move(aError);
+
+ 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 (!mDatabase->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->IsActive().
+
+ const nsTArray<ObjectStoreSpec>& specArray =
+ mDatabase->Spec()->objectStores();
+
+ if (specArray.IsEmpty()) {
+ // This case is specially handled as a performance optimization, it is
+ // equivalent to the else block.
+ mObjectStores.Clear();
+ } else {
+ const auto validIds = TransformToHashtable<nsUint64HashKey>(
+ specArray, [](const auto& spec) {
+ const int64_t objectStoreId = spec.metadata().id();
+ MOZ_ASSERT(objectStoreId);
+ return static_cast<uint64_t>(objectStoreId);
+ });
+
+ mObjectStores.RemoveLastElements(
+ mObjectStores.end() -
+ std::remove_if(mObjectStores.begin(), mObjectStores.end(),
+ [&validIds](const auto& objectStore) {
+ return !validIds.Contains(
+ uint64_t(objectStore->Id()));
+ }));
+
+ std::copy_if(std::make_move_iterator(mDeletedObjectStores.begin()),
+ std::make_move_iterator(mDeletedObjectStores.end()),
+ MakeBackInserter(mObjectStores),
+ [&validIds](const auto& deletedObjectStore) {
+ const int64_t objectStoreId = deletedObjectStore->Id();
+ MOZ_ASSERT(objectStoreId);
+ return validIds.Contains(uint64_t(objectStoreId));
+ });
+ }
+ 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* const aRequest) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aRequest);
+
+ if (IsCommittingOrFinished()) {
+ // Already started (and maybe finished) the commit or abort so there is
+ // nothing to do here.
+ return;
+ }
+
+ ErrorResult rv;
+ RefPtr<DOMException> error = aRequest->GetError(rv);
+
+ // TODO: Do we deliberately ignore rv here? Isn't there a static analysis that
+ // prevents that?
+
+ AbortInternal(aRequest->GetErrorCode(), std::move(error));
+}
+
+void IDBTransaction::Abort(const nsresult aErrorCode) {
+ AssertIsOnOwningThread();
+
+ if (IsCommittingOrFinished()) {
+ // Already started (and maybe finished) the commit or abort so there is
+ // nothing to do here.
+ return;
+ }
+
+ AbortInternal(aErrorCode, DOMException::Create(aErrorCode));
+}
+
+// Specified by https://w3c.github.io/IndexedDB/#dom-idbtransaction-abort.
+void IDBTransaction::Abort(ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ if (IsCommittingOrFinished()) {
+ aRv = NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
+ return;
+ }
+
+ mReadyState = ReadyState::Inactive;
+
+ AbortInternal(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR, nullptr);
+
+ mAbortedByScript.Flip();
+}
+
+// Specified by https://w3c.github.io/IndexedDB/#dom-idbtransaction-commit.
+void IDBTransaction::Commit(ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ if (mReadyState != ReadyState::Active || !mNotedActiveTransaction) {
+ aRv = NS_ERROR_DOM_INVALID_STATE_ERR;
+ return;
+ }
+
+ MOZ_ASSERT(!mSentCommitOrAbort);
+
+ MOZ_ASSERT(mReadyState == ReadyState::Active);
+ mReadyState = ReadyState::Committing;
+ if (NS_WARN_IF(NS_FAILED(mAbortCode))) {
+ SendAbort(mAbortCode);
+ aRv = mAbortCode;
+ return;
+ }
+
+#ifdef DEBUG
+ mWasExplicitlyCommitted.Flip();
+#endif
+
+ SendCommit(false);
+}
+
+void IDBTransaction::FireCompleteOrAbortEvents(const nsresult aResult) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!mFiredCompleteOrAbort);
+
+ mReadyState = ReadyState::Finished;
+
+#ifdef DEBUG
+ mFiredCompleteOrAbort.Flip();
+#endif
+
+ // Make sure we drop the WorkerRef when this function completes.
+ const auto scopeExit = MakeScopeExit([&] { mWorkerRef = nullptr; });
+
+ RefPtr<Event> event;
+ if (NS_SUCCEEDED(aResult)) {
+ event = CreateGenericEvent(this, nsDependentString(kCompleteEventType),
+ eDoesNotBubble, eNotCancelable);
+ MOZ_ASSERT(event);
+
+ // If we hit this assertion, it probably means transaction object on the
+ // parent process doesn't propagate error properly.
+ MOZ_ASSERT(NS_SUCCEEDED(mAbortCode));
+ } else {
+ if (aResult == NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR) {
+ mDatabase->SetQuotaExceeded();
+ }
+
+ if (!mError && !mAbortedByScript) {
+ mError = DOMException::Create(aResult);
+ }
+
+ event = CreateGenericEvent(this, nsDependentString(kAbortEventType),
+ eDoesBubble, eNotCancelable);
+ MOZ_ASSERT(event);
+
+ if (NS_SUCCEEDED(mAbortCode)) {
+ mAbortCode = aResult;
+ }
+ }
+
+ if (NS_SUCCEEDED(mAbortCode)) {
+ IDB_LOG_MARK_CHILD_TRANSACTION("Firing 'complete' event",
+ "IDBTransaction 'complete' event",
+ mLoggingSerialNumber);
+ } else {
+ IDB_LOG_MARK_CHILD_TRANSACTION(
+ "Firing 'abort' event with error 0x%" PRIx32,
+ "IDBTransaction 'abort' event (0x%" PRIx32 ")", mLoggingSerialNumber,
+ static_cast<uint32_t>(mAbortCode));
+ }
+
+ IgnoredErrorResult rv;
+ DispatchEvent(*event, rv);
+ if (rv.Failed()) {
+ NS_WARNING("DispatchEvent failed!");
+ }
+
+ // Normally, we note inactive transaction here instead of
+ // IDBTransaction::ClearBackgroundActor() because here is the earliest place
+ // to know that it becomes non-blocking to allow the scheduler to start the
+ // preemption as soon as it can.
+ // Note: If the IDBTransaction object is held by the script,
+ // ClearBackgroundActor() will be done in ~IDBTransaction() until garbage
+ // collected after its window is closed which prevents us to preempt its
+ // window immediately after committed.
+ MaybeNoteInactiveTransaction();
+}
+
+int64_t IDBTransaction::NextObjectStoreId() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(Mode::VersionChange == mMode);
+
+ return mNextObjectStoreId++;
+}
+
+int64_t IDBTransaction::NextIndexId() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(Mode::VersionChange == mMode);
+
+ return mNextIndexId++;
+}
+
+void IDBTransaction::InvalidateCursorCaches() {
+ AssertIsOnOwningThread();
+
+ for (const auto& cursor : mCursors) {
+ cursor->InvalidateCachedResponses();
+ }
+}
+
+void IDBTransaction::RegisterCursor(IDBCursor& aCursor) {
+ AssertIsOnOwningThread();
+
+ mCursors.AppendElement(WrapNotNullUnchecked(&aCursor));
+}
+
+void IDBTransaction::UnregisterCursor(IDBCursor& aCursor) {
+ AssertIsOnOwningThread();
+
+ DebugOnly<bool> removed = mCursors.RemoveElement(&aCursor);
+ MOZ_ASSERT(removed);
+}
+
+nsIGlobalObject* IDBTransaction::GetParentObject() const {
+ AssertIsOnOwningThread();
+
+ return mDatabase->GetParentObject();
+}
+
+IDBTransactionMode IDBTransaction::GetMode(ErrorResult& aRv) const {
+ AssertIsOnOwningThread();
+
+ switch (mMode) {
+ case Mode::ReadOnly:
+ return IDBTransactionMode::Readonly;
+
+ case Mode::ReadWrite:
+ return IDBTransactionMode::Readwrite;
+
+ case Mode::ReadWriteFlush:
+ return IDBTransactionMode::Readwriteflush;
+
+ case Mode::Cleanup:
+ return IDBTransactionMode::Cleanup;
+
+ case Mode::VersionChange:
+ return IDBTransactionMode::Versionchange;
+
+ case Mode::Invalid:
+ default:
+ MOZ_CRASH("Bad mode!");
+ }
+}
+
+DOMException* IDBTransaction::GetError() const {
+ AssertIsOnOwningThread();
+
+ return mError;
+}
+
+RefPtr<DOMStringList> IDBTransaction::ObjectStoreNames() const {
+ AssertIsOnOwningThread();
+
+ if (mMode == Mode::VersionChange) {
+ return mDatabase->ObjectStoreNames();
+ }
+
+ auto list = MakeRefPtr<DOMStringList>();
+ list->StringArray() = mObjectStoreNames.Clone();
+ return list;
+}
+
+RefPtr<IDBObjectStore> IDBTransaction::ObjectStore(const nsAString& aName,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ if (IsCommittingOrFinished()) {
+ aRv.ThrowInvalidStateError("Transaction is already committing or done.");
+ return nullptr;
+ }
+
+ auto* const spec = [this, &aName]() -> ObjectStoreSpec* {
+ if (IDBTransaction::Mode::VersionChange == mMode ||
+ mObjectStoreNames.Contains(aName)) {
+ return mDatabase->LookupModifiableObjectStoreSpec(
+ [&aName](const auto& objectStore) {
+ return objectStore.metadata().name() == aName;
+ });
+ }
+ return nullptr;
+ }();
+
+ if (!spec) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
+ return nullptr;
+ }
+
+ RefPtr<IDBObjectStore> objectStore;
+
+ const auto foundIt = std::find_if(
+ mObjectStores.cbegin(), mObjectStores.cend(),
+ [desiredId = spec->metadata().id()](const auto& existingObjectStore) {
+ return existingObjectStore->Id() == desiredId;
+ });
+ if (foundIt != mObjectStores.cend()) {
+ objectStore = *foundIt;
+ } else {
+ objectStore = IDBObjectStore::Create(
+ SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, *spec);
+ MOZ_ASSERT(objectStore);
+
+ mObjectStores.AppendElement(objectStore);
+ }
+
+ return objectStore;
+}
+
+NS_IMPL_ADDREF_INHERITED(IDBTransaction, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(IDBTransaction, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBTransaction)
+ NS_INTERFACE_MAP_ENTRY(nsIRunnable)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(IDBTransaction)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBTransaction,
+ DOMEventTargetHelper)
+ 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,
+ DOMEventTargetHelper)
+ // 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* const aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ AssertIsOnOwningThread();
+
+ return IDBTransaction_Binding::Wrap(aCx, this, std::move(aGivenProto));
+}
+
+void IDBTransaction::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
+ AssertIsOnOwningThread();
+
+ aVisitor.mCanHandle = true;
+ aVisitor.SetParentTarget(mDatabase, false);
+}
+
+NS_IMETHODIMP
+IDBTransaction::Run() {
+ AssertIsOnOwningThread();
+
+ // TODO: Instead of checking for Finished and Committing states here, we could
+ // remove the transaction from the pending IDB transactions list on
+ // abort/commit.
+
+ if (ReadyState::Finished == mReadyState) {
+ // There are three cases where mReadyState is set to Finished: In
+ // FileCompleteOrAbortEvents, AbortInternal and in CommitIfNotStarted. We
+ // shouldn't get here after CommitIfNotStarted again.
+ MOZ_ASSERT(mFiredCompleteOrAbort || IsAborted());
+ return NS_OK;
+ }
+
+ if (ReadyState::Committing == mReadyState) {
+ MOZ_ASSERT(mSentCommitOrAbort);
+ return NS_OK;
+ }
+ // We're back at the event loop, no longer newborn, so
+ // return to Inactive state:
+ // https://w3c.github.io/IndexedDB/#cleanup-indexed-database-transactions.
+ MOZ_ASSERT(ReadyState::Active == mReadyState);
+ mReadyState = ReadyState::Inactive;
+
+ CommitIfNotStarted();
+
+ return NS_OK;
+}
+
+void IDBTransaction::CommitIfNotStarted() {
+ AssertIsOnOwningThread();
+
+ MOZ_ASSERT(ReadyState::Inactive == mReadyState);
+
+ // Maybe commit if there were no requests generated.
+ if (!mStarted) {
+ MOZ_ASSERT(!mPendingRequestCount);
+ mReadyState = ReadyState::Finished;
+
+ SendCommit(true);
+ }
+}
+
+} // namespace mozilla::dom