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"
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
));
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
;
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
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
),
98 mObjectStoreNames(aObjectStoreNames
.Clone()),
99 mLoggingSerialNumber(GetIndexedDBThreadLocal()->NextTransactionSN(aMode
)),
100 mNextObjectStoreId(0),
104 mPendingRequestCount(0),
105 mFilename(std::move(aFilename
)),
109 mDurability(aDurability
),
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;
120 if (!aObjectStoreNames
.IsEmpty()) {
121 // Make sure the array is properly sorted.
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()));
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
);
146 mDatabase
->UnregisterTransaction(*this);
152 if (HasTransactionChild()) {
153 if (mMode
== Mode::VersionChange
) {
154 mBackgroundActor
.mVersionChangeBackgroundActor
->SendDeleteMeInternal(
155 /* aFailedConstructor */ false);
157 mBackgroundActor
.mNormalBackgroundActor
->SendDeleteMeInternal();
160 MOZ_ASSERT(!HasTransactionChild(),
161 "SendDeleteMeInternal should have cleared!");
163 mozilla::DropJSObjects(this);
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();
175 MOZ_ASSERT(aNextObjectStoreId
> 0);
176 MOZ_ASSERT(aNextIndexId
> 0);
178 const nsTArray
<nsString
> emptyObjectStoreNames
;
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;
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
);
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
,
235 if (NS_WARN_IF(!workerRef
)) {
237 // Silence the destructor assertions if we never made this object live.
238 transaction
->mReadyState
= ReadyState::Finished
;
239 transaction
->mSentCommitOrAbort
.Flip();
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;
258 Maybe
<IDBTransaction
&> IDBTransaction::MaybeCurrent() {
259 using namespace mozilla::ipc
;
261 MOZ_ASSERT(BackgroundChild::GetForCurrentThread());
263 return GetIndexedDBThreadLocal()->MaybeCurrentTransactionRef();
268 void IDBTransaction::AssertIsOnOwningThread() const {
269 MOZ_ASSERT(mDatabase
);
270 mDatabase
->AssertIsOnOwningThread();
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__().
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().
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
);
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
) {
358 if (aRequestCompletedSuccessfully
) {
359 if (mReadyState
== ReadyState::Inactive
) {
360 mReadyState
= ReadyState::Committing
;
363 if (NS_SUCCEEDED(mAbortCode
)) {
366 SendAbort(mAbortCode
);
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
)> {
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))
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();
472 aFilename
= mFilename
;
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());
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
);
501 mBackgroundActor
.mVersionChangeBackgroundActor
->SendCreateObjectStore(
504 RefPtr
<IDBObjectStore
> objectStore
= IDBObjectStore::Create(
505 SafeRefPtr
{this, AcquireStrongRefFromRawPtr
{}}, aSpec
);
506 MOZ_ASSERT(objectStore
);
508 mObjectStores
.AppendElement(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());
521 mBackgroundActor
.mVersionChangeBackgroundActor
->SendDeleteObjectStore(
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());
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());
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());
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());
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();
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()));
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
) {
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.
683 RefPtr
<DOMException
> error
= aRequest
->GetError(rv
);
685 // TODO: Do we deliberately ignore rv here? Isn't there a static analysis 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.
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
;
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
;
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
);
739 mWasExplicitlyCommitted
.Flip();
745 void IDBTransaction::FireCompleteOrAbortEvents(const nsresult aResult
) {
746 AssertIsOnOwningThread();
747 MOZ_ASSERT(!mFiredCompleteOrAbort
);
749 mReadyState
= ReadyState::Finished
;
752 mFiredCompleteOrAbort
.Flip();
755 // Make sure we drop the WorkerRef when this function completes.
756 const auto scopeExit
= MakeScopeExit([&] { mWorkerRef
= nullptr; });
759 if (NS_SUCCEEDED(aResult
)) {
760 event
= CreateGenericEvent(this, nsDependentString(kCompleteEventType
),
761 eDoesNotBubble
, eNotCancelable
);
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
));
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
);
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
);
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
);
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
);
854 nsIGlobalObject
* IDBTransaction::GetParentObject() const {
855 AssertIsOnOwningThread();
857 return mDatabase
->GetParentObject();
860 IDBTransactionMode
IDBTransaction::GetMode(ErrorResult
& aRv
) const {
861 AssertIsOnOwningThread();
865 return IDBTransactionMode::Readonly
;
867 case Mode::ReadWrite
:
868 return IDBTransactionMode::Readwrite
;
870 case Mode::ReadWriteFlush
:
871 return IDBTransactionMode::Readwriteflush
;
874 return IDBTransactionMode::Cleanup
;
876 case Mode::VersionChange
:
877 return IDBTransactionMode::Versionchange
;
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
;
899 MOZ_CRASH("Bad mode!");
903 DOMException
* IDBTransaction::GetError() const {
904 AssertIsOnOwningThread();
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();
921 RefPtr
<IDBObjectStore
> IDBTransaction::ObjectStore(const nsAString
& aName
,
923 AssertIsOnOwningThread();
925 if (IsCommittingOrFinished()) {
926 aRv
.ThrowInvalidStateError("Transaction is already committing or done.");
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
;
942 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR
);
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
;
956 objectStore
= IDBObjectStore::Create(
957 SafeRefPtr
{this, AcquireStrongRefFromRawPtr
{}}, *spec
);
958 MOZ_ASSERT(objectStore
);
960 mObjectStores
.AppendElement(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);
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
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());
1021 if (ReadyState::Committing
== mReadyState
) {
1022 MOZ_ASSERT(mSentCommitOrAbort
);
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();
1036 void IDBTransaction::CommitIfNotStarted() {
1037 AssertIsOnOwningThread();
1039 MOZ_ASSERT(ReadyState::Inactive
== mReadyState
);
1041 // Maybe commit if there were no requests generated.
1043 MOZ_ASSERT(!mPendingRequestCount
);
1044 mReadyState
= ReadyState::Finished
;
1050 } // namespace mozilla::dom