Bug 1936278 - Prevent search mode chiclet from being dismissed when clicking in page...
[gecko.git] / dom / indexedDB / IDBTransaction.cpp
blob054f4faf49d4a2e614a4f4a4d90f826b0b8e8f17
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 JSCallingLocation&& aCallerLocation,
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 mNextRequestId(0),
102 mAbortCode(NS_OK),
103 mPendingRequestCount(0),
104 mCallerLocation(std::move(aCallerLocation)),
105 mMode(aMode),
106 mDurability(aDurability),
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::Active);
136 MOZ_ASSERT(mReadyState != ReadyState::Inactive);
137 MOZ_ASSERT(mReadyState != ReadyState::Committing);
138 MOZ_ASSERT(!mNotedActiveTransaction);
139 MOZ_ASSERT(mSentCommitOrAbort);
140 MOZ_ASSERT_IF(HasTransactionChild(), mFiredCompleteOrAbort);
142 if (mRegistered) {
143 mDatabase->UnregisterTransaction(*this);
144 #ifdef DEBUG
145 mRegistered = false;
146 #endif
149 if (HasTransactionChild()) {
150 if (mMode == Mode::VersionChange) {
151 mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteMeInternal(
152 /* aFailedConstructor */ false);
153 } else {
154 mBackgroundActor.mNormalBackgroundActor->SendDeleteMeInternal();
157 MOZ_ASSERT(!HasTransactionChild(),
158 "SendDeleteMeInternal should have cleared!");
160 mozilla::DropJSObjects(this);
163 // static
164 SafeRefPtr<IDBTransaction> IDBTransaction::CreateVersionChange(
165 IDBDatabase* const aDatabase,
166 BackgroundVersionChangeTransactionChild* const aActor,
167 const NotNull<IDBOpenDBRequest*> aOpenRequest,
168 const int64_t aNextObjectStoreId, const int64_t aNextIndexId) {
169 MOZ_ASSERT(aDatabase);
170 aDatabase->AssertIsOnOwningThread();
171 MOZ_ASSERT(aActor);
172 MOZ_ASSERT(aNextObjectStoreId > 0);
173 MOZ_ASSERT(aNextIndexId > 0);
175 const nsTArray<nsString> emptyObjectStoreNames;
177 // XXX: What should we have as durability hint here?
178 auto transaction = MakeSafeRefPtr<IDBTransaction>(
179 aDatabase, emptyObjectStoreNames, Mode::VersionChange,
180 Durability::Default, JSCallingLocation(aOpenRequest->GetCallerLocation()),
181 CreatedFromFactoryFunction{});
183 transaction->NoteActiveTransaction();
185 transaction->mBackgroundActor.mVersionChangeBackgroundActor = aActor;
186 transaction->mNextObjectStoreId = aNextObjectStoreId;
187 transaction->mNextIndexId = aNextIndexId;
189 aDatabase->RegisterTransaction(*transaction);
190 transaction->mRegistered = true;
192 return transaction;
195 // static
196 SafeRefPtr<IDBTransaction> IDBTransaction::Create(
197 JSContext* const aCx, IDBDatabase* const aDatabase,
198 const nsTArray<nsString>& aObjectStoreNames, const Mode aMode,
199 const Durability aDurability) {
200 MOZ_ASSERT(aDatabase);
201 aDatabase->AssertIsOnOwningThread();
202 MOZ_ASSERT(!aObjectStoreNames.IsEmpty());
203 MOZ_ASSERT(aMode == Mode::ReadOnly || aMode == Mode::ReadWrite ||
204 aMode == Mode::ReadWriteFlush || aMode == Mode::Cleanup);
206 auto transaction = MakeSafeRefPtr<IDBTransaction>(
207 aDatabase, aObjectStoreNames, aMode, aDurability,
208 JSCallingLocation::Get(aCx), CreatedFromFactoryFunction{});
210 if (!NS_IsMainThread()) {
211 WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate();
212 MOZ_ASSERT(workerPrivate);
214 workerPrivate->AssertIsOnWorkerThread();
216 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
217 workerPrivate, "IDBTransaction",
218 [transaction = AsRefPtr(transaction.clonePtr())]() {
219 transaction->AssertIsOnOwningThread();
220 if (!transaction->IsCommittingOrFinished()) {
221 IDB_REPORT_INTERNAL_ERR();
222 transaction->AbortInternal(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
223 nullptr);
226 if (NS_WARN_IF(!workerRef)) {
227 #ifdef DEBUG
228 // Silence the destructor assertions if we never made this object live.
229 transaction->mReadyState = ReadyState::Finished;
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([this, actor, &aParams](auto& transactionChild) {
288 transactionChild.SendPBackgroundIDBRequestConstructor(
289 actor, NextRequestId(), aParams);
292 // Balanced in BackgroundRequestChild::Recv__delete__().
293 OnNewRequest();
295 return actor;
298 void IDBTransaction::OpenCursor(PBackgroundIDBCursorChild& aBackgroundActor,
299 const OpenCursorParams& aParams) {
300 AssertIsOnOwningThread();
301 MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
303 DoWithTransactionChild([this, &aBackgroundActor, &aParams](auto& actor) {
304 actor.SendPBackgroundIDBCursorConstructor(&aBackgroundActor,
305 NextRequestId(), aParams);
308 // Balanced in BackgroundCursorChild::RecvResponse().
309 OnNewRequest();
312 void IDBTransaction::RefreshSpec(const bool aMayDelete) {
313 AssertIsOnOwningThread();
315 for (auto& objectStore : mObjectStores) {
316 objectStore->RefreshSpec(aMayDelete);
319 for (auto& objectStore : mDeletedObjectStores) {
320 objectStore->RefreshSpec(false);
324 void IDBTransaction::OnNewRequest() {
325 AssertIsOnOwningThread();
327 if (!mPendingRequestCount) {
328 MOZ_ASSERT(ReadyState::Active == mReadyState);
329 mStarted.Flip();
332 ++mPendingRequestCount;
335 void IDBTransaction::OnRequestFinished(
336 const bool aRequestCompletedSuccessfully) {
337 AssertIsOnOwningThread();
338 MOZ_ASSERT(mReadyState != ReadyState::Active);
339 MOZ_ASSERT_IF(mReadyState == ReadyState::Finished, !NS_SUCCEEDED(mAbortCode));
340 MOZ_ASSERT(mPendingRequestCount);
342 --mPendingRequestCount;
344 if (!mPendingRequestCount) {
345 if (mSentCommitOrAbort) {
346 return;
349 if (aRequestCompletedSuccessfully) {
350 if (mReadyState == ReadyState::Inactive) {
351 mReadyState = ReadyState::Committing;
354 if (NS_SUCCEEDED(mAbortCode)) {
355 SendCommit(true);
356 } else {
357 SendAbort(mAbortCode);
359 } else {
360 // Don't try to send any more messages to the parent if the request actor
361 // was killed. Set our state accordingly to Finished.
362 mReadyState = ReadyState::Finished;
363 mSentCommitOrAbort.Flip();
364 IDB_LOG_MARK_CHILD_TRANSACTION(
365 "Request actor was killed, transaction will be aborted",
366 "IDBTransaction abort", LoggingSerialNumber());
371 void IDBTransaction::SendCommit(const bool aAutoCommit) {
372 AssertIsOnOwningThread();
373 MOZ_ASSERT(NS_SUCCEEDED(mAbortCode));
374 MOZ_ASSERT(IsCommittingOrFinished());
376 // Don't do this in the macro because we always need to increment the serial
377 // number to keep in sync with the parent.
378 const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
380 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
381 "Committing transaction (%s)", "IDBTransaction commit (%s)",
382 LoggingSerialNumber(), requestSerialNumber,
383 aAutoCommit ? "automatically" : "explicitly");
385 const int64_t requestId = NextRequestId();
387 const auto lastRequestId = [this, aAutoCommit,
388 requestId]() -> Maybe<decltype(requestId)> {
389 if (aAutoCommit) {
390 return Nothing();
393 // In case of an explicit commit, we need to note the id of the last
394 // request to check if a request submitted before the commit request
395 // failed. If we are currently in an event handler for a request on this
396 // transaction, ignore this request. This is used to synchronize the
397 // transaction's committing state with the parent side, to abort the
398 // transaction in case of a request resulting in an error (see
399 // https://w3c.github.io/IndexedDB/#async-execute-request, step 5.3.). With
400 // automatic commit, this is not necessary, as the transaction's state will
401 // only be set to committing after the last request completed.
402 const auto maybeCurrentTransaction =
403 BackgroundChildImpl::GetThreadLocalForCurrentThread()
404 ->mIndexedDBThreadLocal->MaybeCurrentTransactionRef();
405 const bool dispatchingEventForThisTransaction =
406 maybeCurrentTransaction && &maybeCurrentTransaction.ref() == this;
408 return Some(requestId
409 ? (requestId - (dispatchingEventForThisTransaction ? 0 : 1))
410 : 0);
411 }();
413 DoWithTransactionChild(
414 [lastRequestId](auto& actor) { actor.SendCommit(lastRequestId); });
416 mSentCommitOrAbort.Flip();
419 void IDBTransaction::SendAbort(const nsresult aResultCode) {
420 AssertIsOnOwningThread();
421 MOZ_ASSERT(NS_FAILED(aResultCode));
422 MOZ_ASSERT(IsCommittingOrFinished());
424 // Don't do this in the macro because we always need to increment the serial
425 // number to keep in sync with the parent.
426 const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
428 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
429 "Aborting transaction with result 0x%" PRIx32,
430 "IDBTransaction abort (0x%" PRIx32 ")", LoggingSerialNumber(),
431 requestSerialNumber, static_cast<uint32_t>(aResultCode));
433 DoWithTransactionChild(
434 [aResultCode](auto& actor) { actor.SendAbort(aResultCode); });
436 mSentCommitOrAbort.Flip();
439 void IDBTransaction::NoteActiveTransaction() {
440 AssertIsOnOwningThread();
441 MOZ_ASSERT(!mNotedActiveTransaction);
443 mDatabase->NoteActiveTransaction();
444 mNotedActiveTransaction = true;
447 void IDBTransaction::MaybeNoteInactiveTransaction() {
448 AssertIsOnOwningThread();
450 if (mNotedActiveTransaction) {
451 mDatabase->NoteInactiveTransaction();
452 mNotedActiveTransaction = false;
456 RefPtr<IDBObjectStore> IDBTransaction::CreateObjectStore(
457 ObjectStoreSpec& aSpec) {
458 AssertIsOnOwningThread();
459 MOZ_ASSERT(aSpec.metadata().id());
460 MOZ_ASSERT(Mode::VersionChange == mMode);
461 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
462 MOZ_ASSERT(IsActive());
464 #ifdef DEBUG
466 // TODO: Bind name outside of lambda capture as a workaround for GCC 7 bug
467 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66735.
468 const auto& name = aSpec.metadata().name();
469 // TODO: Use #ifdef and local variable as a workaround for Bug 1583449.
470 const bool objectStoreNameDoesNotYetExist =
471 std::all_of(mObjectStores.cbegin(), mObjectStores.cend(),
472 [&name](const auto& objectStore) {
473 return objectStore->Name() != name;
475 MOZ_ASSERT(objectStoreNameDoesNotYetExist);
477 #endif
479 MOZ_ALWAYS_TRUE(
480 mBackgroundActor.mVersionChangeBackgroundActor->SendCreateObjectStore(
481 aSpec.metadata()));
483 RefPtr<IDBObjectStore> objectStore = IDBObjectStore::Create(
484 SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, aSpec);
485 MOZ_ASSERT(objectStore);
487 mObjectStores.AppendElement(objectStore);
489 return objectStore;
492 void IDBTransaction::DeleteObjectStore(const int64_t aObjectStoreId) {
493 AssertIsOnOwningThread();
494 MOZ_ASSERT(aObjectStoreId);
495 MOZ_ASSERT(Mode::VersionChange == mMode);
496 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
497 MOZ_ASSERT(IsActive());
499 MOZ_ALWAYS_TRUE(
500 mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteObjectStore(
501 aObjectStoreId));
503 const auto foundIt =
504 std::find_if(mObjectStores.begin(), mObjectStores.end(),
505 [aObjectStoreId](const auto& objectStore) {
506 return objectStore->Id() == aObjectStoreId;
508 if (foundIt != mObjectStores.end()) {
509 auto& objectStore = *foundIt;
510 objectStore->NoteDeletion();
512 RefPtr<IDBObjectStore>* deletedObjectStore =
513 mDeletedObjectStores.AppendElement();
514 deletedObjectStore->swap(objectStore);
516 mObjectStores.RemoveElementAt(foundIt);
520 void IDBTransaction::RenameObjectStore(const int64_t aObjectStoreId,
521 const nsAString& aName) const {
522 AssertIsOnOwningThread();
523 MOZ_ASSERT(aObjectStoreId);
524 MOZ_ASSERT(Mode::VersionChange == mMode);
525 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
526 MOZ_ASSERT(IsActive());
528 MOZ_ALWAYS_TRUE(
529 mBackgroundActor.mVersionChangeBackgroundActor->SendRenameObjectStore(
530 aObjectStoreId, nsString(aName)));
533 void IDBTransaction::CreateIndex(
534 IDBObjectStore* const aObjectStore,
535 const indexedDB::IndexMetadata& aMetadata) const {
536 AssertIsOnOwningThread();
537 MOZ_ASSERT(aObjectStore);
538 MOZ_ASSERT(aMetadata.id());
539 MOZ_ASSERT(Mode::VersionChange == mMode);
540 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
541 MOZ_ASSERT(IsActive());
543 MOZ_ALWAYS_TRUE(
544 mBackgroundActor.mVersionChangeBackgroundActor->SendCreateIndex(
545 aObjectStore->Id(), aMetadata));
548 void IDBTransaction::DeleteIndex(IDBObjectStore* const aObjectStore,
549 const int64_t aIndexId) const {
550 AssertIsOnOwningThread();
551 MOZ_ASSERT(aObjectStore);
552 MOZ_ASSERT(aIndexId);
553 MOZ_ASSERT(Mode::VersionChange == mMode);
554 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
555 MOZ_ASSERT(IsActive());
557 MOZ_ALWAYS_TRUE(
558 mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteIndex(
559 aObjectStore->Id(), aIndexId));
562 void IDBTransaction::RenameIndex(IDBObjectStore* const aObjectStore,
563 const int64_t aIndexId,
564 const nsAString& aName) const {
565 AssertIsOnOwningThread();
566 MOZ_ASSERT(aObjectStore);
567 MOZ_ASSERT(aIndexId);
568 MOZ_ASSERT(Mode::VersionChange == mMode);
569 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
570 MOZ_ASSERT(IsActive());
572 MOZ_ALWAYS_TRUE(
573 mBackgroundActor.mVersionChangeBackgroundActor->SendRenameIndex(
574 aObjectStore->Id(), aIndexId, nsString(aName)));
577 void IDBTransaction::AbortInternal(const nsresult aAbortCode,
578 RefPtr<DOMException> aError) {
579 AssertIsOnOwningThread();
580 MOZ_ASSERT(NS_FAILED(aAbortCode));
581 MOZ_ASSERT(!IsCommittingOrFinished());
583 const bool isVersionChange = mMode == Mode::VersionChange;
584 const bool needToSendAbort = !mStarted;
586 mAbortCode = aAbortCode;
587 mReadyState = ReadyState::Finished;
588 mError = std::move(aError);
590 if (isVersionChange) {
591 // If a version change transaction is aborted, we must revert the world
592 // back to its previous state unless we're being invalidated after the
593 // transaction already completed.
594 if (!mDatabase->IsInvalidated()) {
595 mDatabase->RevertToPreviousState();
598 // We do the reversion only for the mObjectStores/mDeletedObjectStores but
599 // not for the mIndexes/mDeletedIndexes of each IDBObjectStore because it's
600 // time-consuming(O(m*n)) and mIndexes/mDeletedIndexes won't be used anymore
601 // in IDBObjectStore::(Create|Delete)Index() and IDBObjectStore::Index() in
602 // which all the executions are returned earlier by
603 // !transaction->IsActive().
605 const nsTArray<ObjectStoreSpec>& specArray =
606 mDatabase->Spec()->objectStores();
608 if (specArray.IsEmpty()) {
609 // This case is specially handled as a performance optimization, it is
610 // equivalent to the else block.
611 mObjectStores.Clear();
612 } else {
613 const auto validIds = TransformToHashtable<nsUint64HashKey>(
614 specArray, [](const auto& spec) {
615 const int64_t objectStoreId = spec.metadata().id();
616 MOZ_ASSERT(objectStoreId);
617 return static_cast<uint64_t>(objectStoreId);
620 mObjectStores.RemoveLastElements(
621 mObjectStores.end() -
622 std::remove_if(mObjectStores.begin(), mObjectStores.end(),
623 [&validIds](const auto& objectStore) {
624 return !validIds.Contains(
625 uint64_t(objectStore->Id()));
626 }));
628 std::copy_if(std::make_move_iterator(mDeletedObjectStores.begin()),
629 std::make_move_iterator(mDeletedObjectStores.end()),
630 MakeBackInserter(mObjectStores),
631 [&validIds](const auto& deletedObjectStore) {
632 const int64_t objectStoreId = deletedObjectStore->Id();
633 MOZ_ASSERT(objectStoreId);
634 return validIds.Contains(uint64_t(objectStoreId));
637 mDeletedObjectStores.Clear();
640 // Fire the abort event if there are no outstanding requests. Otherwise the
641 // abort event will be fired when all outstanding requests finish.
642 if (needToSendAbort) {
643 SendAbort(aAbortCode);
646 if (isVersionChange) {
647 mDatabase->Close();
651 void IDBTransaction::Abort(IDBRequest* const aRequest) {
652 AssertIsOnOwningThread();
653 MOZ_ASSERT(aRequest);
655 if (IsCommittingOrFinished()) {
656 // Already started (and maybe finished) the commit or abort so there is
657 // nothing to do here.
658 return;
661 ErrorResult rv;
662 RefPtr<DOMException> error = aRequest->GetError(rv);
664 // TODO: Do we deliberately ignore rv here? Isn't there a static analysis that
665 // prevents that?
667 AbortInternal(aRequest->GetErrorCode(), std::move(error));
670 void IDBTransaction::Abort(const nsresult aErrorCode) {
671 AssertIsOnOwningThread();
673 if (IsCommittingOrFinished()) {
674 // Already started (and maybe finished) the commit or abort so there is
675 // nothing to do here.
676 return;
679 AbortInternal(aErrorCode, DOMException::Create(aErrorCode));
682 // Specified by https://w3c.github.io/IndexedDB/#dom-idbtransaction-abort.
683 void IDBTransaction::Abort(ErrorResult& aRv) {
684 AssertIsOnOwningThread();
686 if (IsCommittingOrFinished()) {
687 aRv = NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
688 return;
691 mReadyState = ReadyState::Inactive;
693 AbortInternal(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR, nullptr);
695 mAbortedByScript.Flip();
698 // Specified by https://w3c.github.io/IndexedDB/#dom-idbtransaction-commit.
699 void IDBTransaction::Commit(ErrorResult& aRv) {
700 AssertIsOnOwningThread();
702 if (mReadyState != ReadyState::Active || !mNotedActiveTransaction) {
703 aRv = NS_ERROR_DOM_INVALID_STATE_ERR;
704 return;
707 MOZ_ASSERT(!mSentCommitOrAbort);
709 MOZ_ASSERT(mReadyState == ReadyState::Active);
710 mReadyState = ReadyState::Committing;
711 if (NS_WARN_IF(NS_FAILED(mAbortCode))) {
712 SendAbort(mAbortCode);
713 aRv = mAbortCode;
714 return;
717 #ifdef DEBUG
718 mWasExplicitlyCommitted.Flip();
719 #endif
721 SendCommit(false);
724 void IDBTransaction::FireCompleteOrAbortEvents(const nsresult aResult) {
725 AssertIsOnOwningThread();
726 MOZ_ASSERT(!mFiredCompleteOrAbort);
728 mReadyState = ReadyState::Finished;
730 #ifdef DEBUG
731 mFiredCompleteOrAbort.Flip();
732 #endif
734 // Make sure we drop the WorkerRef when this function completes.
735 const auto scopeExit = MakeScopeExit([&] { mWorkerRef = nullptr; });
737 RefPtr<Event> event;
738 if (NS_SUCCEEDED(aResult)) {
739 event = CreateGenericEvent(this, nsDependentString(kCompleteEventType),
740 eDoesNotBubble, eNotCancelable);
741 MOZ_ASSERT(event);
743 // If we hit this assertion, it probably means transaction object on the
744 // parent process doesn't propagate error properly.
745 MOZ_ASSERT(NS_SUCCEEDED(mAbortCode));
746 } else {
747 if (aResult == NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR) {
748 mDatabase->SetQuotaExceeded();
751 if (!mError && !mAbortedByScript) {
752 mError = DOMException::Create(aResult);
755 event = CreateGenericEvent(this, nsDependentString(kAbortEventType),
756 eDoesBubble, eNotCancelable);
757 MOZ_ASSERT(event);
759 if (NS_SUCCEEDED(mAbortCode)) {
760 mAbortCode = aResult;
764 if (NS_SUCCEEDED(mAbortCode)) {
765 IDB_LOG_MARK_CHILD_TRANSACTION("Firing 'complete' event",
766 "IDBTransaction 'complete' event",
767 mLoggingSerialNumber);
768 } else {
769 IDB_LOG_MARK_CHILD_TRANSACTION(
770 "Firing 'abort' event with error 0x%" PRIx32,
771 "IDBTransaction 'abort' event (0x%" PRIx32 ")", mLoggingSerialNumber,
772 static_cast<uint32_t>(mAbortCode));
775 IgnoredErrorResult rv;
776 DispatchEvent(*event, rv);
777 if (rv.Failed()) {
778 NS_WARNING("DispatchEvent failed!");
781 // Normally, we note inactive transaction here instead of
782 // IDBTransaction::ClearBackgroundActor() because here is the earliest place
783 // to know that it becomes non-blocking to allow the scheduler to start the
784 // preemption as soon as it can.
785 // Note: If the IDBTransaction object is held by the script,
786 // ClearBackgroundActor() will be done in ~IDBTransaction() until garbage
787 // collected after its window is closed which prevents us to preempt its
788 // window immediately after committed.
789 MaybeNoteInactiveTransaction();
792 int64_t IDBTransaction::NextObjectStoreId() {
793 AssertIsOnOwningThread();
794 MOZ_ASSERT(Mode::VersionChange == mMode);
796 return mNextObjectStoreId++;
799 int64_t IDBTransaction::NextIndexId() {
800 AssertIsOnOwningThread();
801 MOZ_ASSERT(Mode::VersionChange == mMode);
803 return mNextIndexId++;
806 int64_t IDBTransaction::NextRequestId() {
807 AssertIsOnOwningThread();
809 return mNextRequestId++;
812 void IDBTransaction::InvalidateCursorCaches() {
813 AssertIsOnOwningThread();
815 for (const auto& cursor : mCursors) {
816 cursor->InvalidateCachedResponses();
820 void IDBTransaction::RegisterCursor(IDBCursor& aCursor) {
821 AssertIsOnOwningThread();
823 mCursors.AppendElement(WrapNotNullUnchecked(&aCursor));
826 void IDBTransaction::UnregisterCursor(IDBCursor& aCursor) {
827 AssertIsOnOwningThread();
829 DebugOnly<bool> removed = mCursors.RemoveElement(&aCursor);
830 MOZ_ASSERT(removed);
833 nsIGlobalObject* IDBTransaction::GetParentObject() const {
834 AssertIsOnOwningThread();
836 return mDatabase->GetParentObject();
839 IDBTransactionMode IDBTransaction::GetMode(ErrorResult& aRv) const {
840 AssertIsOnOwningThread();
842 switch (mMode) {
843 case Mode::ReadOnly:
844 return IDBTransactionMode::Readonly;
846 case Mode::ReadWrite:
847 return IDBTransactionMode::Readwrite;
849 case Mode::ReadWriteFlush:
850 return IDBTransactionMode::Readwriteflush;
852 case Mode::Cleanup:
853 return IDBTransactionMode::Cleanup;
855 case Mode::VersionChange:
856 return IDBTransactionMode::Versionchange;
858 case Mode::Invalid:
859 default:
860 MOZ_CRASH("Bad mode!");
864 IDBTransactionDurability IDBTransaction::GetDurability(ErrorResult& aRv) const {
865 AssertIsOnOwningThread();
867 switch (mDurability) {
868 case Durability::Default:
869 return IDBTransactionDurability::Default;
871 case Durability::Strict:
872 return IDBTransactionDurability::Strict;
874 case Durability::Relaxed:
875 return IDBTransactionDurability::Relaxed;
877 default:
878 MOZ_CRASH("Bad mode!");
882 DOMException* IDBTransaction::GetError() const {
883 AssertIsOnOwningThread();
885 return mError;
888 RefPtr<DOMStringList> IDBTransaction::ObjectStoreNames() const {
889 AssertIsOnOwningThread();
891 if (mMode == Mode::VersionChange) {
892 return mDatabase->ObjectStoreNames();
895 auto list = MakeRefPtr<DOMStringList>();
896 list->StringArray() = mObjectStoreNames.Clone();
897 return list;
900 RefPtr<IDBObjectStore> IDBTransaction::ObjectStore(const nsAString& aName,
901 ErrorResult& aRv) {
902 AssertIsOnOwningThread();
904 if (IsCommittingOrFinished()) {
905 aRv.ThrowInvalidStateError("Transaction is already committing or done.");
906 return nullptr;
909 auto* const spec = [this, &aName]() -> ObjectStoreSpec* {
910 if (IDBTransaction::Mode::VersionChange == mMode ||
911 mObjectStoreNames.Contains(aName)) {
912 return mDatabase->LookupModifiableObjectStoreSpec(
913 [&aName](const auto& objectStore) {
914 return objectStore.metadata().name() == aName;
917 return nullptr;
918 }();
920 if (!spec) {
921 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
922 return nullptr;
925 RefPtr<IDBObjectStore> objectStore;
927 const auto foundIt = std::find_if(
928 mObjectStores.cbegin(), mObjectStores.cend(),
929 [desiredId = spec->metadata().id()](const auto& existingObjectStore) {
930 return existingObjectStore->Id() == desiredId;
932 if (foundIt != mObjectStores.cend()) {
933 objectStore = *foundIt;
934 } else {
935 objectStore = IDBObjectStore::Create(
936 SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, *spec);
937 MOZ_ASSERT(objectStore);
939 mObjectStores.AppendElement(objectStore);
942 return objectStore;
945 NS_IMPL_ADDREF_INHERITED(IDBTransaction, DOMEventTargetHelper)
946 NS_IMPL_RELEASE_INHERITED(IDBTransaction, DOMEventTargetHelper)
948 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBTransaction)
949 NS_INTERFACE_MAP_ENTRY(nsIRunnable)
950 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
952 NS_IMPL_CYCLE_COLLECTION_CLASS(IDBTransaction)
954 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBTransaction,
955 DOMEventTargetHelper)
956 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDatabase)
957 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
958 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObjectStores)
959 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeletedObjectStores)
960 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
962 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBTransaction,
963 DOMEventTargetHelper)
964 // Don't unlink mDatabase!
965 NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
966 NS_IMPL_CYCLE_COLLECTION_UNLINK(mObjectStores)
967 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDeletedObjectStores)
968 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
970 JSObject* IDBTransaction::WrapObject(JSContext* const aCx,
971 JS::Handle<JSObject*> aGivenProto) {
972 AssertIsOnOwningThread();
974 return IDBTransaction_Binding::Wrap(aCx, this, std::move(aGivenProto));
977 void IDBTransaction::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
978 AssertIsOnOwningThread();
980 aVisitor.mCanHandle = true;
981 aVisitor.SetParentTarget(mDatabase, false);
984 NS_IMETHODIMP
985 IDBTransaction::Run() {
986 AssertIsOnOwningThread();
988 // TODO: Instead of checking for Finished and Committing states here, we could
989 // remove the transaction from the pending IDB transactions list on
990 // abort/commit.
992 if (ReadyState::Finished == mReadyState) {
993 // There are three cases where mReadyState is set to Finished: In
994 // FileCompleteOrAbortEvents, AbortInternal and in CommitIfNotStarted. We
995 // shouldn't get here after CommitIfNotStarted again.
996 MOZ_ASSERT(mFiredCompleteOrAbort || IsAborted());
997 return NS_OK;
1000 if (ReadyState::Committing == mReadyState) {
1001 MOZ_ASSERT(mSentCommitOrAbort);
1002 return NS_OK;
1004 // We're back at the event loop, no longer newborn, so
1005 // return to Inactive state:
1006 // https://w3c.github.io/IndexedDB/#cleanup-indexed-database-transactions.
1007 MOZ_ASSERT(ReadyState::Active == mReadyState);
1008 mReadyState = ReadyState::Inactive;
1010 CommitIfNotStarted();
1012 return NS_OK;
1015 void IDBTransaction::CommitIfNotStarted() {
1016 AssertIsOnOwningThread();
1018 MOZ_ASSERT(ReadyState::Inactive == mReadyState);
1020 // Maybe commit if there were no requests generated.
1021 if (!mStarted) {
1022 MOZ_ASSERT(!mPendingRequestCount);
1023 mReadyState = ReadyState::Finished;
1025 SendCommit(true);
1029 } // namespace mozilla::dom