Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / indexedDB / IDBTransaction.cpp
blobb35bea6eff109938a86d5c0b5c75f4b80541106a
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "IDBTransaction.h"
9 #include "BackgroundChildImpl.h"
10 #include "IDBDatabase.h"
11 #include "IDBEvents.h"
12 #include "IDBObjectStore.h"
13 #include "IDBRequest.h"
14 #include "mozilla/ErrorResult.h"
15 #include "mozilla/EventDispatcher.h"
16 #include "mozilla/HoldDropJSObjects.h"
17 #include "mozilla/dom/DOMException.h"
18 #include "mozilla/dom/DOMStringList.h"
19 #include "mozilla/dom/WorkerRef.h"
20 #include "mozilla/dom/WorkerPrivate.h"
21 #include "mozilla/ipc/BackgroundChild.h"
22 #include "mozilla/ScopeExit.h"
23 #include "nsPIDOMWindow.h"
24 #include "nsQueryObject.h"
25 #include "nsServiceManagerUtils.h"
26 #include "nsTHashtable.h"
27 #include "ProfilerHelpers.h"
28 #include "ReportInternalError.h"
29 #include "ThreadLocal.h"
31 // Include this last to avoid path problems on Windows.
32 #include "ActorsChild.h"
34 namespace {
35 using namespace mozilla::dom::indexedDB;
36 using namespace mozilla::ipc;
38 // TODO: Move this to xpcom/ds.
39 template <typename T, typename Range, typename Transformation>
40 nsTHashtable<T> TransformToHashtable(const Range& aRange,
41 const Transformation& aTransformation) {
42 // TODO: Determining the size of the range is not syntactically necessary (and
43 // requires random access iterators if expressed this way). It is a
44 // performance optimization. We could resort to std::distance to support any
45 // iterator category, but this would lead to a double iteration of the range
46 // in case of non-random-access iterators. It is hard to determine in general
47 // if double iteration or reallocation is worse.
48 auto res = nsTHashtable<T>(aRange.cend() - aRange.cbegin());
49 // TOOD: std::transform could be used if nsTHashtable had an insert_iterator,
50 // and this would also allow a more generic version not depending on
51 // nsTHashtable at all.
52 for (const auto& item : aRange) {
53 res.PutEntry(aTransformation(item));
55 return res;
58 ThreadLocal* GetIndexedDBThreadLocal() {
59 BackgroundChildImpl::ThreadLocal* const threadLocal =
60 BackgroundChildImpl::GetThreadLocalForCurrentThread();
61 MOZ_ASSERT(threadLocal);
63 ThreadLocal* idbThreadLocal = threadLocal->mIndexedDBThreadLocal.get();
64 MOZ_ASSERT(idbThreadLocal);
66 return idbThreadLocal;
68 } // namespace
70 namespace mozilla::dom {
72 using namespace mozilla::dom::indexedDB;
73 using namespace mozilla::ipc;
75 bool IDBTransaction::HasTransactionChild() const {
76 return (mMode == Mode::VersionChange
77 ? static_cast<void*>(
78 mBackgroundActor.mVersionChangeBackgroundActor)
79 : mBackgroundActor.mNormalBackgroundActor) != nullptr;
82 template <typename Func>
83 auto IDBTransaction::DoWithTransactionChild(const Func& aFunc) const {
84 MOZ_ASSERT(HasTransactionChild());
85 return mMode == Mode::VersionChange
86 ? aFunc(*mBackgroundActor.mVersionChangeBackgroundActor)
87 : aFunc(*mBackgroundActor.mNormalBackgroundActor);
90 IDBTransaction::IDBTransaction(IDBDatabase* const aDatabase,
91 const nsTArray<nsString>& aObjectStoreNames,
92 const Mode aMode, const Durability aDurability,
93 nsString aFilename, const uint32_t aLineNo,
94 const uint32_t aColumn,
95 CreatedFromFactoryFunction /*aDummy*/)
96 : DOMEventTargetHelper(aDatabase),
97 mDatabase(aDatabase),
98 mObjectStoreNames(aObjectStoreNames.Clone()),
99 mLoggingSerialNumber(GetIndexedDBThreadLocal()->NextTransactionSN(aMode)),
100 mNextObjectStoreId(0),
101 mNextIndexId(0),
102 mNextRequestId(0),
103 mAbortCode(NS_OK),
104 mPendingRequestCount(0),
105 mFilename(std::move(aFilename)),
106 mLineNo(aLineNo),
107 mColumn(aColumn),
108 mMode(aMode),
109 mDurability(aDurability),
110 mRegistered(false),
111 mNotedActiveTransaction(false) {
112 MOZ_ASSERT(aDatabase);
113 aDatabase->AssertIsOnOwningThread();
115 // This also nulls mBackgroundActor.mVersionChangeBackgroundActor, so this is
116 // valid also for mMode == Mode::VersionChange.
117 mBackgroundActor.mNormalBackgroundActor = nullptr;
119 #ifdef DEBUG
120 if (!aObjectStoreNames.IsEmpty()) {
121 // Make sure the array is properly sorted.
122 MOZ_ASSERT(
123 std::is_sorted(aObjectStoreNames.cbegin(), aObjectStoreNames.cend()));
125 // Make sure there are no duplicates in our objectStore names.
126 MOZ_ASSERT(aObjectStoreNames.cend() ==
127 std::adjacent_find(aObjectStoreNames.cbegin(),
128 aObjectStoreNames.cend()));
130 #endif
132 mozilla::HoldJSObjects(this);
135 IDBTransaction::~IDBTransaction() {
136 AssertIsOnOwningThread();
137 MOZ_ASSERT(!mPendingRequestCount);
138 MOZ_ASSERT(mReadyState != ReadyState::Active);
139 MOZ_ASSERT(mReadyState != ReadyState::Inactive);
140 MOZ_ASSERT(mReadyState != ReadyState::Committing);
141 MOZ_ASSERT(!mNotedActiveTransaction);
142 MOZ_ASSERT(mSentCommitOrAbort);
143 MOZ_ASSERT_IF(HasTransactionChild(), mFiredCompleteOrAbort);
145 if (mRegistered) {
146 mDatabase->UnregisterTransaction(*this);
147 #ifdef DEBUG
148 mRegistered = false;
149 #endif
152 if (HasTransactionChild()) {
153 if (mMode == Mode::VersionChange) {
154 mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteMeInternal(
155 /* aFailedConstructor */ false);
156 } else {
157 mBackgroundActor.mNormalBackgroundActor->SendDeleteMeInternal();
160 MOZ_ASSERT(!HasTransactionChild(),
161 "SendDeleteMeInternal should have cleared!");
163 mozilla::DropJSObjects(this);
166 // static
167 SafeRefPtr<IDBTransaction> IDBTransaction::CreateVersionChange(
168 IDBDatabase* const aDatabase,
169 BackgroundVersionChangeTransactionChild* const aActor,
170 const NotNull<IDBOpenDBRequest*> aOpenRequest,
171 const int64_t aNextObjectStoreId, const int64_t aNextIndexId) {
172 MOZ_ASSERT(aDatabase);
173 aDatabase->AssertIsOnOwningThread();
174 MOZ_ASSERT(aActor);
175 MOZ_ASSERT(aNextObjectStoreId > 0);
176 MOZ_ASSERT(aNextIndexId > 0);
178 const nsTArray<nsString> emptyObjectStoreNames;
180 nsString filename;
181 uint32_t lineNo, column;
182 aOpenRequest->GetCallerLocation(filename, &lineNo, &column);
183 // XXX: What should we have as durability hint here?
184 auto transaction = MakeSafeRefPtr<IDBTransaction>(
185 aDatabase, emptyObjectStoreNames, Mode::VersionChange,
186 Durability::Default, std::move(filename), lineNo, column,
187 CreatedFromFactoryFunction{});
189 transaction->NoteActiveTransaction();
191 transaction->mBackgroundActor.mVersionChangeBackgroundActor = aActor;
192 transaction->mNextObjectStoreId = aNextObjectStoreId;
193 transaction->mNextIndexId = aNextIndexId;
195 aDatabase->RegisterTransaction(*transaction);
196 transaction->mRegistered = true;
198 return transaction;
201 // static
202 SafeRefPtr<IDBTransaction> IDBTransaction::Create(
203 JSContext* const aCx, IDBDatabase* const aDatabase,
204 const nsTArray<nsString>& aObjectStoreNames, const Mode aMode,
205 const Durability aDurability) {
206 MOZ_ASSERT(aDatabase);
207 aDatabase->AssertIsOnOwningThread();
208 MOZ_ASSERT(!aObjectStoreNames.IsEmpty());
209 MOZ_ASSERT(aMode == Mode::ReadOnly || aMode == Mode::ReadWrite ||
210 aMode == Mode::ReadWriteFlush || aMode == Mode::Cleanup);
212 nsString filename;
213 uint32_t lineNo, column;
214 IDBRequest::CaptureCaller(aCx, filename, &lineNo, &column);
215 auto transaction = MakeSafeRefPtr<IDBTransaction>(
216 aDatabase, aObjectStoreNames, aMode, aDurability, std::move(filename),
217 lineNo, column, CreatedFromFactoryFunction{});
219 if (!NS_IsMainThread()) {
220 WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate();
221 MOZ_ASSERT(workerPrivate);
223 workerPrivate->AssertIsOnWorkerThread();
225 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
226 workerPrivate, "IDBTransaction",
227 [transaction = AsRefPtr(transaction.clonePtr())]() {
228 transaction->AssertIsOnOwningThread();
229 if (!transaction->IsCommittingOrFinished()) {
230 IDB_REPORT_INTERNAL_ERR();
231 transaction->AbortInternal(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
232 nullptr);
235 if (NS_WARN_IF(!workerRef)) {
236 #ifdef DEBUG
237 // Silence the destructor assertions if we never made this object live.
238 transaction->mReadyState = ReadyState::Finished;
239 transaction->mSentCommitOrAbort.Flip();
240 #endif
241 return nullptr;
244 transaction->mWorkerRef = std::move(workerRef);
247 nsCOMPtr<nsIRunnable> runnable =
248 do_QueryObject(transaction.unsafeGetRawPtr());
249 nsContentUtils::AddPendingIDBTransaction(runnable.forget());
251 aDatabase->RegisterTransaction(*transaction);
252 transaction->mRegistered = true;
254 return transaction;
257 // static
258 Maybe<IDBTransaction&> IDBTransaction::MaybeCurrent() {
259 using namespace mozilla::ipc;
261 MOZ_ASSERT(BackgroundChild::GetForCurrentThread());
263 return GetIndexedDBThreadLocal()->MaybeCurrentTransactionRef();
266 #ifdef DEBUG
268 void IDBTransaction::AssertIsOnOwningThread() const {
269 MOZ_ASSERT(mDatabase);
270 mDatabase->AssertIsOnOwningThread();
273 #endif // DEBUG
275 void IDBTransaction::SetBackgroundActor(
276 indexedDB::BackgroundTransactionChild* const aBackgroundActor) {
277 AssertIsOnOwningThread();
278 MOZ_ASSERT(aBackgroundActor);
279 MOZ_ASSERT(!mBackgroundActor.mNormalBackgroundActor);
280 MOZ_ASSERT(mMode != Mode::VersionChange);
282 NoteActiveTransaction();
284 mBackgroundActor.mNormalBackgroundActor = aBackgroundActor;
287 BackgroundRequestChild* IDBTransaction::StartRequest(
288 MovingNotNull<RefPtr<mozilla::dom::IDBRequest> > aRequest,
289 const RequestParams& aParams) {
290 AssertIsOnOwningThread();
291 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
293 BackgroundRequestChild* const actor =
294 new BackgroundRequestChild(std::move(aRequest));
296 DoWithTransactionChild([this, actor, &aParams](auto& transactionChild) {
297 transactionChild.SendPBackgroundIDBRequestConstructor(
298 actor, NextRequestId(), aParams);
301 // Balanced in BackgroundRequestChild::Recv__delete__().
302 OnNewRequest();
304 return actor;
307 void IDBTransaction::OpenCursor(PBackgroundIDBCursorChild& aBackgroundActor,
308 const OpenCursorParams& aParams) {
309 AssertIsOnOwningThread();
310 MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
312 DoWithTransactionChild([this, &aBackgroundActor, &aParams](auto& actor) {
313 actor.SendPBackgroundIDBCursorConstructor(&aBackgroundActor,
314 NextRequestId(), aParams);
317 // Balanced in BackgroundCursorChild::RecvResponse().
318 OnNewRequest();
321 void IDBTransaction::RefreshSpec(const bool aMayDelete) {
322 AssertIsOnOwningThread();
324 for (auto& objectStore : mObjectStores) {
325 objectStore->RefreshSpec(aMayDelete);
328 for (auto& objectStore : mDeletedObjectStores) {
329 objectStore->RefreshSpec(false);
333 void IDBTransaction::OnNewRequest() {
334 AssertIsOnOwningThread();
336 if (!mPendingRequestCount) {
337 MOZ_ASSERT(ReadyState::Active == mReadyState);
338 mStarted.Flip();
341 ++mPendingRequestCount;
344 void IDBTransaction::OnRequestFinished(
345 const bool aRequestCompletedSuccessfully) {
346 AssertIsOnOwningThread();
347 MOZ_ASSERT(mReadyState != ReadyState::Active);
348 MOZ_ASSERT_IF(mReadyState == ReadyState::Finished, !NS_SUCCEEDED(mAbortCode));
349 MOZ_ASSERT(mPendingRequestCount);
351 --mPendingRequestCount;
353 if (!mPendingRequestCount) {
354 if (mSentCommitOrAbort) {
355 return;
358 if (aRequestCompletedSuccessfully) {
359 if (mReadyState == ReadyState::Inactive) {
360 mReadyState = ReadyState::Committing;
363 if (NS_SUCCEEDED(mAbortCode)) {
364 SendCommit(true);
365 } else {
366 SendAbort(mAbortCode);
368 } else {
369 // Don't try to send any more messages to the parent if the request actor
370 // was killed. Set our state accordingly to Finished.
371 mReadyState = ReadyState::Finished;
372 mSentCommitOrAbort.Flip();
373 IDB_LOG_MARK_CHILD_TRANSACTION(
374 "Request actor was killed, transaction will be aborted",
375 "IDBTransaction abort", LoggingSerialNumber());
380 void IDBTransaction::SendCommit(const bool aAutoCommit) {
381 AssertIsOnOwningThread();
382 MOZ_ASSERT(NS_SUCCEEDED(mAbortCode));
383 MOZ_ASSERT(IsCommittingOrFinished());
385 // Don't do this in the macro because we always need to increment the serial
386 // number to keep in sync with the parent.
387 const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
389 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
390 "Committing transaction (%s)", "IDBTransaction commit (%s)",
391 LoggingSerialNumber(), requestSerialNumber,
392 aAutoCommit ? "automatically" : "explicitly");
394 const int64_t requestId = NextRequestId();
396 const auto lastRequestId = [this, aAutoCommit,
397 requestId]() -> Maybe<decltype(requestId)> {
398 if (aAutoCommit) {
399 return Nothing();
402 // In case of an explicit commit, we need to note the id of the last
403 // request to check if a request submitted before the commit request
404 // failed. If we are currently in an event handler for a request on this
405 // transaction, ignore this request. This is used to synchronize the
406 // transaction's committing state with the parent side, to abort the
407 // transaction in case of a request resulting in an error (see
408 // https://w3c.github.io/IndexedDB/#async-execute-request, step 5.3.). With
409 // automatic commit, this is not necessary, as the transaction's state will
410 // only be set to committing after the last request completed.
411 const auto maybeCurrentTransaction =
412 BackgroundChildImpl::GetThreadLocalForCurrentThread()
413 ->mIndexedDBThreadLocal->MaybeCurrentTransactionRef();
414 const bool dispatchingEventForThisTransaction =
415 maybeCurrentTransaction && &maybeCurrentTransaction.ref() == this;
417 return Some(requestId
418 ? (requestId - (dispatchingEventForThisTransaction ? 0 : 1))
419 : 0);
420 }();
422 DoWithTransactionChild(
423 [lastRequestId](auto& actor) { actor.SendCommit(lastRequestId); });
425 mSentCommitOrAbort.Flip();
428 void IDBTransaction::SendAbort(const nsresult aResultCode) {
429 AssertIsOnOwningThread();
430 MOZ_ASSERT(NS_FAILED(aResultCode));
431 MOZ_ASSERT(IsCommittingOrFinished());
433 // Don't do this in the macro because we always need to increment the serial
434 // number to keep in sync with the parent.
435 const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
437 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
438 "Aborting transaction with result 0x%" PRIx32,
439 "IDBTransaction abort (0x%" PRIx32 ")", LoggingSerialNumber(),
440 requestSerialNumber, static_cast<uint32_t>(aResultCode));
442 DoWithTransactionChild(
443 [aResultCode](auto& actor) { actor.SendAbort(aResultCode); });
445 mSentCommitOrAbort.Flip();
448 void IDBTransaction::NoteActiveTransaction() {
449 AssertIsOnOwningThread();
450 MOZ_ASSERT(!mNotedActiveTransaction);
452 mDatabase->NoteActiveTransaction();
453 mNotedActiveTransaction = true;
456 void IDBTransaction::MaybeNoteInactiveTransaction() {
457 AssertIsOnOwningThread();
459 if (mNotedActiveTransaction) {
460 mDatabase->NoteInactiveTransaction();
461 mNotedActiveTransaction = false;
465 void IDBTransaction::GetCallerLocation(nsAString& aFilename,
466 uint32_t* const aLineNo,
467 uint32_t* const aColumn) const {
468 AssertIsOnOwningThread();
469 MOZ_ASSERT(aLineNo);
470 MOZ_ASSERT(aColumn);
472 aFilename = mFilename;
473 *aLineNo = mLineNo;
474 *aColumn = mColumn;
477 RefPtr<IDBObjectStore> IDBTransaction::CreateObjectStore(
478 ObjectStoreSpec& aSpec) {
479 AssertIsOnOwningThread();
480 MOZ_ASSERT(aSpec.metadata().id());
481 MOZ_ASSERT(Mode::VersionChange == mMode);
482 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
483 MOZ_ASSERT(IsActive());
485 #ifdef DEBUG
487 // TODO: Bind name outside of lambda capture as a workaround for GCC 7 bug
488 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66735.
489 const auto& name = aSpec.metadata().name();
490 // TODO: Use #ifdef and local variable as a workaround for Bug 1583449.
491 const bool objectStoreNameDoesNotYetExist =
492 std::all_of(mObjectStores.cbegin(), mObjectStores.cend(),
493 [&name](const auto& objectStore) {
494 return objectStore->Name() != name;
496 MOZ_ASSERT(objectStoreNameDoesNotYetExist);
498 #endif
500 MOZ_ALWAYS_TRUE(
501 mBackgroundActor.mVersionChangeBackgroundActor->SendCreateObjectStore(
502 aSpec.metadata()));
504 RefPtr<IDBObjectStore> objectStore = IDBObjectStore::Create(
505 SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, aSpec);
506 MOZ_ASSERT(objectStore);
508 mObjectStores.AppendElement(objectStore);
510 return objectStore;
513 void IDBTransaction::DeleteObjectStore(const int64_t aObjectStoreId) {
514 AssertIsOnOwningThread();
515 MOZ_ASSERT(aObjectStoreId);
516 MOZ_ASSERT(Mode::VersionChange == mMode);
517 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
518 MOZ_ASSERT(IsActive());
520 MOZ_ALWAYS_TRUE(
521 mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteObjectStore(
522 aObjectStoreId));
524 const auto foundIt =
525 std::find_if(mObjectStores.begin(), mObjectStores.end(),
526 [aObjectStoreId](const auto& objectStore) {
527 return objectStore->Id() == aObjectStoreId;
529 if (foundIt != mObjectStores.end()) {
530 auto& objectStore = *foundIt;
531 objectStore->NoteDeletion();
533 RefPtr<IDBObjectStore>* deletedObjectStore =
534 mDeletedObjectStores.AppendElement();
535 deletedObjectStore->swap(objectStore);
537 mObjectStores.RemoveElementAt(foundIt);
541 void IDBTransaction::RenameObjectStore(const int64_t aObjectStoreId,
542 const nsAString& aName) const {
543 AssertIsOnOwningThread();
544 MOZ_ASSERT(aObjectStoreId);
545 MOZ_ASSERT(Mode::VersionChange == mMode);
546 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
547 MOZ_ASSERT(IsActive());
549 MOZ_ALWAYS_TRUE(
550 mBackgroundActor.mVersionChangeBackgroundActor->SendRenameObjectStore(
551 aObjectStoreId, nsString(aName)));
554 void IDBTransaction::CreateIndex(
555 IDBObjectStore* const aObjectStore,
556 const indexedDB::IndexMetadata& aMetadata) const {
557 AssertIsOnOwningThread();
558 MOZ_ASSERT(aObjectStore);
559 MOZ_ASSERT(aMetadata.id());
560 MOZ_ASSERT(Mode::VersionChange == mMode);
561 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
562 MOZ_ASSERT(IsActive());
564 MOZ_ALWAYS_TRUE(
565 mBackgroundActor.mVersionChangeBackgroundActor->SendCreateIndex(
566 aObjectStore->Id(), aMetadata));
569 void IDBTransaction::DeleteIndex(IDBObjectStore* const aObjectStore,
570 const int64_t aIndexId) const {
571 AssertIsOnOwningThread();
572 MOZ_ASSERT(aObjectStore);
573 MOZ_ASSERT(aIndexId);
574 MOZ_ASSERT(Mode::VersionChange == mMode);
575 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
576 MOZ_ASSERT(IsActive());
578 MOZ_ALWAYS_TRUE(
579 mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteIndex(
580 aObjectStore->Id(), aIndexId));
583 void IDBTransaction::RenameIndex(IDBObjectStore* const aObjectStore,
584 const int64_t aIndexId,
585 const nsAString& aName) const {
586 AssertIsOnOwningThread();
587 MOZ_ASSERT(aObjectStore);
588 MOZ_ASSERT(aIndexId);
589 MOZ_ASSERT(Mode::VersionChange == mMode);
590 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
591 MOZ_ASSERT(IsActive());
593 MOZ_ALWAYS_TRUE(
594 mBackgroundActor.mVersionChangeBackgroundActor->SendRenameIndex(
595 aObjectStore->Id(), aIndexId, nsString(aName)));
598 void IDBTransaction::AbortInternal(const nsresult aAbortCode,
599 RefPtr<DOMException> aError) {
600 AssertIsOnOwningThread();
601 MOZ_ASSERT(NS_FAILED(aAbortCode));
602 MOZ_ASSERT(!IsCommittingOrFinished());
604 const bool isVersionChange = mMode == Mode::VersionChange;
605 const bool needToSendAbort = !mStarted;
607 mAbortCode = aAbortCode;
608 mReadyState = ReadyState::Finished;
609 mError = std::move(aError);
611 if (isVersionChange) {
612 // If a version change transaction is aborted, we must revert the world
613 // back to its previous state unless we're being invalidated after the
614 // transaction already completed.
615 if (!mDatabase->IsInvalidated()) {
616 mDatabase->RevertToPreviousState();
619 // We do the reversion only for the mObjectStores/mDeletedObjectStores but
620 // not for the mIndexes/mDeletedIndexes of each IDBObjectStore because it's
621 // time-consuming(O(m*n)) and mIndexes/mDeletedIndexes won't be used anymore
622 // in IDBObjectStore::(Create|Delete)Index() and IDBObjectStore::Index() in
623 // which all the executions are returned earlier by
624 // !transaction->IsActive().
626 const nsTArray<ObjectStoreSpec>& specArray =
627 mDatabase->Spec()->objectStores();
629 if (specArray.IsEmpty()) {
630 // This case is specially handled as a performance optimization, it is
631 // equivalent to the else block.
632 mObjectStores.Clear();
633 } else {
634 const auto validIds = TransformToHashtable<nsUint64HashKey>(
635 specArray, [](const auto& spec) {
636 const int64_t objectStoreId = spec.metadata().id();
637 MOZ_ASSERT(objectStoreId);
638 return static_cast<uint64_t>(objectStoreId);
641 mObjectStores.RemoveLastElements(
642 mObjectStores.end() -
643 std::remove_if(mObjectStores.begin(), mObjectStores.end(),
644 [&validIds](const auto& objectStore) {
645 return !validIds.Contains(
646 uint64_t(objectStore->Id()));
647 }));
649 std::copy_if(std::make_move_iterator(mDeletedObjectStores.begin()),
650 std::make_move_iterator(mDeletedObjectStores.end()),
651 MakeBackInserter(mObjectStores),
652 [&validIds](const auto& deletedObjectStore) {
653 const int64_t objectStoreId = deletedObjectStore->Id();
654 MOZ_ASSERT(objectStoreId);
655 return validIds.Contains(uint64_t(objectStoreId));
658 mDeletedObjectStores.Clear();
661 // Fire the abort event if there are no outstanding requests. Otherwise the
662 // abort event will be fired when all outstanding requests finish.
663 if (needToSendAbort) {
664 SendAbort(aAbortCode);
667 if (isVersionChange) {
668 mDatabase->Close();
672 void IDBTransaction::Abort(IDBRequest* const aRequest) {
673 AssertIsOnOwningThread();
674 MOZ_ASSERT(aRequest);
676 if (IsCommittingOrFinished()) {
677 // Already started (and maybe finished) the commit or abort so there is
678 // nothing to do here.
679 return;
682 ErrorResult rv;
683 RefPtr<DOMException> error = aRequest->GetError(rv);
685 // TODO: Do we deliberately ignore rv here? Isn't there a static analysis that
686 // prevents that?
688 AbortInternal(aRequest->GetErrorCode(), std::move(error));
691 void IDBTransaction::Abort(const nsresult aErrorCode) {
692 AssertIsOnOwningThread();
694 if (IsCommittingOrFinished()) {
695 // Already started (and maybe finished) the commit or abort so there is
696 // nothing to do here.
697 return;
700 AbortInternal(aErrorCode, DOMException::Create(aErrorCode));
703 // Specified by https://w3c.github.io/IndexedDB/#dom-idbtransaction-abort.
704 void IDBTransaction::Abort(ErrorResult& aRv) {
705 AssertIsOnOwningThread();
707 if (IsCommittingOrFinished()) {
708 aRv = NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
709 return;
712 mReadyState = ReadyState::Inactive;
714 AbortInternal(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR, nullptr);
716 mAbortedByScript.Flip();
719 // Specified by https://w3c.github.io/IndexedDB/#dom-idbtransaction-commit.
720 void IDBTransaction::Commit(ErrorResult& aRv) {
721 AssertIsOnOwningThread();
723 if (mReadyState != ReadyState::Active || !mNotedActiveTransaction) {
724 aRv = NS_ERROR_DOM_INVALID_STATE_ERR;
725 return;
728 MOZ_ASSERT(!mSentCommitOrAbort);
730 MOZ_ASSERT(mReadyState == ReadyState::Active);
731 mReadyState = ReadyState::Committing;
732 if (NS_WARN_IF(NS_FAILED(mAbortCode))) {
733 SendAbort(mAbortCode);
734 aRv = mAbortCode;
735 return;
738 #ifdef DEBUG
739 mWasExplicitlyCommitted.Flip();
740 #endif
742 SendCommit(false);
745 void IDBTransaction::FireCompleteOrAbortEvents(const nsresult aResult) {
746 AssertIsOnOwningThread();
747 MOZ_ASSERT(!mFiredCompleteOrAbort);
749 mReadyState = ReadyState::Finished;
751 #ifdef DEBUG
752 mFiredCompleteOrAbort.Flip();
753 #endif
755 // Make sure we drop the WorkerRef when this function completes.
756 const auto scopeExit = MakeScopeExit([&] { mWorkerRef = nullptr; });
758 RefPtr<Event> event;
759 if (NS_SUCCEEDED(aResult)) {
760 event = CreateGenericEvent(this, nsDependentString(kCompleteEventType),
761 eDoesNotBubble, eNotCancelable);
762 MOZ_ASSERT(event);
764 // If we hit this assertion, it probably means transaction object on the
765 // parent process doesn't propagate error properly.
766 MOZ_ASSERT(NS_SUCCEEDED(mAbortCode));
767 } else {
768 if (aResult == NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR) {
769 mDatabase->SetQuotaExceeded();
772 if (!mError && !mAbortedByScript) {
773 mError = DOMException::Create(aResult);
776 event = CreateGenericEvent(this, nsDependentString(kAbortEventType),
777 eDoesBubble, eNotCancelable);
778 MOZ_ASSERT(event);
780 if (NS_SUCCEEDED(mAbortCode)) {
781 mAbortCode = aResult;
785 if (NS_SUCCEEDED(mAbortCode)) {
786 IDB_LOG_MARK_CHILD_TRANSACTION("Firing 'complete' event",
787 "IDBTransaction 'complete' event",
788 mLoggingSerialNumber);
789 } else {
790 IDB_LOG_MARK_CHILD_TRANSACTION(
791 "Firing 'abort' event with error 0x%" PRIx32,
792 "IDBTransaction 'abort' event (0x%" PRIx32 ")", mLoggingSerialNumber,
793 static_cast<uint32_t>(mAbortCode));
796 IgnoredErrorResult rv;
797 DispatchEvent(*event, rv);
798 if (rv.Failed()) {
799 NS_WARNING("DispatchEvent failed!");
802 // Normally, we note inactive transaction here instead of
803 // IDBTransaction::ClearBackgroundActor() because here is the earliest place
804 // to know that it becomes non-blocking to allow the scheduler to start the
805 // preemption as soon as it can.
806 // Note: If the IDBTransaction object is held by the script,
807 // ClearBackgroundActor() will be done in ~IDBTransaction() until garbage
808 // collected after its window is closed which prevents us to preempt its
809 // window immediately after committed.
810 MaybeNoteInactiveTransaction();
813 int64_t IDBTransaction::NextObjectStoreId() {
814 AssertIsOnOwningThread();
815 MOZ_ASSERT(Mode::VersionChange == mMode);
817 return mNextObjectStoreId++;
820 int64_t IDBTransaction::NextIndexId() {
821 AssertIsOnOwningThread();
822 MOZ_ASSERT(Mode::VersionChange == mMode);
824 return mNextIndexId++;
827 int64_t IDBTransaction::NextRequestId() {
828 AssertIsOnOwningThread();
830 return mNextRequestId++;
833 void IDBTransaction::InvalidateCursorCaches() {
834 AssertIsOnOwningThread();
836 for (const auto& cursor : mCursors) {
837 cursor->InvalidateCachedResponses();
841 void IDBTransaction::RegisterCursor(IDBCursor& aCursor) {
842 AssertIsOnOwningThread();
844 mCursors.AppendElement(WrapNotNullUnchecked(&aCursor));
847 void IDBTransaction::UnregisterCursor(IDBCursor& aCursor) {
848 AssertIsOnOwningThread();
850 DebugOnly<bool> removed = mCursors.RemoveElement(&aCursor);
851 MOZ_ASSERT(removed);
854 nsIGlobalObject* IDBTransaction::GetParentObject() const {
855 AssertIsOnOwningThread();
857 return mDatabase->GetParentObject();
860 IDBTransactionMode IDBTransaction::GetMode(ErrorResult& aRv) const {
861 AssertIsOnOwningThread();
863 switch (mMode) {
864 case Mode::ReadOnly:
865 return IDBTransactionMode::Readonly;
867 case Mode::ReadWrite:
868 return IDBTransactionMode::Readwrite;
870 case Mode::ReadWriteFlush:
871 return IDBTransactionMode::Readwriteflush;
873 case Mode::Cleanup:
874 return IDBTransactionMode::Cleanup;
876 case Mode::VersionChange:
877 return IDBTransactionMode::Versionchange;
879 case Mode::Invalid:
880 default:
881 MOZ_CRASH("Bad mode!");
885 IDBTransactionDurability IDBTransaction::GetDurability(ErrorResult& aRv) const {
886 AssertIsOnOwningThread();
888 switch (mDurability) {
889 case Durability::Default:
890 return IDBTransactionDurability::Default;
892 case Durability::Strict:
893 return IDBTransactionDurability::Strict;
895 case Durability::Relaxed:
896 return IDBTransactionDurability::Relaxed;
898 default:
899 MOZ_CRASH("Bad mode!");
903 DOMException* IDBTransaction::GetError() const {
904 AssertIsOnOwningThread();
906 return mError;
909 RefPtr<DOMStringList> IDBTransaction::ObjectStoreNames() const {
910 AssertIsOnOwningThread();
912 if (mMode == Mode::VersionChange) {
913 return mDatabase->ObjectStoreNames();
916 auto list = MakeRefPtr<DOMStringList>();
917 list->StringArray() = mObjectStoreNames.Clone();
918 return list;
921 RefPtr<IDBObjectStore> IDBTransaction::ObjectStore(const nsAString& aName,
922 ErrorResult& aRv) {
923 AssertIsOnOwningThread();
925 if (IsCommittingOrFinished()) {
926 aRv.ThrowInvalidStateError("Transaction is already committing or done.");
927 return nullptr;
930 auto* const spec = [this, &aName]() -> ObjectStoreSpec* {
931 if (IDBTransaction::Mode::VersionChange == mMode ||
932 mObjectStoreNames.Contains(aName)) {
933 return mDatabase->LookupModifiableObjectStoreSpec(
934 [&aName](const auto& objectStore) {
935 return objectStore.metadata().name() == aName;
938 return nullptr;
939 }();
941 if (!spec) {
942 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
943 return nullptr;
946 RefPtr<IDBObjectStore> objectStore;
948 const auto foundIt = std::find_if(
949 mObjectStores.cbegin(), mObjectStores.cend(),
950 [desiredId = spec->metadata().id()](const auto& existingObjectStore) {
951 return existingObjectStore->Id() == desiredId;
953 if (foundIt != mObjectStores.cend()) {
954 objectStore = *foundIt;
955 } else {
956 objectStore = IDBObjectStore::Create(
957 SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, *spec);
958 MOZ_ASSERT(objectStore);
960 mObjectStores.AppendElement(objectStore);
963 return objectStore;
966 NS_IMPL_ADDREF_INHERITED(IDBTransaction, DOMEventTargetHelper)
967 NS_IMPL_RELEASE_INHERITED(IDBTransaction, DOMEventTargetHelper)
969 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBTransaction)
970 NS_INTERFACE_MAP_ENTRY(nsIRunnable)
971 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
973 NS_IMPL_CYCLE_COLLECTION_CLASS(IDBTransaction)
975 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBTransaction,
976 DOMEventTargetHelper)
977 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDatabase)
978 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
979 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObjectStores)
980 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeletedObjectStores)
981 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
983 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBTransaction,
984 DOMEventTargetHelper)
985 // Don't unlink mDatabase!
986 NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
987 NS_IMPL_CYCLE_COLLECTION_UNLINK(mObjectStores)
988 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDeletedObjectStores)
989 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
991 JSObject* IDBTransaction::WrapObject(JSContext* const aCx,
992 JS::Handle<JSObject*> aGivenProto) {
993 AssertIsOnOwningThread();
995 return IDBTransaction_Binding::Wrap(aCx, this, std::move(aGivenProto));
998 void IDBTransaction::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
999 AssertIsOnOwningThread();
1001 aVisitor.mCanHandle = true;
1002 aVisitor.SetParentTarget(mDatabase, false);
1005 NS_IMETHODIMP
1006 IDBTransaction::Run() {
1007 AssertIsOnOwningThread();
1009 // TODO: Instead of checking for Finished and Committing states here, we could
1010 // remove the transaction from the pending IDB transactions list on
1011 // abort/commit.
1013 if (ReadyState::Finished == mReadyState) {
1014 // There are three cases where mReadyState is set to Finished: In
1015 // FileCompleteOrAbortEvents, AbortInternal and in CommitIfNotStarted. We
1016 // shouldn't get here after CommitIfNotStarted again.
1017 MOZ_ASSERT(mFiredCompleteOrAbort || IsAborted());
1018 return NS_OK;
1021 if (ReadyState::Committing == mReadyState) {
1022 MOZ_ASSERT(mSentCommitOrAbort);
1023 return NS_OK;
1025 // We're back at the event loop, no longer newborn, so
1026 // return to Inactive state:
1027 // https://w3c.github.io/IndexedDB/#cleanup-indexed-database-transactions.
1028 MOZ_ASSERT(ReadyState::Active == mReadyState);
1029 mReadyState = ReadyState::Inactive;
1031 CommitIfNotStarted();
1033 return NS_OK;
1036 void IDBTransaction::CommitIfNotStarted() {
1037 AssertIsOnOwningThread();
1039 MOZ_ASSERT(ReadyState::Inactive == mReadyState);
1041 // Maybe commit if there were no requests generated.
1042 if (!mStarted) {
1043 MOZ_ASSERT(!mPendingRequestCount);
1044 mReadyState = ReadyState::Finished;
1046 SendCommit(true);
1050 } // namespace mozilla::dom