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/. */
6 
7 #include "IDBTransaction.h"
8 
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"
30 
31 // Include this last to avoid path problems on Windows.
32 #include "ActorsChild.h"
33 
34 namespace {
35 using namespace mozilla::dom::indexedDB;
36 using namespace mozilla::ipc;
37 
38 // TODO: Move this to xpcom/ds.
39 template <typename T, typename Range, typename Transformation>
TransformToHashtable(const Range & aRange,const Transformation & aTransformation)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));
54   }
55   return res;
56 }
57 
GetIndexedDBThreadLocal()58 ThreadLocal* GetIndexedDBThreadLocal() {
59   BackgroundChildImpl::ThreadLocal* const threadLocal =
60       BackgroundChildImpl::GetThreadLocalForCurrentThread();
61   MOZ_ASSERT(threadLocal);
62 
63   ThreadLocal* idbThreadLocal = threadLocal->mIndexedDBThreadLocal.get();
64   MOZ_ASSERT(idbThreadLocal);
65 
66   return idbThreadLocal;
67 }
68 }  // namespace
69 
70 namespace mozilla::dom {
71 
72 using namespace mozilla::dom::indexedDB;
73 using namespace mozilla::ipc;
74 
HasTransactionChild() const75 bool IDBTransaction::HasTransactionChild() const {
76   return (mMode == Mode::VersionChange
77               ? static_cast<void*>(
78                     mBackgroundActor.mVersionChangeBackgroundActor)
79               : mBackgroundActor.mNormalBackgroundActor) != nullptr;
80 }
81 
82 template <typename Func>
DoWithTransactionChild(const Func & aFunc) const83 auto IDBTransaction::DoWithTransactionChild(const Func& aFunc) const {
84   MOZ_ASSERT(HasTransactionChild());
85   return mMode == Mode::VersionChange
86              ? aFunc(*mBackgroundActor.mVersionChangeBackgroundActor)
87              : aFunc(*mBackgroundActor.mNormalBackgroundActor);
88 }
89 
IDBTransaction(IDBDatabase * const aDatabase,const nsTArray<nsString> & aObjectStoreNames,const Mode aMode,nsString aFilename,const uint32_t aLineNo,const uint32_t aColumn,CreatedFromFactoryFunction)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();
111 
112   // This also nulls mBackgroundActor.mVersionChangeBackgroundActor, so this is
113   // valid also for mMode == Mode::VersionChange.
114   mBackgroundActor.mNormalBackgroundActor = nullptr;
115 
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()));
121 
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()));
126   }
127 #endif
128 
129   mozilla::HoldJSObjects(this);
130 }
131 
~IDBTransaction()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);
139 
140   if (mRegistered) {
141     mDatabase->UnregisterTransaction(*this);
142 #ifdef DEBUG
143     mRegistered = false;
144 #endif
145   }
146 
147   if (HasTransactionChild()) {
148     if (mMode == Mode::VersionChange) {
149       mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteMeInternal(
150           /* aFailedConstructor */ false);
151     } else {
152       mBackgroundActor.mNormalBackgroundActor->SendDeleteMeInternal();
153     }
154   }
155   MOZ_ASSERT(!HasTransactionChild(),
156              "SendDeleteMeInternal should have cleared!");
157 
158   mozilla::DropJSObjects(this);
159 }
160 
161 // static
CreateVersionChange(IDBDatabase * const aDatabase,BackgroundVersionChangeTransactionChild * const aActor,const NotNull<IDBOpenDBRequest * > aOpenRequest,const int64_t aNextObjectStoreId,const int64_t aNextIndexId)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);
172 
173   const nsTArray<nsString> emptyObjectStoreNames;
174 
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{});
181 
182   transaction->NoteActiveTransaction();
183 
184   transaction->mBackgroundActor.mVersionChangeBackgroundActor = aActor;
185   transaction->mNextObjectStoreId = aNextObjectStoreId;
186   transaction->mNextIndexId = aNextIndexId;
187 
188   aDatabase->RegisterTransaction(*transaction);
189   transaction->mRegistered = true;
190 
191   return transaction;
192 }
193 
194 // static
Create(JSContext * const aCx,IDBDatabase * const aDatabase,const nsTArray<nsString> & aObjectStoreNames,const Mode aMode)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);
203 
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{});
210 
211   if (!NS_IsMainThread()) {
212     WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate();
213     MOZ_ASSERT(workerPrivate);
214 
215     workerPrivate->AssertIsOnWorkerThread();
216 
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);
225           }
226         });
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;
233     }
234 
235     transaction->mWorkerRef = std::move(workerRef);
236   }
237 
238   nsCOMPtr<nsIRunnable> runnable =
239       do_QueryObject(transaction.unsafeGetRawPtr());
240   nsContentUtils::AddPendingIDBTransaction(runnable.forget());
241 
242   aDatabase->RegisterTransaction(*transaction);
243   transaction->mRegistered = true;
244 
245   return transaction;
246 }
247 
248 // static
MaybeCurrent()249 Maybe<IDBTransaction&> IDBTransaction::MaybeCurrent() {
250   using namespace mozilla::ipc;
251 
252   MOZ_ASSERT(BackgroundChild::GetForCurrentThread());
253 
254   return GetIndexedDBThreadLocal()->MaybeCurrentTransactionRef();
255 }
256 
257 #ifdef DEBUG
258 
AssertIsOnOwningThread() const259 void IDBTransaction::AssertIsOnOwningThread() const {
260   MOZ_ASSERT(mDatabase);
261   mDatabase->AssertIsOnOwningThread();
262 }
263 
264 #endif  // DEBUG
265 
SetBackgroundActor(indexedDB::BackgroundTransactionChild * const aBackgroundActor)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);
272 
273   NoteActiveTransaction();
274 
275   mBackgroundActor.mNormalBackgroundActor = aBackgroundActor;
276 }
277 
StartRequest(MovingNotNull<RefPtr<mozilla::dom::IDBRequest>> aRequest,const RequestParams & aParams)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);
283 
284   BackgroundRequestChild* const actor =
285       new BackgroundRequestChild(std::move(aRequest));
286 
287   DoWithTransactionChild([actor, &aParams](auto& transactionChild) {
288     transactionChild.SendPBackgroundIDBRequestConstructor(actor, aParams);
289   });
290 
291   MOZ_ASSERT(actor->GetActorEventTarget(),
292              "The event target shall be inherited from its manager actor.");
293 
294   // Balanced in BackgroundRequestChild::Recv__delete__().
295   OnNewRequest();
296 
297   return actor;
298 }
299 
OpenCursor(PBackgroundIDBCursorChild & aBackgroundActor,const OpenCursorParams & aParams)300 void IDBTransaction::OpenCursor(PBackgroundIDBCursorChild& aBackgroundActor,
301                                 const OpenCursorParams& aParams) {
302   AssertIsOnOwningThread();
303   MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
304 
305   DoWithTransactionChild([&aBackgroundActor, &aParams](auto& actor) {
306     actor.SendPBackgroundIDBCursorConstructor(&aBackgroundActor, aParams);
307   });
308 
309   MOZ_ASSERT(aBackgroundActor.GetActorEventTarget(),
310              "The event target shall be inherited from its manager actor.");
311 
312   // Balanced in BackgroundCursorChild::RecvResponse().
313   OnNewRequest();
314 }
315 
RefreshSpec(const bool aMayDelete)316 void IDBTransaction::RefreshSpec(const bool aMayDelete) {
317   AssertIsOnOwningThread();
318 
319   for (auto& objectStore : mObjectStores) {
320     objectStore->RefreshSpec(aMayDelete);
321   }
322 
323   for (auto& objectStore : mDeletedObjectStores) {
324     objectStore->RefreshSpec(false);
325   }
326 }
327 
OnNewRequest()328 void IDBTransaction::OnNewRequest() {
329   AssertIsOnOwningThread();
330 
331   if (!mPendingRequestCount) {
332     MOZ_ASSERT(ReadyState::Active == mReadyState);
333     mStarted.Flip();
334   }
335 
336   ++mPendingRequestCount;
337 }
338 
OnRequestFinished(const bool aRequestCompletedSuccessfully)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);
345 
346   --mPendingRequestCount;
347 
348   if (!mPendingRequestCount) {
349     if (mSentCommitOrAbort) {
350       return;
351     }
352 
353     if (mReadyState == ReadyState::Inactive) {
354       mReadyState = ReadyState::Committing;
355     }
356 
357     if (aRequestCompletedSuccessfully) {
358       if (NS_SUCCEEDED(mAbortCode)) {
359         SendCommit(true);
360       } else {
361         SendAbort(mAbortCode);
362       }
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());
370     }
371   }
372 }
373 
SendCommit(const bool aAutoCommit)374 void IDBTransaction::SendCommit(const bool aAutoCommit) {
375   AssertIsOnOwningThread();
376   MOZ_ASSERT(NS_SUCCEEDED(mAbortCode));
377   MOZ_ASSERT(IsCommittingOrFinished());
378 
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();
382 
383   IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
384       "Committing transaction (%s)", "IDBTransaction commit (%s)",
385       LoggingSerialNumber(), requestSerialNumber,
386       aAutoCommit ? "automatically" : "explicitly");
387 
388   const auto lastRequestSerialNumber =
389       [this, aAutoCommit,
390        requestSerialNumber]() -> Maybe<decltype(requestSerialNumber)> {
391     if (aAutoCommit) {
392       return Nothing();
393     }
394 
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;
409 
410     return Some(requestSerialNumber
411                     ? (requestSerialNumber -
412                        (dispatchingEventForThisTransaction ? 0 : 1))
413                     : 0);
414   }();
415 
416   DoWithTransactionChild([lastRequestSerialNumber](auto& actor) {
417     actor.SendCommit(lastRequestSerialNumber);
418   });
419 
420   mSentCommitOrAbort.Flip();
421 }
422 
SendAbort(const nsresult aResultCode)423 void IDBTransaction::SendAbort(const nsresult aResultCode) {
424   AssertIsOnOwningThread();
425   MOZ_ASSERT(NS_FAILED(aResultCode));
426   MOZ_ASSERT(IsCommittingOrFinished());
427 
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();
431 
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));
436 
437   DoWithTransactionChild(
438       [aResultCode](auto& actor) { actor.SendAbort(aResultCode); });
439 
440   mSentCommitOrAbort.Flip();
441 }
442 
NoteActiveTransaction()443 void IDBTransaction::NoteActiveTransaction() {
444   AssertIsOnOwningThread();
445   MOZ_ASSERT(!mNotedActiveTransaction);
446 
447   mDatabase->NoteActiveTransaction();
448   mNotedActiveTransaction = true;
449 }
450 
MaybeNoteInactiveTransaction()451 void IDBTransaction::MaybeNoteInactiveTransaction() {
452   AssertIsOnOwningThread();
453 
454   if (mNotedActiveTransaction) {
455     mDatabase->NoteInactiveTransaction();
456     mNotedActiveTransaction = false;
457   }
458 }
459 
460 IDBTransaction::AutoRestoreState<IDBTransaction::ReadyState::Inactive,
461                                  IDBTransaction::ReadyState::Active>
TemporarilyTransitionToActive()462 IDBTransaction::TemporarilyTransitionToActive() {
463   return AutoRestoreState<ReadyState::Inactive, ReadyState::Active>{*this};
464 }
465 
466 IDBTransaction::AutoRestoreState<IDBTransaction::ReadyState::Active,
467                                  IDBTransaction::ReadyState::Inactive>
TemporarilyTransitionToInactive()468 IDBTransaction::TemporarilyTransitionToInactive() {
469   return AutoRestoreState<ReadyState::Active, ReadyState::Inactive>{*this};
470 }
471 
GetCallerLocation(nsAString & aFilename,uint32_t * const aLineNo,uint32_t * const aColumn) const472 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);
478 
479   aFilename = mFilename;
480   *aLineNo = mLineNo;
481   *aColumn = mColumn;
482 }
483 
CreateObjectStore(ObjectStoreSpec & aSpec)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());
491 
492 #ifdef DEBUG
493   {
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;
502                     });
503     MOZ_ASSERT(objectStoreNameDoesNotYetExist);
504   }
505 #endif
506 
507   MOZ_ALWAYS_TRUE(
508       mBackgroundActor.mVersionChangeBackgroundActor->SendCreateObjectStore(
509           aSpec.metadata()));
510 
511   RefPtr<IDBObjectStore> objectStore = IDBObjectStore::Create(
512       SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, aSpec);
513   MOZ_ASSERT(objectStore);
514 
515   mObjectStores.AppendElement(objectStore);
516 
517   return objectStore;
518 }
519 
DeleteObjectStore(const int64_t aObjectStoreId)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());
526 
527   MOZ_ALWAYS_TRUE(
528       mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteObjectStore(
529           aObjectStoreId));
530 
531   const auto foundIt =
532       std::find_if(mObjectStores.begin(), mObjectStores.end(),
533                    [aObjectStoreId](const auto& objectStore) {
534                      return objectStore->Id() == aObjectStoreId;
535                    });
536   if (foundIt != mObjectStores.end()) {
537     auto& objectStore = *foundIt;
538     objectStore->NoteDeletion();
539 
540     RefPtr<IDBObjectStore>* deletedObjectStore =
541         mDeletedObjectStores.AppendElement();
542     deletedObjectStore->swap(objectStore);
543 
544     mObjectStores.RemoveElementAt(foundIt);
545   }
546 }
547 
RenameObjectStore(const int64_t aObjectStoreId,const nsAString & aName)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());
555 
556   MOZ_ALWAYS_TRUE(
557       mBackgroundActor.mVersionChangeBackgroundActor->SendRenameObjectStore(
558           aObjectStoreId, nsString(aName)));
559 }
560 
CreateIndex(IDBObjectStore * const aObjectStore,const indexedDB::IndexMetadata & aMetadata)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());
569 
570   MOZ_ALWAYS_TRUE(
571       mBackgroundActor.mVersionChangeBackgroundActor->SendCreateIndex(
572           aObjectStore->Id(), aMetadata));
573 }
574 
DeleteIndex(IDBObjectStore * const aObjectStore,const int64_t aIndexId)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());
583 
584   MOZ_ALWAYS_TRUE(
585       mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteIndex(
586           aObjectStore->Id(), aIndexId));
587 }
588 
RenameIndex(IDBObjectStore * const aObjectStore,const int64_t aIndexId,const nsAString & aName)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());
598 
599   MOZ_ALWAYS_TRUE(
600       mBackgroundActor.mVersionChangeBackgroundActor->SendRenameIndex(
601           aObjectStore->Id(), aIndexId, nsString(aName)));
602 }
603 
AbortInternal(const nsresult aAbortCode,RefPtr<DOMException> aError)604 void IDBTransaction::AbortInternal(const nsresult aAbortCode,
605                                    RefPtr<DOMException> aError) {
606   AssertIsOnOwningThread();
607   MOZ_ASSERT(NS_FAILED(aAbortCode));
608   MOZ_ASSERT(!IsCommittingOrFinished());
609 
610   const bool isVersionChange = mMode == Mode::VersionChange;
611   const bool needToSendAbort = mReadyState == ReadyState::Inactive && !mStarted;
612 
613   mAbortCode = aAbortCode;
614   mReadyState = ReadyState::Finished;
615   mError = std::move(aError);
616 
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();
623     }
624 
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().
631 
632     const nsTArray<ObjectStoreSpec>& specArray =
633         mDatabase->Spec()->objectStores();
634 
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);
645           });
646 
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                          }));
654 
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));
662                    });
663     }
664     mDeletedObjectStores.Clear();
665   }
666 
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);
671   }
672 
673   if (isVersionChange) {
674     mDatabase->Close();
675   }
676 }
677 
Abort(IDBRequest * const aRequest)678 void IDBTransaction::Abort(IDBRequest* const aRequest) {
679   AssertIsOnOwningThread();
680   MOZ_ASSERT(aRequest);
681 
682   if (IsCommittingOrFinished()) {
683     // Already started (and maybe finished) the commit or abort so there is
684     // nothing to do here.
685     return;
686   }
687 
688   ErrorResult rv;
689   RefPtr<DOMException> error = aRequest->GetError(rv);
690 
691   // TODO: Do we deliberately ignore rv here? Isn't there a static analysis that
692   // prevents that?
693 
694   AbortInternal(aRequest->GetErrorCode(), std::move(error));
695 }
696 
Abort(const nsresult aErrorCode)697 void IDBTransaction::Abort(const nsresult aErrorCode) {
698   AssertIsOnOwningThread();
699 
700   if (IsCommittingOrFinished()) {
701     // Already started (and maybe finished) the commit or abort so there is
702     // nothing to do here.
703     return;
704   }
705 
706   AbortInternal(aErrorCode, DOMException::Create(aErrorCode));
707 }
708 
709 // Specified by https://w3c.github.io/IndexedDB/#dom-idbtransaction-abort.
Abort(ErrorResult & aRv)710 void IDBTransaction::Abort(ErrorResult& aRv) {
711   AssertIsOnOwningThread();
712 
713   if (IsCommittingOrFinished()) {
714     aRv = NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
715     return;
716   }
717 
718   mReadyState = ReadyState::Inactive;
719 
720   AbortInternal(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR, nullptr);
721 
722   mAbortedByScript.Flip();
723 }
724 
725 // Specified by https://w3c.github.io/IndexedDB/#dom-idbtransaction-commit.
Commit(ErrorResult & aRv)726 void IDBTransaction::Commit(ErrorResult& aRv) {
727   AssertIsOnOwningThread();
728 
729   if (mReadyState != ReadyState::Active || !mNotedActiveTransaction) {
730     aRv = NS_ERROR_DOM_INVALID_STATE_ERR;
731     return;
732   }
733 
734   MOZ_ASSERT(!mSentCommitOrAbort);
735 
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;
742   }
743 
744 #ifdef DEBUG
745   mWasExplicitlyCommitted.Flip();
746 #endif
747 
748   SendCommit(false);
749 }
750 
FireCompleteOrAbortEvents(const nsresult aResult)751 void IDBTransaction::FireCompleteOrAbortEvents(const nsresult aResult) {
752   AssertIsOnOwningThread();
753   MOZ_ASSERT(!mFiredCompleteOrAbort);
754 
755   mReadyState = ReadyState::Finished;
756 
757 #ifdef DEBUG
758   mFiredCompleteOrAbort.Flip();
759 #endif
760 
761   // Make sure we drop the WorkerRef when this function completes.
762   const auto scopeExit = MakeScopeExit([&] { mWorkerRef = nullptr; });
763 
764   RefPtr<Event> event;
765   if (NS_SUCCEEDED(aResult)) {
766     event = CreateGenericEvent(this, nsDependentString(kCompleteEventType),
767                                eDoesNotBubble, eNotCancelable);
768     MOZ_ASSERT(event);
769 
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();
776     }
777 
778     if (!mError && !mAbortedByScript) {
779       mError = DOMException::Create(aResult);
780     }
781 
782     event = CreateGenericEvent(this, nsDependentString(kAbortEventType),
783                                eDoesBubble, eNotCancelable);
784     MOZ_ASSERT(event);
785 
786     if (NS_SUCCEEDED(mAbortCode)) {
787       mAbortCode = aResult;
788     }
789   }
790 
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));
800   }
801 
802   IgnoredErrorResult rv;
803   DispatchEvent(*event, rv);
804   if (rv.Failed()) {
805     NS_WARNING("DispatchEvent failed!");
806   }
807 
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();
817 }
818 
NextObjectStoreId()819 int64_t IDBTransaction::NextObjectStoreId() {
820   AssertIsOnOwningThread();
821   MOZ_ASSERT(Mode::VersionChange == mMode);
822 
823   return mNextObjectStoreId++;
824 }
825 
NextIndexId()826 int64_t IDBTransaction::NextIndexId() {
827   AssertIsOnOwningThread();
828   MOZ_ASSERT(Mode::VersionChange == mMode);
829 
830   return mNextIndexId++;
831 }
832 
InvalidateCursorCaches()833 void IDBTransaction::InvalidateCursorCaches() {
834   AssertIsOnOwningThread();
835 
836   for (const auto& cursor : mCursors) {
837     cursor->InvalidateCachedResponses();
838   }
839 }
840 
RegisterCursor(IDBCursor & aCursor)841 void IDBTransaction::RegisterCursor(IDBCursor& aCursor) {
842   AssertIsOnOwningThread();
843 
844   mCursors.AppendElement(WrapNotNullUnchecked(&aCursor));
845 }
846 
UnregisterCursor(IDBCursor & aCursor)847 void IDBTransaction::UnregisterCursor(IDBCursor& aCursor) {
848   AssertIsOnOwningThread();
849 
850   DebugOnly<bool> removed = mCursors.RemoveElement(&aCursor);
851   MOZ_ASSERT(removed);
852 }
853 
GetParentObject() const854 nsIGlobalObject* IDBTransaction::GetParentObject() const {
855   AssertIsOnOwningThread();
856 
857   return mDatabase->GetParentObject();
858 }
859 
GetMode(ErrorResult & aRv) const860 IDBTransactionMode IDBTransaction::GetMode(ErrorResult& aRv) const {
861   AssertIsOnOwningThread();
862 
863   switch (mMode) {
864     case Mode::ReadOnly:
865       return IDBTransactionMode::Readonly;
866 
867     case Mode::ReadWrite:
868       return IDBTransactionMode::Readwrite;
869 
870     case Mode::ReadWriteFlush:
871       return IDBTransactionMode::Readwriteflush;
872 
873     case Mode::Cleanup:
874       return IDBTransactionMode::Cleanup;
875 
876     case Mode::VersionChange:
877       return IDBTransactionMode::Versionchange;
878 
879     case Mode::Invalid:
880     default:
881       MOZ_CRASH("Bad mode!");
882   }
883 }
884 
GetError() const885 DOMException* IDBTransaction::GetError() const {
886   AssertIsOnOwningThread();
887 
888   return mError;
889 }
890 
ObjectStoreNames() const891 RefPtr<DOMStringList> IDBTransaction::ObjectStoreNames() const {
892   AssertIsOnOwningThread();
893 
894   if (mMode == Mode::VersionChange) {
895     return mDatabase->ObjectStoreNames();
896   }
897 
898   auto list = MakeRefPtr<DOMStringList>();
899   list->StringArray() = mObjectStoreNames.Clone();
900   return list;
901 }
902 
ObjectStore(const nsAString & aName,ErrorResult & aRv)903 RefPtr<IDBObjectStore> IDBTransaction::ObjectStore(const nsAString& aName,
904                                                    ErrorResult& aRv) {
905   AssertIsOnOwningThread();
906 
907   if (IsCommittingOrFinished()) {
908     aRv.ThrowInvalidStateError("Transaction is already committing or done.");
909     return nullptr;
910   }
911 
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;
918           });
919     }
920     return nullptr;
921   }();
922 
923   if (!spec) {
924     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
925     return nullptr;
926   }
927 
928   RefPtr<IDBObjectStore> objectStore;
929 
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;
934       });
935   if (foundIt != mObjectStores.cend()) {
936     objectStore = *foundIt;
937   } else {
938     objectStore = IDBObjectStore::Create(
939         SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, *spec);
940     MOZ_ASSERT(objectStore);
941 
942     mObjectStores.AppendElement(objectStore);
943   }
944 
945   return objectStore;
946 }
947 
NS_IMPL_ADDREF_INHERITED(IDBTransaction,DOMEventTargetHelper)948 NS_IMPL_ADDREF_INHERITED(IDBTransaction, DOMEventTargetHelper)
949 NS_IMPL_RELEASE_INHERITED(IDBTransaction, DOMEventTargetHelper)
950 
951 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBTransaction)
952   NS_INTERFACE_MAP_ENTRY(nsIRunnable)
953 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
954 
955 NS_IMPL_CYCLE_COLLECTION_CLASS(IDBTransaction)
956 
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
964 
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
972 
973 JSObject* IDBTransaction::WrapObject(JSContext* const aCx,
974                                      JS::Handle<JSObject*> aGivenProto) {
975   AssertIsOnOwningThread();
976 
977   return IDBTransaction_Binding::Wrap(aCx, this, std::move(aGivenProto));
978 }
979 
GetEventTargetParent(EventChainPreVisitor & aVisitor)980 void IDBTransaction::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
981   AssertIsOnOwningThread();
982 
983   aVisitor.mCanHandle = true;
984   aVisitor.SetParentTarget(mDatabase, false);
985 }
986 
987 NS_IMETHODIMP
Run()988 IDBTransaction::Run() {
989   AssertIsOnOwningThread();
990 
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.
994 
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;
1001   }
1002 
1003   if (ReadyState::Committing == mReadyState) {
1004     MOZ_ASSERT(mSentCommitOrAbort);
1005     return NS_OK;
1006   }
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;
1012 
1013   CommitIfNotStarted();
1014 
1015   return NS_OK;
1016 }
1017 
CommitIfNotStarted()1018 void IDBTransaction::CommitIfNotStarted() {
1019   AssertIsOnOwningThread();
1020 
1021   MOZ_ASSERT(ReadyState::Inactive == mReadyState);
1022 
1023   // Maybe commit if there were no requests generated.
1024   if (!mStarted) {
1025     MOZ_ASSERT(!mPendingRequestCount);
1026     mReadyState = ReadyState::Finished;
1027 
1028     SendCommit(true);
1029   }
1030 }
1031 
1032 }  // namespace mozilla::dom
1033