tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

IDBTransaction.cpp (32846B)


      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 "ProfilerHelpers.h"
     15 #include "ReportInternalError.h"
     16 #include "ThreadLocal.h"
     17 #include "mozilla/ErrorResult.h"
     18 #include "mozilla/EventDispatcher.h"
     19 #include "mozilla/HoldDropJSObjects.h"
     20 #include "mozilla/ScopeExit.h"
     21 #include "mozilla/dom/DOMException.h"
     22 #include "mozilla/dom/DOMStringList.h"
     23 #include "mozilla/dom/WorkerPrivate.h"
     24 #include "mozilla/dom/WorkerRef.h"
     25 #include "mozilla/ipc/BackgroundChild.h"
     26 #include "nsPIDOMWindow.h"
     27 #include "nsQueryObject.h"
     28 #include "nsServiceManagerUtils.h"
     29 #include "nsTHashtable.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>
     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 
     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 
     75 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>
     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);
     88 }
     89 
     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();
    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 
    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);
    141 
    142  if (mRegistered) {
    143    mDatabase->UnregisterTransaction(*this);
    144 #ifdef DEBUG
    145    mRegistered = false;
    146 #endif
    147  }
    148 
    149  if (HasTransactionChild()) {
    150    if (mMode == Mode::VersionChange) {
    151      mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteMeInternal(
    152          /* aFailedConstructor */ false);
    153    } else {
    154      mBackgroundActor.mNormalBackgroundActor->SendDeleteMeInternal();
    155    }
    156  }
    157  MOZ_ASSERT(!HasTransactionChild(),
    158             "SendDeleteMeInternal should have cleared!");
    159 
    160  mozilla::DropJSObjects(this);
    161 }
    162 
    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);
    174 
    175  const nsTArray<nsString> emptyObjectStoreNames;
    176 
    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{});
    182 
    183  transaction->NoteActiveTransaction();
    184 
    185  transaction->mBackgroundActor.mVersionChangeBackgroundActor = aActor;
    186  transaction->mNextObjectStoreId = aNextObjectStoreId;
    187  transaction->mNextIndexId = aNextIndexId;
    188 
    189  aDatabase->RegisterTransaction(*transaction);
    190  transaction->mRegistered = true;
    191 
    192  return transaction;
    193 }
    194 
    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);
    205 
    206  auto transaction = MakeSafeRefPtr<IDBTransaction>(
    207      aDatabase, aObjectStoreNames, aMode, aDurability,
    208      JSCallingLocation::Get(aCx), CreatedFromFactoryFunction{});
    209 
    210  if (!NS_IsMainThread()) {
    211    WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate();
    212    MOZ_ASSERT(workerPrivate);
    213 
    214    workerPrivate->AssertIsOnWorkerThread();
    215 
    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);
    224          }
    225        });
    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;
    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
    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 
    259 void IDBTransaction::AssertIsOnOwningThread() const {
    260  MOZ_ASSERT(mDatabase);
    261  mDatabase->AssertIsOnOwningThread();
    262 }
    263 
    264 #endif  // DEBUG
    265 
    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 
    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([this, actor, &aParams](auto& transactionChild) {
    288    transactionChild.SendPBackgroundIDBRequestConstructor(
    289        actor, NextRequestId(), aParams);
    290  });
    291 
    292  // Balanced in BackgroundRequestChild::Recv__delete__().
    293  OnNewRequest();
    294 
    295  return actor;
    296 }
    297 
    298 void IDBTransaction::OpenCursor(PBackgroundIDBCursorChild& aBackgroundActor,
    299                                const OpenCursorParams& aParams) {
    300  AssertIsOnOwningThread();
    301  MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
    302 
    303  DoWithTransactionChild([this, &aBackgroundActor, &aParams](auto& actor) {
    304    actor.SendPBackgroundIDBCursorConstructor(&aBackgroundActor,
    305                                              NextRequestId(), aParams);
    306  });
    307 
    308  // Balanced in BackgroundCursorChild::RecvResponse().
    309  OnNewRequest();
    310 }
    311 
    312 void IDBTransaction::RefreshSpec(const bool aMayDelete) {
    313  AssertIsOnOwningThread();
    314 
    315  for (auto& objectStore : mObjectStores) {
    316    objectStore->RefreshSpec(aMayDelete);
    317  }
    318 
    319  for (auto& objectStore : mDeletedObjectStores) {
    320    objectStore->RefreshSpec(false);
    321  }
    322 }
    323 
    324 void IDBTransaction::OnNewRequest() {
    325  AssertIsOnOwningThread();
    326 
    327  if (!mPendingRequestCount) {
    328    MOZ_ASSERT(ReadyState::Active == mReadyState);
    329    mStarted.Flip();
    330  }
    331 
    332  ++mPendingRequestCount;
    333 }
    334 
    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);
    341 
    342  --mPendingRequestCount;
    343 
    344  if (!mPendingRequestCount) {
    345    if (mSentCommitOrAbort) {
    346      return;
    347    }
    348 
    349    if (aRequestCompletedSuccessfully) {
    350      if (mReadyState == ReadyState::Inactive) {
    351        mReadyState = ReadyState::Committing;
    352      }
    353 
    354      if (NS_SUCCEEDED(mAbortCode)) {
    355        SendCommit(true);
    356      } else {
    357        SendAbort(mAbortCode);
    358      }
    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());
    367    }
    368  }
    369 }
    370 
    371 void IDBTransaction::SendCommit(const bool aAutoCommit) {
    372  AssertIsOnOwningThread();
    373  MOZ_ASSERT(NS_SUCCEEDED(mAbortCode));
    374  MOZ_ASSERT(IsCommittingOrFinished());
    375 
    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();
    379 
    380  IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
    381      "Committing transaction (%s)", "IDBTransaction commit (%s)",
    382      LoggingSerialNumber(), requestSerialNumber,
    383      aAutoCommit ? "automatically" : "explicitly");
    384 
    385  const int64_t requestId = NextRequestId();
    386 
    387  const auto lastRequestId = [this, aAutoCommit,
    388                              requestId]() -> Maybe<decltype(requestId)> {
    389    if (aAutoCommit) {
    390      return Nothing();
    391    }
    392 
    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;
    407 
    408    return Some(requestId
    409                    ? (requestId - (dispatchingEventForThisTransaction ? 0 : 1))
    410                    : 0);
    411  }();
    412 
    413  DoWithTransactionChild(
    414      [lastRequestId](auto& actor) { actor.SendCommit(lastRequestId); });
    415 
    416  mSentCommitOrAbort.Flip();
    417 }
    418 
    419 void IDBTransaction::SendAbort(const nsresult aResultCode) {
    420  AssertIsOnOwningThread();
    421  MOZ_ASSERT(NS_FAILED(aResultCode));
    422  MOZ_ASSERT(IsCommittingOrFinished());
    423 
    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();
    427 
    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));
    432 
    433  DoWithTransactionChild(
    434      [aResultCode](auto& actor) { actor.SendAbort(aResultCode); });
    435 
    436  mSentCommitOrAbort.Flip();
    437 }
    438 
    439 void IDBTransaction::NoteActiveTransaction() {
    440  AssertIsOnOwningThread();
    441  MOZ_ASSERT(!mNotedActiveTransaction);
    442 
    443  mDatabase->NoteActiveTransaction();
    444  mNotedActiveTransaction = true;
    445 }
    446 
    447 void IDBTransaction::MaybeNoteInactiveTransaction() {
    448  AssertIsOnOwningThread();
    449 
    450  if (mNotedActiveTransaction) {
    451    mDatabase->NoteInactiveTransaction();
    452    mNotedActiveTransaction = false;
    453  }
    454 }
    455 
    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());
    463 
    464 #ifdef DEBUG
    465  {
    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;
    474                    });
    475    MOZ_ASSERT(objectStoreNameDoesNotYetExist);
    476  }
    477 #endif
    478 
    479  MOZ_ALWAYS_TRUE(
    480      mBackgroundActor.mVersionChangeBackgroundActor->SendCreateObjectStore(
    481          aSpec.metadata()));
    482 
    483  RefPtr<IDBObjectStore> objectStore = IDBObjectStore::Create(
    484      SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, aSpec);
    485  MOZ_ASSERT(objectStore);
    486 
    487  mObjectStores.AppendElement(objectStore);
    488 
    489  return objectStore;
    490 }
    491 
    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());
    498 
    499  MOZ_ALWAYS_TRUE(
    500      mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteObjectStore(
    501          aObjectStoreId));
    502 
    503  const auto foundIt =
    504      std::find_if(mObjectStores.begin(), mObjectStores.end(),
    505                   [aObjectStoreId](const auto& objectStore) {
    506                     return objectStore->Id() == aObjectStoreId;
    507                   });
    508  if (foundIt != mObjectStores.end()) {
    509    auto& objectStore = *foundIt;
    510    objectStore->NoteDeletion();
    511 
    512    RefPtr<IDBObjectStore>* deletedObjectStore =
    513        mDeletedObjectStores.AppendElement();
    514    deletedObjectStore->swap(objectStore);
    515 
    516    mObjectStores.RemoveElementAt(foundIt);
    517  }
    518 }
    519 
    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());
    527 
    528  MOZ_ALWAYS_TRUE(
    529      mBackgroundActor.mVersionChangeBackgroundActor->SendRenameObjectStore(
    530          aObjectStoreId, nsString(aName)));
    531 }
    532 
    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());
    542 
    543  MOZ_ALWAYS_TRUE(
    544      mBackgroundActor.mVersionChangeBackgroundActor->SendCreateIndex(
    545          aObjectStore->Id(), aMetadata));
    546 }
    547 
    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());
    556 
    557  MOZ_ALWAYS_TRUE(
    558      mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteIndex(
    559          aObjectStore->Id(), aIndexId));
    560 }
    561 
    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());
    571 
    572  MOZ_ALWAYS_TRUE(
    573      mBackgroundActor.mVersionChangeBackgroundActor->SendRenameIndex(
    574          aObjectStore->Id(), aIndexId, nsString(aName)));
    575 }
    576 
    577 void IDBTransaction::AbortInternal(const nsresult aAbortCode,
    578                                   RefPtr<DOMException> aError) {
    579  AssertIsOnOwningThread();
    580  MOZ_ASSERT(NS_FAILED(aAbortCode));
    581  MOZ_ASSERT(!IsCommittingOrFinished());
    582 
    583  const bool isVersionChange = mMode == Mode::VersionChange;
    584  const bool needToSendAbort = !mStarted;
    585 
    586  mAbortCode = aAbortCode;
    587  mReadyState = ReadyState::Finished;
    588  mError = std::move(aError);
    589 
    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();
    596    }
    597 
    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().
    604 
    605    const nsTArray<ObjectStoreSpec>& specArray =
    606        mDatabase->Spec()->objectStores();
    607 
    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);
    618          });
    619 
    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                         }));
    627 
    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));
    635                   });
    636    }
    637    mDeletedObjectStores.Clear();
    638  }
    639 
    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);
    644  }
    645 
    646  if (isVersionChange) {
    647    mDatabase->Close();
    648  }
    649 }
    650 
    651 void IDBTransaction::Abort(IDBRequest* const aRequest) {
    652  AssertIsOnOwningThread();
    653  MOZ_ASSERT(aRequest);
    654 
    655  if (IsCommittingOrFinished()) {
    656    // Already started (and maybe finished) the commit or abort so there is
    657    // nothing to do here.
    658    return;
    659  }
    660 
    661  ErrorResult rv;
    662  RefPtr<DOMException> error = aRequest->GetError(rv);
    663 
    664  // TODO: Do we deliberately ignore rv here? Isn't there a static analysis that
    665  // prevents that?
    666 
    667  AbortInternal(aRequest->GetErrorCode(), std::move(error));
    668 }
    669 
    670 void IDBTransaction::Abort(const nsresult aErrorCode) {
    671  AssertIsOnOwningThread();
    672 
    673  if (IsCommittingOrFinished()) {
    674    // Already started (and maybe finished) the commit or abort so there is
    675    // nothing to do here.
    676    return;
    677  }
    678 
    679  AbortInternal(aErrorCode, DOMException::Create(aErrorCode));
    680 }
    681 
    682 // Specified by https://w3c.github.io/IndexedDB/#dom-idbtransaction-abort.
    683 void IDBTransaction::Abort(ErrorResult& aRv) {
    684  AssertIsOnOwningThread();
    685 
    686  if (IsCommittingOrFinished()) {
    687    aRv = NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
    688    return;
    689  }
    690 
    691  mReadyState = ReadyState::Inactive;
    692 
    693  AbortInternal(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR, nullptr);
    694 
    695  mAbortedByScript.Flip();
    696 }
    697 
    698 // Specified by https://w3c.github.io/IndexedDB/#dom-idbtransaction-commit.
    699 void IDBTransaction::Commit(ErrorResult& aRv) {
    700  AssertIsOnOwningThread();
    701 
    702  if (mReadyState != ReadyState::Active || !mNotedActiveTransaction) {
    703    aRv = NS_ERROR_DOM_INVALID_STATE_ERR;
    704    return;
    705  }
    706 
    707  MOZ_ASSERT(!mSentCommitOrAbort);
    708 
    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;
    715  }
    716 
    717 #ifdef DEBUG
    718  mWasExplicitlyCommitted.Flip();
    719 #endif
    720 
    721  SendCommit(false);
    722 }
    723 
    724 void IDBTransaction::FireCompleteOrAbortEvents(const nsresult aResult) {
    725  AssertIsOnOwningThread();
    726  MOZ_ASSERT(!mFiredCompleteOrAbort);
    727 
    728  mReadyState = ReadyState::Finished;
    729 
    730 #ifdef DEBUG
    731  mFiredCompleteOrAbort.Flip();
    732 #endif
    733 
    734  // Make sure we drop the WorkerRef when this function completes.
    735  const auto scopeExit = MakeScopeExit([&] { mWorkerRef = nullptr; });
    736 
    737  RefPtr<Event> event;
    738  if (NS_SUCCEEDED(aResult)) {
    739    event = CreateGenericEvent(this, nsDependentString(kCompleteEventType),
    740                               eDoesNotBubble, eNotCancelable);
    741    MOZ_ASSERT(event);
    742 
    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();
    749    }
    750 
    751    if (!mError && !mAbortedByScript) {
    752      mError = DOMException::Create(aResult);
    753    }
    754 
    755    event = CreateGenericEvent(this, nsDependentString(kAbortEventType),
    756                               eDoesBubble, eNotCancelable);
    757    MOZ_ASSERT(event);
    758 
    759    if (NS_SUCCEEDED(mAbortCode)) {
    760      mAbortCode = aResult;
    761    }
    762  }
    763 
    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));
    773  }
    774 
    775  IgnoredErrorResult rv;
    776  DispatchEvent(*event, rv);
    777  if (rv.Failed()) {
    778    NS_WARNING("DispatchEvent failed!");
    779  }
    780 
    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();
    790 }
    791 
    792 int64_t IDBTransaction::NextObjectStoreId() {
    793  AssertIsOnOwningThread();
    794  MOZ_ASSERT(Mode::VersionChange == mMode);
    795 
    796  return mNextObjectStoreId++;
    797 }
    798 
    799 int64_t IDBTransaction::NextIndexId() {
    800  AssertIsOnOwningThread();
    801  MOZ_ASSERT(Mode::VersionChange == mMode);
    802 
    803  return mNextIndexId++;
    804 }
    805 
    806 int64_t IDBTransaction::NextRequestId() {
    807  AssertIsOnOwningThread();
    808 
    809  return mNextRequestId++;
    810 }
    811 
    812 void IDBTransaction::InvalidateCursorCaches() {
    813  AssertIsOnOwningThread();
    814 
    815  for (const auto& cursor : mCursors) {
    816    cursor->InvalidateCachedResponses();
    817  }
    818 }
    819 
    820 void IDBTransaction::RegisterCursor(IDBCursor& aCursor) {
    821  AssertIsOnOwningThread();
    822 
    823  mCursors.AppendElement(WrapNotNullUnchecked(&aCursor));
    824 }
    825 
    826 void IDBTransaction::UnregisterCursor(IDBCursor& aCursor) {
    827  AssertIsOnOwningThread();
    828 
    829  DebugOnly<bool> removed = mCursors.RemoveElement(&aCursor);
    830  MOZ_ASSERT(removed);
    831 }
    832 
    833 nsIGlobalObject* IDBTransaction::GetParentObject() const {
    834  AssertIsOnOwningThread();
    835 
    836  return mDatabase->GetParentObject();
    837 }
    838 
    839 IDBTransactionMode IDBTransaction::GetMode(ErrorResult& aRv) const {
    840  AssertIsOnOwningThread();
    841 
    842  switch (mMode) {
    843    case Mode::ReadOnly:
    844      return IDBTransactionMode::Readonly;
    845 
    846    case Mode::ReadWrite:
    847      return IDBTransactionMode::Readwrite;
    848 
    849    case Mode::ReadWriteFlush:
    850      return IDBTransactionMode::Readwriteflush;
    851 
    852    case Mode::Cleanup:
    853      return IDBTransactionMode::Cleanup;
    854 
    855    case Mode::VersionChange:
    856      return IDBTransactionMode::Versionchange;
    857 
    858    case Mode::Invalid:
    859    default:
    860      MOZ_CRASH("Bad mode!");
    861  }
    862 }
    863 
    864 IDBTransactionDurability IDBTransaction::GetDurability(ErrorResult& aRv) const {
    865  AssertIsOnOwningThread();
    866 
    867  switch (mDurability) {
    868    case Durability::Default:
    869      return IDBTransactionDurability::Default;
    870 
    871    case Durability::Strict:
    872      return IDBTransactionDurability::Strict;
    873 
    874    case Durability::Relaxed:
    875      return IDBTransactionDurability::Relaxed;
    876 
    877    default:
    878      MOZ_CRASH("Bad mode!");
    879  }
    880 }
    881 
    882 DOMException* IDBTransaction::GetError() const {
    883  AssertIsOnOwningThread();
    884 
    885  return mError;
    886 }
    887 
    888 RefPtr<DOMStringList> IDBTransaction::ObjectStoreNames() const {
    889  AssertIsOnOwningThread();
    890 
    891  if (mMode == Mode::VersionChange) {
    892    return mDatabase->ObjectStoreNames();
    893  }
    894 
    895  auto list = MakeRefPtr<DOMStringList>();
    896  list->StringArray() = mObjectStoreNames.Clone();
    897  return list;
    898 }
    899 
    900 RefPtr<IDBObjectStore> IDBTransaction::ObjectStore(const nsAString& aName,
    901                                                   ErrorResult& aRv) {
    902  AssertIsOnOwningThread();
    903 
    904  if (IsCommittingOrFinished()) {
    905    aRv.ThrowInvalidStateError("Transaction is already committing or done.");
    906    return nullptr;
    907  }
    908 
    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;
    915          });
    916    }
    917    return nullptr;
    918  }();
    919 
    920  if (!spec) {
    921    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
    922    return nullptr;
    923  }
    924 
    925  RefPtr<IDBObjectStore> objectStore;
    926 
    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;
    931      });
    932  if (foundIt != mObjectStores.cend()) {
    933    objectStore = *foundIt;
    934  } else {
    935    objectStore = IDBObjectStore::Create(
    936        SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, *spec);
    937    MOZ_ASSERT(objectStore);
    938 
    939    mObjectStores.AppendElement(objectStore);
    940  }
    941 
    942  return objectStore;
    943 }
    944 
    945 NS_IMPL_ADDREF_INHERITED(IDBTransaction, DOMEventTargetHelper)
    946 NS_IMPL_RELEASE_INHERITED(IDBTransaction, DOMEventTargetHelper)
    947 
    948 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBTransaction)
    949  NS_INTERFACE_MAP_ENTRY(nsIRunnable)
    950 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
    951 
    952 NS_IMPL_CYCLE_COLLECTION_CLASS(IDBTransaction)
    953 
    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
    961 
    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
    969 
    970 JSObject* IDBTransaction::WrapObject(JSContext* const aCx,
    971                                     JS::Handle<JSObject*> aGivenProto) {
    972  AssertIsOnOwningThread();
    973 
    974  return IDBTransaction_Binding::Wrap(aCx, this, std::move(aGivenProto));
    975 }
    976 
    977 void IDBTransaction::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
    978  AssertIsOnOwningThread();
    979 
    980  aVisitor.mCanHandle = true;
    981  aVisitor.SetParentTarget(mDatabase, false);
    982 }
    983 
    984 NS_IMETHODIMP
    985 IDBTransaction::Run() {
    986  AssertIsOnOwningThread();
    987 
    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.
    991 
    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;
    998  }
    999 
   1000  if (ReadyState::Committing == mReadyState) {
   1001    MOZ_ASSERT(mSentCommitOrAbort);
   1002    return NS_OK;
   1003  }
   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;
   1009 
   1010  CommitIfNotStarted();
   1011 
   1012  return NS_OK;
   1013 }
   1014 
   1015 void IDBTransaction::CommitIfNotStarted() {
   1016  AssertIsOnOwningThread();
   1017 
   1018  MOZ_ASSERT(ReadyState::Inactive == mReadyState);
   1019 
   1020  // Maybe commit if there were no requests generated.
   1021  if (!mStarted) {
   1022    MOZ_ASSERT(!mPendingRequestCount);
   1023    mReadyState = ReadyState::Finished;
   1024 
   1025    SendCommit(true);
   1026  }
   1027 }
   1028 
   1029 }  // namespace mozilla::dom