Bug 1729395 - Handle message sender going away during message processing r=robwu
[gecko.git] / dom / indexedDB / IDBTransaction.cpp
blobbbff8dd6899a6f672dee3f91cd92ae63178b152f
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, nsString aFilename,
93 const uint32_t aLineNo, const uint32_t aColumn,
94 CreatedFromFactoryFunction /*aDummy*/)
95 : DOMEventTargetHelper(aDatabase),
96 mDatabase(aDatabase),
97 mObjectStoreNames(aObjectStoreNames.Clone()),
98 mLoggingSerialNumber(GetIndexedDBThreadLocal()->NextTransactionSN(aMode)),
99 mNextObjectStoreId(0),
100 mNextIndexId(0),
101 mAbortCode(NS_OK),
102 mPendingRequestCount(0),
103 mFilename(std::move(aFilename)),
104 mLineNo(aLineNo),
105 mColumn(aColumn),
106 mMode(aMode),
107 mRegistered(false),
108 mNotedActiveTransaction(false) {
109 MOZ_ASSERT(aDatabase);
110 aDatabase->AssertIsOnOwningThread();
112 // This also nulls mBackgroundActor.mVersionChangeBackgroundActor, so this is
113 // valid also for mMode == Mode::VersionChange.
114 mBackgroundActor.mNormalBackgroundActor = nullptr;
116 #ifdef DEBUG
117 if (!aObjectStoreNames.IsEmpty()) {
118 // Make sure the array is properly sorted.
119 MOZ_ASSERT(
120 std::is_sorted(aObjectStoreNames.cbegin(), aObjectStoreNames.cend()));
122 // Make sure there are no duplicates in our objectStore names.
123 MOZ_ASSERT(aObjectStoreNames.cend() ==
124 std::adjacent_find(aObjectStoreNames.cbegin(),
125 aObjectStoreNames.cend()));
127 #endif
129 mozilla::HoldJSObjects(this);
132 IDBTransaction::~IDBTransaction() {
133 AssertIsOnOwningThread();
134 MOZ_ASSERT(!mPendingRequestCount);
135 MOZ_ASSERT(mReadyState == ReadyState::Finished);
136 MOZ_ASSERT(!mNotedActiveTransaction);
137 MOZ_ASSERT(mSentCommitOrAbort);
138 MOZ_ASSERT_IF(HasTransactionChild(), mFiredCompleteOrAbort);
140 if (mRegistered) {
141 mDatabase->UnregisterTransaction(*this);
142 #ifdef DEBUG
143 mRegistered = false;
144 #endif
147 if (HasTransactionChild()) {
148 if (mMode == Mode::VersionChange) {
149 mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteMeInternal(
150 /* aFailedConstructor */ false);
151 } else {
152 mBackgroundActor.mNormalBackgroundActor->SendDeleteMeInternal();
155 MOZ_ASSERT(!HasTransactionChild(),
156 "SendDeleteMeInternal should have cleared!");
158 mozilla::DropJSObjects(this);
161 // static
162 SafeRefPtr<IDBTransaction> IDBTransaction::CreateVersionChange(
163 IDBDatabase* const aDatabase,
164 BackgroundVersionChangeTransactionChild* const aActor,
165 const NotNull<IDBOpenDBRequest*> aOpenRequest,
166 const int64_t aNextObjectStoreId, const int64_t aNextIndexId) {
167 MOZ_ASSERT(aDatabase);
168 aDatabase->AssertIsOnOwningThread();
169 MOZ_ASSERT(aActor);
170 MOZ_ASSERT(aNextObjectStoreId > 0);
171 MOZ_ASSERT(aNextIndexId > 0);
173 const nsTArray<nsString> emptyObjectStoreNames;
175 nsString filename;
176 uint32_t lineNo, column;
177 aOpenRequest->GetCallerLocation(filename, &lineNo, &column);
178 auto transaction = MakeSafeRefPtr<IDBTransaction>(
179 aDatabase, emptyObjectStoreNames, Mode::VersionChange,
180 std::move(filename), lineNo, column, CreatedFromFactoryFunction{});
182 transaction->NoteActiveTransaction();
184 transaction->mBackgroundActor.mVersionChangeBackgroundActor = aActor;
185 transaction->mNextObjectStoreId = aNextObjectStoreId;
186 transaction->mNextIndexId = aNextIndexId;
188 aDatabase->RegisterTransaction(*transaction);
189 transaction->mRegistered = true;
191 return transaction;
194 // static
195 SafeRefPtr<IDBTransaction> IDBTransaction::Create(
196 JSContext* const aCx, IDBDatabase* const aDatabase,
197 const nsTArray<nsString>& aObjectStoreNames, const Mode aMode) {
198 MOZ_ASSERT(aDatabase);
199 aDatabase->AssertIsOnOwningThread();
200 MOZ_ASSERT(!aObjectStoreNames.IsEmpty());
201 MOZ_ASSERT(aMode == Mode::ReadOnly || aMode == Mode::ReadWrite ||
202 aMode == Mode::ReadWriteFlush || aMode == Mode::Cleanup);
204 nsString filename;
205 uint32_t lineNo, column;
206 IDBRequest::CaptureCaller(aCx, filename, &lineNo, &column);
207 auto transaction = MakeSafeRefPtr<IDBTransaction>(
208 aDatabase, aObjectStoreNames, aMode, std::move(filename), lineNo, column,
209 CreatedFromFactoryFunction{});
211 if (!NS_IsMainThread()) {
212 WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate();
213 MOZ_ASSERT(workerPrivate);
215 workerPrivate->AssertIsOnWorkerThread();
217 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
218 workerPrivate, "IDBTransaction",
219 [transaction = AsRefPtr(transaction.clonePtr())]() {
220 transaction->AssertIsOnOwningThread();
221 if (!transaction->IsCommittingOrFinished()) {
222 IDB_REPORT_INTERNAL_ERR();
223 transaction->AbortInternal(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
224 nullptr);
227 if (NS_WARN_IF(!workerRef)) {
228 // Silence the destructor assertion if we never made this object live.
229 #ifdef DEBUG
230 transaction->mSentCommitOrAbort.Flip();
231 #endif
232 return nullptr;
235 transaction->mWorkerRef = std::move(workerRef);
238 nsCOMPtr<nsIRunnable> runnable =
239 do_QueryObject(transaction.unsafeGetRawPtr());
240 nsContentUtils::AddPendingIDBTransaction(runnable.forget());
242 aDatabase->RegisterTransaction(*transaction);
243 transaction->mRegistered = true;
245 return transaction;
248 // static
249 Maybe<IDBTransaction&> IDBTransaction::MaybeCurrent() {
250 using namespace mozilla::ipc;
252 MOZ_ASSERT(BackgroundChild::GetForCurrentThread());
254 return GetIndexedDBThreadLocal()->MaybeCurrentTransactionRef();
257 #ifdef DEBUG
259 void IDBTransaction::AssertIsOnOwningThread() const {
260 MOZ_ASSERT(mDatabase);
261 mDatabase->AssertIsOnOwningThread();
264 #endif // DEBUG
266 void IDBTransaction::SetBackgroundActor(
267 indexedDB::BackgroundTransactionChild* const aBackgroundActor) {
268 AssertIsOnOwningThread();
269 MOZ_ASSERT(aBackgroundActor);
270 MOZ_ASSERT(!mBackgroundActor.mNormalBackgroundActor);
271 MOZ_ASSERT(mMode != Mode::VersionChange);
273 NoteActiveTransaction();
275 mBackgroundActor.mNormalBackgroundActor = aBackgroundActor;
278 BackgroundRequestChild* IDBTransaction::StartRequest(
279 MovingNotNull<RefPtr<mozilla::dom::IDBRequest> > aRequest,
280 const RequestParams& aParams) {
281 AssertIsOnOwningThread();
282 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
284 BackgroundRequestChild* const actor =
285 new BackgroundRequestChild(std::move(aRequest));
287 DoWithTransactionChild([actor, &aParams](auto& transactionChild) {
288 transactionChild.SendPBackgroundIDBRequestConstructor(actor, aParams);
291 MOZ_ASSERT(actor->GetActorEventTarget(),
292 "The event target shall be inherited from its manager actor.");
294 // Balanced in BackgroundRequestChild::Recv__delete__().
295 OnNewRequest();
297 return actor;
300 void IDBTransaction::OpenCursor(PBackgroundIDBCursorChild& aBackgroundActor,
301 const OpenCursorParams& aParams) {
302 AssertIsOnOwningThread();
303 MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
305 DoWithTransactionChild([&aBackgroundActor, &aParams](auto& actor) {
306 actor.SendPBackgroundIDBCursorConstructor(&aBackgroundActor, aParams);
309 MOZ_ASSERT(aBackgroundActor.GetActorEventTarget(),
310 "The event target shall be inherited from its manager actor.");
312 // Balanced in BackgroundCursorChild::RecvResponse().
313 OnNewRequest();
316 void IDBTransaction::RefreshSpec(const bool aMayDelete) {
317 AssertIsOnOwningThread();
319 for (auto& objectStore : mObjectStores) {
320 objectStore->RefreshSpec(aMayDelete);
323 for (auto& objectStore : mDeletedObjectStores) {
324 objectStore->RefreshSpec(false);
328 void IDBTransaction::OnNewRequest() {
329 AssertIsOnOwningThread();
331 if (!mPendingRequestCount) {
332 MOZ_ASSERT(ReadyState::Active == mReadyState);
333 mStarted.Flip();
336 ++mPendingRequestCount;
339 void IDBTransaction::OnRequestFinished(
340 const bool aRequestCompletedSuccessfully) {
341 AssertIsOnOwningThread();
342 MOZ_ASSERT(mReadyState != ReadyState::Active);
343 MOZ_ASSERT_IF(mReadyState == ReadyState::Finished, !NS_SUCCEEDED(mAbortCode));
344 MOZ_ASSERT(mPendingRequestCount);
346 --mPendingRequestCount;
348 if (!mPendingRequestCount) {
349 if (mSentCommitOrAbort) {
350 return;
353 if (mReadyState == ReadyState::Inactive) {
354 mReadyState = ReadyState::Committing;
357 if (aRequestCompletedSuccessfully) {
358 if (NS_SUCCEEDED(mAbortCode)) {
359 SendCommit(true);
360 } else {
361 SendAbort(mAbortCode);
363 } else {
364 // Don't try to send any more messages to the parent if the request actor
365 // was killed.
366 mSentCommitOrAbort.Flip();
367 IDB_LOG_MARK_CHILD_TRANSACTION(
368 "Request actor was killed, transaction will be aborted",
369 "IDBTransaction abort", LoggingSerialNumber());
374 void IDBTransaction::SendCommit(const bool aAutoCommit) {
375 AssertIsOnOwningThread();
376 MOZ_ASSERT(NS_SUCCEEDED(mAbortCode));
377 MOZ_ASSERT(IsCommittingOrFinished());
379 // Don't do this in the macro because we always need to increment the serial
380 // number to keep in sync with the parent.
381 const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
383 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
384 "Committing transaction (%s)", "IDBTransaction commit (%s)",
385 LoggingSerialNumber(), requestSerialNumber,
386 aAutoCommit ? "automatically" : "explicitly");
388 const auto lastRequestSerialNumber =
389 [this, aAutoCommit,
390 requestSerialNumber]() -> Maybe<decltype(requestSerialNumber)> {
391 if (aAutoCommit) {
392 return Nothing();
395 // In case of an explicit commit, we need to note the serial number of the
396 // last request to check if a request submitted before the commit request
397 // failed. If we are currently in an event handler for a request on this
398 // transaction, ignore this request. This is used to synchronize the
399 // transaction's committing state with the parent side, to abort the
400 // transaction in case of a request resulting in an error (see
401 // https://w3c.github.io/IndexedDB/#async-execute-request, step 5.3.). With
402 // automatic commit, this is not necessary, as the transaction's state will
403 // only be set to committing after the last request completed.
404 const auto maybeCurrentTransaction =
405 BackgroundChildImpl::GetThreadLocalForCurrentThread()
406 ->mIndexedDBThreadLocal->MaybeCurrentTransactionRef();
407 const bool dispatchingEventForThisTransaction =
408 maybeCurrentTransaction && &maybeCurrentTransaction.ref() == this;
410 return Some(requestSerialNumber
411 ? (requestSerialNumber -
412 (dispatchingEventForThisTransaction ? 0 : 1))
413 : 0);
414 }();
416 DoWithTransactionChild([lastRequestSerialNumber](auto& actor) {
417 actor.SendCommit(lastRequestSerialNumber);
420 mSentCommitOrAbort.Flip();
423 void IDBTransaction::SendAbort(const nsresult aResultCode) {
424 AssertIsOnOwningThread();
425 MOZ_ASSERT(NS_FAILED(aResultCode));
426 MOZ_ASSERT(IsCommittingOrFinished());
428 // Don't do this in the macro because we always need to increment the serial
429 // number to keep in sync with the parent.
430 const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
432 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
433 "Aborting transaction with result 0x%" PRIx32,
434 "IDBTransaction abort (0x%" PRIx32 ")", LoggingSerialNumber(),
435 requestSerialNumber, static_cast<uint32_t>(aResultCode));
437 DoWithTransactionChild(
438 [aResultCode](auto& actor) { actor.SendAbort(aResultCode); });
440 mSentCommitOrAbort.Flip();
443 void IDBTransaction::NoteActiveTransaction() {
444 AssertIsOnOwningThread();
445 MOZ_ASSERT(!mNotedActiveTransaction);
447 mDatabase->NoteActiveTransaction();
448 mNotedActiveTransaction = true;
451 void IDBTransaction::MaybeNoteInactiveTransaction() {
452 AssertIsOnOwningThread();
454 if (mNotedActiveTransaction) {
455 mDatabase->NoteInactiveTransaction();
456 mNotedActiveTransaction = false;
460 IDBTransaction::AutoRestoreState<IDBTransaction::ReadyState::Inactive,
461 IDBTransaction::ReadyState::Active>
462 IDBTransaction::TemporarilyTransitionToActive() {
463 return AutoRestoreState<ReadyState::Inactive, ReadyState::Active>{*this};
466 IDBTransaction::AutoRestoreState<IDBTransaction::ReadyState::Active,
467 IDBTransaction::ReadyState::Inactive>
468 IDBTransaction::TemporarilyTransitionToInactive() {
469 return AutoRestoreState<ReadyState::Active, ReadyState::Inactive>{*this};
472 void IDBTransaction::GetCallerLocation(nsAString& aFilename,
473 uint32_t* const aLineNo,
474 uint32_t* const aColumn) const {
475 AssertIsOnOwningThread();
476 MOZ_ASSERT(aLineNo);
477 MOZ_ASSERT(aColumn);
479 aFilename = mFilename;
480 *aLineNo = mLineNo;
481 *aColumn = mColumn;
484 RefPtr<IDBObjectStore> IDBTransaction::CreateObjectStore(
485 ObjectStoreSpec& aSpec) {
486 AssertIsOnOwningThread();
487 MOZ_ASSERT(aSpec.metadata().id());
488 MOZ_ASSERT(Mode::VersionChange == mMode);
489 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
490 MOZ_ASSERT(IsActive());
492 #ifdef DEBUG
494 // TODO: Bind name outside of lambda capture as a workaround for GCC 7 bug
495 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66735.
496 const auto& name = aSpec.metadata().name();
497 // TODO: Use #ifdef and local variable as a workaround for Bug 1583449.
498 const bool objectStoreNameDoesNotYetExist =
499 std::all_of(mObjectStores.cbegin(), mObjectStores.cend(),
500 [&name](const auto& objectStore) {
501 return objectStore->Name() != name;
503 MOZ_ASSERT(objectStoreNameDoesNotYetExist);
505 #endif
507 MOZ_ALWAYS_TRUE(
508 mBackgroundActor.mVersionChangeBackgroundActor->SendCreateObjectStore(
509 aSpec.metadata()));
511 RefPtr<IDBObjectStore> objectStore = IDBObjectStore::Create(
512 SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, aSpec);
513 MOZ_ASSERT(objectStore);
515 mObjectStores.AppendElement(objectStore);
517 return objectStore;
520 void IDBTransaction::DeleteObjectStore(const int64_t aObjectStoreId) {
521 AssertIsOnOwningThread();
522 MOZ_ASSERT(aObjectStoreId);
523 MOZ_ASSERT(Mode::VersionChange == mMode);
524 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
525 MOZ_ASSERT(IsActive());
527 MOZ_ALWAYS_TRUE(
528 mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteObjectStore(
529 aObjectStoreId));
531 const auto foundIt =
532 std::find_if(mObjectStores.begin(), mObjectStores.end(),
533 [aObjectStoreId](const auto& objectStore) {
534 return objectStore->Id() == aObjectStoreId;
536 if (foundIt != mObjectStores.end()) {
537 auto& objectStore = *foundIt;
538 objectStore->NoteDeletion();
540 RefPtr<IDBObjectStore>* deletedObjectStore =
541 mDeletedObjectStores.AppendElement();
542 deletedObjectStore->swap(objectStore);
544 mObjectStores.RemoveElementAt(foundIt);
548 void IDBTransaction::RenameObjectStore(const int64_t aObjectStoreId,
549 const nsAString& aName) {
550 AssertIsOnOwningThread();
551 MOZ_ASSERT(aObjectStoreId);
552 MOZ_ASSERT(Mode::VersionChange == mMode);
553 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
554 MOZ_ASSERT(IsActive());
556 MOZ_ALWAYS_TRUE(
557 mBackgroundActor.mVersionChangeBackgroundActor->SendRenameObjectStore(
558 aObjectStoreId, nsString(aName)));
561 void IDBTransaction::CreateIndex(IDBObjectStore* const aObjectStore,
562 const indexedDB::IndexMetadata& aMetadata) {
563 AssertIsOnOwningThread();
564 MOZ_ASSERT(aObjectStore);
565 MOZ_ASSERT(aMetadata.id());
566 MOZ_ASSERT(Mode::VersionChange == mMode);
567 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
568 MOZ_ASSERT(IsActive());
570 MOZ_ALWAYS_TRUE(
571 mBackgroundActor.mVersionChangeBackgroundActor->SendCreateIndex(
572 aObjectStore->Id(), aMetadata));
575 void IDBTransaction::DeleteIndex(IDBObjectStore* const aObjectStore,
576 const int64_t aIndexId) {
577 AssertIsOnOwningThread();
578 MOZ_ASSERT(aObjectStore);
579 MOZ_ASSERT(aIndexId);
580 MOZ_ASSERT(Mode::VersionChange == mMode);
581 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
582 MOZ_ASSERT(IsActive());
584 MOZ_ALWAYS_TRUE(
585 mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteIndex(
586 aObjectStore->Id(), aIndexId));
589 void IDBTransaction::RenameIndex(IDBObjectStore* const aObjectStore,
590 const int64_t aIndexId,
591 const nsAString& aName) {
592 AssertIsOnOwningThread();
593 MOZ_ASSERT(aObjectStore);
594 MOZ_ASSERT(aIndexId);
595 MOZ_ASSERT(Mode::VersionChange == mMode);
596 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
597 MOZ_ASSERT(IsActive());
599 MOZ_ALWAYS_TRUE(
600 mBackgroundActor.mVersionChangeBackgroundActor->SendRenameIndex(
601 aObjectStore->Id(), aIndexId, nsString(aName)));
604 void IDBTransaction::AbortInternal(const nsresult aAbortCode,
605 RefPtr<DOMException> aError) {
606 AssertIsOnOwningThread();
607 MOZ_ASSERT(NS_FAILED(aAbortCode));
608 MOZ_ASSERT(!IsCommittingOrFinished());
610 const bool isVersionChange = mMode == Mode::VersionChange;
611 const bool needToSendAbort = mReadyState == ReadyState::Inactive && !mStarted;
613 mAbortCode = aAbortCode;
614 mReadyState = ReadyState::Finished;
615 mError = std::move(aError);
617 if (isVersionChange) {
618 // If a version change transaction is aborted, we must revert the world
619 // back to its previous state unless we're being invalidated after the
620 // transaction already completed.
621 if (!mDatabase->IsInvalidated()) {
622 mDatabase->RevertToPreviousState();
625 // We do the reversion only for the mObjectStores/mDeletedObjectStores but
626 // not for the mIndexes/mDeletedIndexes of each IDBObjectStore because it's
627 // time-consuming(O(m*n)) and mIndexes/mDeletedIndexes won't be used anymore
628 // in IDBObjectStore::(Create|Delete)Index() and IDBObjectStore::Index() in
629 // which all the executions are returned earlier by
630 // !transaction->IsActive().
632 const nsTArray<ObjectStoreSpec>& specArray =
633 mDatabase->Spec()->objectStores();
635 if (specArray.IsEmpty()) {
636 // This case is specially handled as a performance optimization, it is
637 // equivalent to the else block.
638 mObjectStores.Clear();
639 } else {
640 const auto validIds = TransformToHashtable<nsUint64HashKey>(
641 specArray, [](const auto& spec) {
642 const int64_t objectStoreId = spec.metadata().id();
643 MOZ_ASSERT(objectStoreId);
644 return static_cast<uint64_t>(objectStoreId);
647 mObjectStores.RemoveLastElements(
648 mObjectStores.end() -
649 std::remove_if(mObjectStores.begin(), mObjectStores.end(),
650 [&validIds](const auto& objectStore) {
651 return !validIds.Contains(
652 uint64_t(objectStore->Id()));
653 }));
655 std::copy_if(std::make_move_iterator(mDeletedObjectStores.begin()),
656 std::make_move_iterator(mDeletedObjectStores.end()),
657 MakeBackInserter(mObjectStores),
658 [&validIds](const auto& deletedObjectStore) {
659 const int64_t objectStoreId = deletedObjectStore->Id();
660 MOZ_ASSERT(objectStoreId);
661 return validIds.Contains(uint64_t(objectStoreId));
664 mDeletedObjectStores.Clear();
667 // Fire the abort event if there are no outstanding requests. Otherwise the
668 // abort event will be fired when all outstanding requests finish.
669 if (needToSendAbort) {
670 SendAbort(aAbortCode);
673 if (isVersionChange) {
674 mDatabase->Close();
678 void IDBTransaction::Abort(IDBRequest* const aRequest) {
679 AssertIsOnOwningThread();
680 MOZ_ASSERT(aRequest);
682 if (IsCommittingOrFinished()) {
683 // Already started (and maybe finished) the commit or abort so there is
684 // nothing to do here.
685 return;
688 ErrorResult rv;
689 RefPtr<DOMException> error = aRequest->GetError(rv);
691 // TODO: Do we deliberately ignore rv here? Isn't there a static analysis that
692 // prevents that?
694 AbortInternal(aRequest->GetErrorCode(), std::move(error));
697 void IDBTransaction::Abort(const nsresult aErrorCode) {
698 AssertIsOnOwningThread();
700 if (IsCommittingOrFinished()) {
701 // Already started (and maybe finished) the commit or abort so there is
702 // nothing to do here.
703 return;
706 AbortInternal(aErrorCode, DOMException::Create(aErrorCode));
709 // Specified by https://w3c.github.io/IndexedDB/#dom-idbtransaction-abort.
710 void IDBTransaction::Abort(ErrorResult& aRv) {
711 AssertIsOnOwningThread();
713 if (IsCommittingOrFinished()) {
714 aRv = NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
715 return;
718 mReadyState = ReadyState::Inactive;
720 AbortInternal(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR, nullptr);
722 mAbortedByScript.Flip();
725 // Specified by https://w3c.github.io/IndexedDB/#dom-idbtransaction-commit.
726 void IDBTransaction::Commit(ErrorResult& aRv) {
727 AssertIsOnOwningThread();
729 if (mReadyState != ReadyState::Active || !mNotedActiveTransaction) {
730 aRv = NS_ERROR_DOM_INVALID_STATE_ERR;
731 return;
734 MOZ_ASSERT(!mSentCommitOrAbort);
736 MOZ_ASSERT(mReadyState == ReadyState::Active);
737 mReadyState = ReadyState::Committing;
738 if (NS_WARN_IF(NS_FAILED(mAbortCode))) {
739 SendAbort(mAbortCode);
740 aRv = mAbortCode;
741 return;
744 #ifdef DEBUG
745 mWasExplicitlyCommitted.Flip();
746 #endif
748 SendCommit(false);
751 void IDBTransaction::FireCompleteOrAbortEvents(const nsresult aResult) {
752 AssertIsOnOwningThread();
753 MOZ_ASSERT(!mFiredCompleteOrAbort);
755 mReadyState = ReadyState::Finished;
757 #ifdef DEBUG
758 mFiredCompleteOrAbort.Flip();
759 #endif
761 // Make sure we drop the WorkerRef when this function completes.
762 const auto scopeExit = MakeScopeExit([&] { mWorkerRef = nullptr; });
764 RefPtr<Event> event;
765 if (NS_SUCCEEDED(aResult)) {
766 event = CreateGenericEvent(this, nsDependentString(kCompleteEventType),
767 eDoesNotBubble, eNotCancelable);
768 MOZ_ASSERT(event);
770 // If we hit this assertion, it probably means transaction object on the
771 // parent process doesn't propagate error properly.
772 MOZ_ASSERT(NS_SUCCEEDED(mAbortCode));
773 } else {
774 if (aResult == NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR) {
775 mDatabase->SetQuotaExceeded();
778 if (!mError && !mAbortedByScript) {
779 mError = DOMException::Create(aResult);
782 event = CreateGenericEvent(this, nsDependentString(kAbortEventType),
783 eDoesBubble, eNotCancelable);
784 MOZ_ASSERT(event);
786 if (NS_SUCCEEDED(mAbortCode)) {
787 mAbortCode = aResult;
791 if (NS_SUCCEEDED(mAbortCode)) {
792 IDB_LOG_MARK_CHILD_TRANSACTION("Firing 'complete' event",
793 "IDBTransaction 'complete' event",
794 mLoggingSerialNumber);
795 } else {
796 IDB_LOG_MARK_CHILD_TRANSACTION(
797 "Firing 'abort' event with error 0x%" PRIx32,
798 "IDBTransaction 'abort' event (0x%" PRIx32 ")", mLoggingSerialNumber,
799 static_cast<uint32_t>(mAbortCode));
802 IgnoredErrorResult rv;
803 DispatchEvent(*event, rv);
804 if (rv.Failed()) {
805 NS_WARNING("DispatchEvent failed!");
808 // Normally, we note inactive transaction here instead of
809 // IDBTransaction::ClearBackgroundActor() because here is the earliest place
810 // to know that it becomes non-blocking to allow the scheduler to start the
811 // preemption as soon as it can.
812 // Note: If the IDBTransaction object is held by the script,
813 // ClearBackgroundActor() will be done in ~IDBTransaction() until garbage
814 // collected after its window is closed which prevents us to preempt its
815 // window immediately after committed.
816 MaybeNoteInactiveTransaction();
819 int64_t IDBTransaction::NextObjectStoreId() {
820 AssertIsOnOwningThread();
821 MOZ_ASSERT(Mode::VersionChange == mMode);
823 return mNextObjectStoreId++;
826 int64_t IDBTransaction::NextIndexId() {
827 AssertIsOnOwningThread();
828 MOZ_ASSERT(Mode::VersionChange == mMode);
830 return mNextIndexId++;
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 DOMException* IDBTransaction::GetError() const {
886 AssertIsOnOwningThread();
888 return mError;
891 RefPtr<DOMStringList> IDBTransaction::ObjectStoreNames() const {
892 AssertIsOnOwningThread();
894 if (mMode == Mode::VersionChange) {
895 return mDatabase->ObjectStoreNames();
898 auto list = MakeRefPtr<DOMStringList>();
899 list->StringArray() = mObjectStoreNames.Clone();
900 return list;
903 RefPtr<IDBObjectStore> IDBTransaction::ObjectStore(const nsAString& aName,
904 ErrorResult& aRv) {
905 AssertIsOnOwningThread();
907 if (IsCommittingOrFinished()) {
908 aRv.ThrowInvalidStateError("Transaction is already committing or done.");
909 return nullptr;
912 auto* const spec = [this, &aName]() -> ObjectStoreSpec* {
913 if (IDBTransaction::Mode::VersionChange == mMode ||
914 mObjectStoreNames.Contains(aName)) {
915 return mDatabase->LookupModifiableObjectStoreSpec(
916 [&aName](const auto& objectStore) {
917 return objectStore.metadata().name() == aName;
920 return nullptr;
921 }();
923 if (!spec) {
924 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
925 return nullptr;
928 RefPtr<IDBObjectStore> objectStore;
930 const auto foundIt = std::find_if(
931 mObjectStores.cbegin(), mObjectStores.cend(),
932 [desiredId = spec->metadata().id()](const auto& existingObjectStore) {
933 return existingObjectStore->Id() == desiredId;
935 if (foundIt != mObjectStores.cend()) {
936 objectStore = *foundIt;
937 } else {
938 objectStore = IDBObjectStore::Create(
939 SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, *spec);
940 MOZ_ASSERT(objectStore);
942 mObjectStores.AppendElement(objectStore);
945 return objectStore;
948 NS_IMPL_ADDREF_INHERITED(IDBTransaction, DOMEventTargetHelper)
949 NS_IMPL_RELEASE_INHERITED(IDBTransaction, DOMEventTargetHelper)
951 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBTransaction)
952 NS_INTERFACE_MAP_ENTRY(nsIRunnable)
953 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
955 NS_IMPL_CYCLE_COLLECTION_CLASS(IDBTransaction)
957 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBTransaction,
958 DOMEventTargetHelper)
959 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDatabase)
960 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
961 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObjectStores)
962 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeletedObjectStores)
963 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
965 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBTransaction,
966 DOMEventTargetHelper)
967 // Don't unlink mDatabase!
968 NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
969 NS_IMPL_CYCLE_COLLECTION_UNLINK(mObjectStores)
970 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDeletedObjectStores)
971 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
973 JSObject* IDBTransaction::WrapObject(JSContext* const aCx,
974 JS::Handle<JSObject*> aGivenProto) {
975 AssertIsOnOwningThread();
977 return IDBTransaction_Binding::Wrap(aCx, this, std::move(aGivenProto));
980 void IDBTransaction::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
981 AssertIsOnOwningThread();
983 aVisitor.mCanHandle = true;
984 aVisitor.SetParentTarget(mDatabase, false);
987 NS_IMETHODIMP
988 IDBTransaction::Run() {
989 AssertIsOnOwningThread();
991 // TODO: Instead of checking for Finished and Committing states here, we could
992 // remove the transaction from the pending IDB transactions list on
993 // abort/commit.
995 if (ReadyState::Finished == mReadyState) {
996 // There are three cases where mReadyState is set to Finished: In
997 // FileCompleteOrAbortEvents, AbortInternal and in CommitIfNotStarted. We
998 // shouldn't get here after CommitIfNotStarted again.
999 MOZ_ASSERT(mFiredCompleteOrAbort || IsAborted());
1000 return NS_OK;
1003 if (ReadyState::Committing == mReadyState) {
1004 MOZ_ASSERT(mSentCommitOrAbort);
1005 return NS_OK;
1007 // We're back at the event loop, no longer newborn, so
1008 // return to Inactive state:
1009 // https://w3c.github.io/IndexedDB/#cleanup-indexed-database-transactions.
1010 MOZ_ASSERT(ReadyState::Active == mReadyState);
1011 mReadyState = ReadyState::Inactive;
1013 CommitIfNotStarted();
1015 return NS_OK;
1018 void IDBTransaction::CommitIfNotStarted() {
1019 AssertIsOnOwningThread();
1021 MOZ_ASSERT(ReadyState::Inactive == mReadyState);
1023 // Maybe commit if there were no requests generated.
1024 if (!mStarted) {
1025 MOZ_ASSERT(!mPendingRequestCount);
1026 mReadyState = ReadyState::Finished;
1028 SendCommit(true);
1032 } // namespace mozilla::dom