mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			1050 lines
		
	
	
	
		
			33 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1050 lines
		
	
	
	
		
			33 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
#include "IDBTransaction.h"
 | 
						|
 | 
						|
#include "BackgroundChildImpl.h"
 | 
						|
#include "IDBDatabase.h"
 | 
						|
#include "IDBEvents.h"
 | 
						|
#include "IDBObjectStore.h"
 | 
						|
#include "IDBRequest.h"
 | 
						|
#include "mozilla/ErrorResult.h"
 | 
						|
#include "mozilla/EventDispatcher.h"
 | 
						|
#include "mozilla/HoldDropJSObjects.h"
 | 
						|
#include "mozilla/dom/DOMException.h"
 | 
						|
#include "mozilla/dom/DOMStringList.h"
 | 
						|
#include "mozilla/dom/WorkerRef.h"
 | 
						|
#include "mozilla/dom/WorkerPrivate.h"
 | 
						|
#include "mozilla/ipc/BackgroundChild.h"
 | 
						|
#include "mozilla/ScopeExit.h"
 | 
						|
#include "nsPIDOMWindow.h"
 | 
						|
#include "nsQueryObject.h"
 | 
						|
#include "nsServiceManagerUtils.h"
 | 
						|
#include "nsTHashtable.h"
 | 
						|
#include "ProfilerHelpers.h"
 | 
						|
#include "ReportInternalError.h"
 | 
						|
#include "ThreadLocal.h"
 | 
						|
 | 
						|
// Include this last to avoid path problems on Windows.
 | 
						|
#include "ActorsChild.h"
 | 
						|
 | 
						|
namespace {
 | 
						|
using namespace mozilla::dom::indexedDB;
 | 
						|
using namespace mozilla::ipc;
 | 
						|
 | 
						|
// TODO: Move this to xpcom/ds.
 | 
						|
template <typename T, typename Range, typename Transformation>
 | 
						|
nsTHashtable<T> TransformToHashtable(const Range& aRange,
 | 
						|
                                     const Transformation& aTransformation) {
 | 
						|
  // TODO: Determining the size of the range is not syntactically necessary (and
 | 
						|
  // requires random access iterators if expressed this way). It is a
 | 
						|
  // performance optimization. We could resort to std::distance to support any
 | 
						|
  // iterator category, but this would lead to a double iteration of the range
 | 
						|
  // in case of non-random-access iterators. It is hard to determine in general
 | 
						|
  // if double iteration or reallocation is worse.
 | 
						|
  auto res = nsTHashtable<T>(aRange.cend() - aRange.cbegin());
 | 
						|
  // TOOD: std::transform could be used if nsTHashtable had an insert_iterator,
 | 
						|
  // and this would also allow a more generic version not depending on
 | 
						|
  // nsTHashtable at all.
 | 
						|
  for (const auto& item : aRange) {
 | 
						|
    res.PutEntry(aTransformation(item));
 | 
						|
  }
 | 
						|
  return res;
 | 
						|
}
 | 
						|
 | 
						|
ThreadLocal* GetIndexedDBThreadLocal() {
 | 
						|
  BackgroundChildImpl::ThreadLocal* const threadLocal =
 | 
						|
      BackgroundChildImpl::GetThreadLocalForCurrentThread();
 | 
						|
  MOZ_ASSERT(threadLocal);
 | 
						|
 | 
						|
  ThreadLocal* idbThreadLocal = threadLocal->mIndexedDBThreadLocal.get();
 | 
						|
  MOZ_ASSERT(idbThreadLocal);
 | 
						|
 | 
						|
  return idbThreadLocal;
 | 
						|
}
 | 
						|
}  // namespace
 | 
						|
 | 
						|
namespace mozilla::dom {
 | 
						|
 | 
						|
using namespace mozilla::dom::indexedDB;
 | 
						|
using namespace mozilla::ipc;
 | 
						|
 | 
						|
bool IDBTransaction::HasTransactionChild() const {
 | 
						|
  return (mMode == Mode::VersionChange
 | 
						|
              ? static_cast<void*>(
 | 
						|
                    mBackgroundActor.mVersionChangeBackgroundActor)
 | 
						|
              : mBackgroundActor.mNormalBackgroundActor) != nullptr;
 | 
						|
}
 | 
						|
 | 
						|
template <typename Func>
 | 
						|
auto IDBTransaction::DoWithTransactionChild(const Func& aFunc) const {
 | 
						|
  MOZ_ASSERT(HasTransactionChild());
 | 
						|
  return mMode == Mode::VersionChange
 | 
						|
             ? aFunc(*mBackgroundActor.mVersionChangeBackgroundActor)
 | 
						|
             : aFunc(*mBackgroundActor.mNormalBackgroundActor);
 | 
						|
}
 | 
						|
 | 
						|
IDBTransaction::IDBTransaction(IDBDatabase* const aDatabase,
 | 
						|
                               const nsTArray<nsString>& aObjectStoreNames,
 | 
						|
                               const Mode aMode, const Durability aDurability,
 | 
						|
                               nsString aFilename, const uint32_t aLineNo,
 | 
						|
                               const uint32_t aColumn,
 | 
						|
                               CreatedFromFactoryFunction /*aDummy*/)
 | 
						|
    : DOMEventTargetHelper(aDatabase),
 | 
						|
      mDatabase(aDatabase),
 | 
						|
      mObjectStoreNames(aObjectStoreNames.Clone()),
 | 
						|
      mLoggingSerialNumber(GetIndexedDBThreadLocal()->NextTransactionSN(aMode)),
 | 
						|
      mNextObjectStoreId(0),
 | 
						|
      mNextIndexId(0),
 | 
						|
      mNextRequestId(0),
 | 
						|
      mAbortCode(NS_OK),
 | 
						|
      mPendingRequestCount(0),
 | 
						|
      mFilename(std::move(aFilename)),
 | 
						|
      mLineNo(aLineNo),
 | 
						|
      mColumn(aColumn),
 | 
						|
      mMode(aMode),
 | 
						|
      mDurability(aDurability),
 | 
						|
      mRegistered(false),
 | 
						|
      mNotedActiveTransaction(false) {
 | 
						|
  MOZ_ASSERT(aDatabase);
 | 
						|
  aDatabase->AssertIsOnOwningThread();
 | 
						|
 | 
						|
  // This also nulls mBackgroundActor.mVersionChangeBackgroundActor, so this is
 | 
						|
  // valid also for mMode == Mode::VersionChange.
 | 
						|
  mBackgroundActor.mNormalBackgroundActor = nullptr;
 | 
						|
 | 
						|
#ifdef DEBUG
 | 
						|
  if (!aObjectStoreNames.IsEmpty()) {
 | 
						|
    // Make sure the array is properly sorted.
 | 
						|
    MOZ_ASSERT(
 | 
						|
        std::is_sorted(aObjectStoreNames.cbegin(), aObjectStoreNames.cend()));
 | 
						|
 | 
						|
    // Make sure there are no duplicates in our objectStore names.
 | 
						|
    MOZ_ASSERT(aObjectStoreNames.cend() ==
 | 
						|
               std::adjacent_find(aObjectStoreNames.cbegin(),
 | 
						|
                                  aObjectStoreNames.cend()));
 | 
						|
  }
 | 
						|
#endif
 | 
						|
 | 
						|
  mozilla::HoldJSObjects(this);
 | 
						|
}
 | 
						|
 | 
						|
IDBTransaction::~IDBTransaction() {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(!mPendingRequestCount);
 | 
						|
  MOZ_ASSERT(mReadyState != ReadyState::Active);
 | 
						|
  MOZ_ASSERT(mReadyState != ReadyState::Inactive);
 | 
						|
  MOZ_ASSERT(mReadyState != ReadyState::Committing);
 | 
						|
  MOZ_ASSERT(!mNotedActiveTransaction);
 | 
						|
  MOZ_ASSERT(mSentCommitOrAbort);
 | 
						|
  MOZ_ASSERT_IF(HasTransactionChild(), mFiredCompleteOrAbort);
 | 
						|
 | 
						|
  if (mRegistered) {
 | 
						|
    mDatabase->UnregisterTransaction(*this);
 | 
						|
#ifdef DEBUG
 | 
						|
    mRegistered = false;
 | 
						|
#endif
 | 
						|
  }
 | 
						|
 | 
						|
  if (HasTransactionChild()) {
 | 
						|
    if (mMode == Mode::VersionChange) {
 | 
						|
      mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteMeInternal(
 | 
						|
          /* aFailedConstructor */ false);
 | 
						|
    } else {
 | 
						|
      mBackgroundActor.mNormalBackgroundActor->SendDeleteMeInternal();
 | 
						|
    }
 | 
						|
  }
 | 
						|
  MOZ_ASSERT(!HasTransactionChild(),
 | 
						|
             "SendDeleteMeInternal should have cleared!");
 | 
						|
 | 
						|
  mozilla::DropJSObjects(this);
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
SafeRefPtr<IDBTransaction> IDBTransaction::CreateVersionChange(
 | 
						|
    IDBDatabase* const aDatabase,
 | 
						|
    BackgroundVersionChangeTransactionChild* const aActor,
 | 
						|
    const NotNull<IDBOpenDBRequest*> aOpenRequest,
 | 
						|
    const int64_t aNextObjectStoreId, const int64_t aNextIndexId) {
 | 
						|
  MOZ_ASSERT(aDatabase);
 | 
						|
  aDatabase->AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(aActor);
 | 
						|
  MOZ_ASSERT(aNextObjectStoreId > 0);
 | 
						|
  MOZ_ASSERT(aNextIndexId > 0);
 | 
						|
 | 
						|
  const nsTArray<nsString> emptyObjectStoreNames;
 | 
						|
 | 
						|
  nsString filename;
 | 
						|
  uint32_t lineNo, column;
 | 
						|
  aOpenRequest->GetCallerLocation(filename, &lineNo, &column);
 | 
						|
  // XXX: What should we have as durability hint here?
 | 
						|
  auto transaction = MakeSafeRefPtr<IDBTransaction>(
 | 
						|
      aDatabase, emptyObjectStoreNames, Mode::VersionChange,
 | 
						|
      Durability::Default, std::move(filename), lineNo, column,
 | 
						|
      CreatedFromFactoryFunction{});
 | 
						|
 | 
						|
  transaction->NoteActiveTransaction();
 | 
						|
 | 
						|
  transaction->mBackgroundActor.mVersionChangeBackgroundActor = aActor;
 | 
						|
  transaction->mNextObjectStoreId = aNextObjectStoreId;
 | 
						|
  transaction->mNextIndexId = aNextIndexId;
 | 
						|
 | 
						|
  aDatabase->RegisterTransaction(*transaction);
 | 
						|
  transaction->mRegistered = true;
 | 
						|
 | 
						|
  return transaction;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
SafeRefPtr<IDBTransaction> IDBTransaction::Create(
 | 
						|
    JSContext* const aCx, IDBDatabase* const aDatabase,
 | 
						|
    const nsTArray<nsString>& aObjectStoreNames, const Mode aMode,
 | 
						|
    const Durability aDurability) {
 | 
						|
  MOZ_ASSERT(aDatabase);
 | 
						|
  aDatabase->AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(!aObjectStoreNames.IsEmpty());
 | 
						|
  MOZ_ASSERT(aMode == Mode::ReadOnly || aMode == Mode::ReadWrite ||
 | 
						|
             aMode == Mode::ReadWriteFlush || aMode == Mode::Cleanup);
 | 
						|
 | 
						|
  nsString filename;
 | 
						|
  uint32_t lineNo, column;
 | 
						|
  IDBRequest::CaptureCaller(aCx, filename, &lineNo, &column);
 | 
						|
  auto transaction = MakeSafeRefPtr<IDBTransaction>(
 | 
						|
      aDatabase, aObjectStoreNames, aMode, aDurability, std::move(filename),
 | 
						|
      lineNo, column, CreatedFromFactoryFunction{});
 | 
						|
 | 
						|
  if (!NS_IsMainThread()) {
 | 
						|
    WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate();
 | 
						|
    MOZ_ASSERT(workerPrivate);
 | 
						|
 | 
						|
    workerPrivate->AssertIsOnWorkerThread();
 | 
						|
 | 
						|
    RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
 | 
						|
        workerPrivate, "IDBTransaction",
 | 
						|
        [transaction = AsRefPtr(transaction.clonePtr())]() {
 | 
						|
          transaction->AssertIsOnOwningThread();
 | 
						|
          if (!transaction->IsCommittingOrFinished()) {
 | 
						|
            IDB_REPORT_INTERNAL_ERR();
 | 
						|
            transaction->AbortInternal(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
 | 
						|
                                       nullptr);
 | 
						|
          }
 | 
						|
        });
 | 
						|
    if (NS_WARN_IF(!workerRef)) {
 | 
						|
#ifdef DEBUG
 | 
						|
      // Silence the destructor assertions if we never made this object live.
 | 
						|
      transaction->mReadyState = ReadyState::Finished;
 | 
						|
      transaction->mSentCommitOrAbort.Flip();
 | 
						|
#endif
 | 
						|
      return nullptr;
 | 
						|
    }
 | 
						|
 | 
						|
    transaction->mWorkerRef = std::move(workerRef);
 | 
						|
  }
 | 
						|
 | 
						|
  nsCOMPtr<nsIRunnable> runnable =
 | 
						|
      do_QueryObject(transaction.unsafeGetRawPtr());
 | 
						|
  nsContentUtils::AddPendingIDBTransaction(runnable.forget());
 | 
						|
 | 
						|
  aDatabase->RegisterTransaction(*transaction);
 | 
						|
  transaction->mRegistered = true;
 | 
						|
 | 
						|
  return transaction;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
Maybe<IDBTransaction&> IDBTransaction::MaybeCurrent() {
 | 
						|
  using namespace mozilla::ipc;
 | 
						|
 | 
						|
  MOZ_ASSERT(BackgroundChild::GetForCurrentThread());
 | 
						|
 | 
						|
  return GetIndexedDBThreadLocal()->MaybeCurrentTransactionRef();
 | 
						|
}
 | 
						|
 | 
						|
#ifdef DEBUG
 | 
						|
 | 
						|
void IDBTransaction::AssertIsOnOwningThread() const {
 | 
						|
  MOZ_ASSERT(mDatabase);
 | 
						|
  mDatabase->AssertIsOnOwningThread();
 | 
						|
}
 | 
						|
 | 
						|
#endif  // DEBUG
 | 
						|
 | 
						|
void IDBTransaction::SetBackgroundActor(
 | 
						|
    indexedDB::BackgroundTransactionChild* const aBackgroundActor) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(aBackgroundActor);
 | 
						|
  MOZ_ASSERT(!mBackgroundActor.mNormalBackgroundActor);
 | 
						|
  MOZ_ASSERT(mMode != Mode::VersionChange);
 | 
						|
 | 
						|
  NoteActiveTransaction();
 | 
						|
 | 
						|
  mBackgroundActor.mNormalBackgroundActor = aBackgroundActor;
 | 
						|
}
 | 
						|
 | 
						|
BackgroundRequestChild* IDBTransaction::StartRequest(
 | 
						|
    MovingNotNull<RefPtr<mozilla::dom::IDBRequest> > aRequest,
 | 
						|
    const RequestParams& aParams) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(aParams.type() != RequestParams::T__None);
 | 
						|
 | 
						|
  BackgroundRequestChild* const actor =
 | 
						|
      new BackgroundRequestChild(std::move(aRequest));
 | 
						|
 | 
						|
  DoWithTransactionChild([this, actor, &aParams](auto& transactionChild) {
 | 
						|
    transactionChild.SendPBackgroundIDBRequestConstructor(
 | 
						|
        actor, NextRequestId(), aParams);
 | 
						|
  });
 | 
						|
 | 
						|
  // Balanced in BackgroundRequestChild::Recv__delete__().
 | 
						|
  OnNewRequest();
 | 
						|
 | 
						|
  return actor;
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::OpenCursor(PBackgroundIDBCursorChild& aBackgroundActor,
 | 
						|
                                const OpenCursorParams& aParams) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
 | 
						|
 | 
						|
  DoWithTransactionChild([this, &aBackgroundActor, &aParams](auto& actor) {
 | 
						|
    actor.SendPBackgroundIDBCursorConstructor(&aBackgroundActor,
 | 
						|
                                              NextRequestId(), aParams);
 | 
						|
  });
 | 
						|
 | 
						|
  // Balanced in BackgroundCursorChild::RecvResponse().
 | 
						|
  OnNewRequest();
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::RefreshSpec(const bool aMayDelete) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  for (auto& objectStore : mObjectStores) {
 | 
						|
    objectStore->RefreshSpec(aMayDelete);
 | 
						|
  }
 | 
						|
 | 
						|
  for (auto& objectStore : mDeletedObjectStores) {
 | 
						|
    objectStore->RefreshSpec(false);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::OnNewRequest() {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  if (!mPendingRequestCount) {
 | 
						|
    MOZ_ASSERT(ReadyState::Active == mReadyState);
 | 
						|
    mStarted.Flip();
 | 
						|
  }
 | 
						|
 | 
						|
  ++mPendingRequestCount;
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::OnRequestFinished(
 | 
						|
    const bool aRequestCompletedSuccessfully) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(mReadyState != ReadyState::Active);
 | 
						|
  MOZ_ASSERT_IF(mReadyState == ReadyState::Finished, !NS_SUCCEEDED(mAbortCode));
 | 
						|
  MOZ_ASSERT(mPendingRequestCount);
 | 
						|
 | 
						|
  --mPendingRequestCount;
 | 
						|
 | 
						|
  if (!mPendingRequestCount) {
 | 
						|
    if (mSentCommitOrAbort) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (aRequestCompletedSuccessfully) {
 | 
						|
      if (mReadyState == ReadyState::Inactive) {
 | 
						|
        mReadyState = ReadyState::Committing;
 | 
						|
      }
 | 
						|
 | 
						|
      if (NS_SUCCEEDED(mAbortCode)) {
 | 
						|
        SendCommit(true);
 | 
						|
      } else {
 | 
						|
        SendAbort(mAbortCode);
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      // Don't try to send any more messages to the parent if the request actor
 | 
						|
      // was killed. Set our state accordingly to Finished.
 | 
						|
      mReadyState = ReadyState::Finished;
 | 
						|
      mSentCommitOrAbort.Flip();
 | 
						|
      IDB_LOG_MARK_CHILD_TRANSACTION(
 | 
						|
          "Request actor was killed, transaction will be aborted",
 | 
						|
          "IDBTransaction abort", LoggingSerialNumber());
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::SendCommit(const bool aAutoCommit) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(NS_SUCCEEDED(mAbortCode));
 | 
						|
  MOZ_ASSERT(IsCommittingOrFinished());
 | 
						|
 | 
						|
  // Don't do this in the macro because we always need to increment the serial
 | 
						|
  // number to keep in sync with the parent.
 | 
						|
  const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
 | 
						|
 | 
						|
  IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
 | 
						|
      "Committing transaction (%s)", "IDBTransaction commit (%s)",
 | 
						|
      LoggingSerialNumber(), requestSerialNumber,
 | 
						|
      aAutoCommit ? "automatically" : "explicitly");
 | 
						|
 | 
						|
  const int64_t requestId = NextRequestId();
 | 
						|
 | 
						|
  const auto lastRequestId = [this, aAutoCommit,
 | 
						|
                              requestId]() -> Maybe<decltype(requestId)> {
 | 
						|
    if (aAutoCommit) {
 | 
						|
      return Nothing();
 | 
						|
    }
 | 
						|
 | 
						|
    // In case of an explicit commit, we need to note the id of the last
 | 
						|
    // request to check if a request submitted before the commit request
 | 
						|
    // failed. If we are currently in an event handler for a request on this
 | 
						|
    // transaction, ignore this request. This is used to synchronize the
 | 
						|
    // transaction's committing state with the parent side, to abort the
 | 
						|
    // transaction in case of a request resulting in an error (see
 | 
						|
    // https://w3c.github.io/IndexedDB/#async-execute-request, step 5.3.). With
 | 
						|
    // automatic commit, this is not necessary, as the transaction's state will
 | 
						|
    // only be set to committing after the last request completed.
 | 
						|
    const auto maybeCurrentTransaction =
 | 
						|
        BackgroundChildImpl::GetThreadLocalForCurrentThread()
 | 
						|
            ->mIndexedDBThreadLocal->MaybeCurrentTransactionRef();
 | 
						|
    const bool dispatchingEventForThisTransaction =
 | 
						|
        maybeCurrentTransaction && &maybeCurrentTransaction.ref() == this;
 | 
						|
 | 
						|
    return Some(requestId
 | 
						|
                    ? (requestId - (dispatchingEventForThisTransaction ? 0 : 1))
 | 
						|
                    : 0);
 | 
						|
  }();
 | 
						|
 | 
						|
  DoWithTransactionChild(
 | 
						|
      [lastRequestId](auto& actor) { actor.SendCommit(lastRequestId); });
 | 
						|
 | 
						|
  mSentCommitOrAbort.Flip();
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::SendAbort(const nsresult aResultCode) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(NS_FAILED(aResultCode));
 | 
						|
  MOZ_ASSERT(IsCommittingOrFinished());
 | 
						|
 | 
						|
  // Don't do this in the macro because we always need to increment the serial
 | 
						|
  // number to keep in sync with the parent.
 | 
						|
  const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
 | 
						|
 | 
						|
  IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
 | 
						|
      "Aborting transaction with result 0x%" PRIx32,
 | 
						|
      "IDBTransaction abort (0x%" PRIx32 ")", LoggingSerialNumber(),
 | 
						|
      requestSerialNumber, static_cast<uint32_t>(aResultCode));
 | 
						|
 | 
						|
  DoWithTransactionChild(
 | 
						|
      [aResultCode](auto& actor) { actor.SendAbort(aResultCode); });
 | 
						|
 | 
						|
  mSentCommitOrAbort.Flip();
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::NoteActiveTransaction() {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(!mNotedActiveTransaction);
 | 
						|
 | 
						|
  mDatabase->NoteActiveTransaction();
 | 
						|
  mNotedActiveTransaction = true;
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::MaybeNoteInactiveTransaction() {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  if (mNotedActiveTransaction) {
 | 
						|
    mDatabase->NoteInactiveTransaction();
 | 
						|
    mNotedActiveTransaction = false;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::GetCallerLocation(nsAString& aFilename,
 | 
						|
                                       uint32_t* const aLineNo,
 | 
						|
                                       uint32_t* const aColumn) const {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(aLineNo);
 | 
						|
  MOZ_ASSERT(aColumn);
 | 
						|
 | 
						|
  aFilename = mFilename;
 | 
						|
  *aLineNo = mLineNo;
 | 
						|
  *aColumn = mColumn;
 | 
						|
}
 | 
						|
 | 
						|
RefPtr<IDBObjectStore> IDBTransaction::CreateObjectStore(
 | 
						|
    ObjectStoreSpec& aSpec) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(aSpec.metadata().id());
 | 
						|
  MOZ_ASSERT(Mode::VersionChange == mMode);
 | 
						|
  MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
 | 
						|
  MOZ_ASSERT(IsActive());
 | 
						|
 | 
						|
#ifdef DEBUG
 | 
						|
  {
 | 
						|
    // TODO: Bind name outside of lambda capture as a workaround for GCC 7 bug
 | 
						|
    // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66735.
 | 
						|
    const auto& name = aSpec.metadata().name();
 | 
						|
    // TODO: Use #ifdef and local variable as a workaround for Bug 1583449.
 | 
						|
    const bool objectStoreNameDoesNotYetExist =
 | 
						|
        std::all_of(mObjectStores.cbegin(), mObjectStores.cend(),
 | 
						|
                    [&name](const auto& objectStore) {
 | 
						|
                      return objectStore->Name() != name;
 | 
						|
                    });
 | 
						|
    MOZ_ASSERT(objectStoreNameDoesNotYetExist);
 | 
						|
  }
 | 
						|
#endif
 | 
						|
 | 
						|
  MOZ_ALWAYS_TRUE(
 | 
						|
      mBackgroundActor.mVersionChangeBackgroundActor->SendCreateObjectStore(
 | 
						|
          aSpec.metadata()));
 | 
						|
 | 
						|
  RefPtr<IDBObjectStore> objectStore = IDBObjectStore::Create(
 | 
						|
      SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, aSpec);
 | 
						|
  MOZ_ASSERT(objectStore);
 | 
						|
 | 
						|
  mObjectStores.AppendElement(objectStore);
 | 
						|
 | 
						|
  return objectStore;
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::DeleteObjectStore(const int64_t aObjectStoreId) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(aObjectStoreId);
 | 
						|
  MOZ_ASSERT(Mode::VersionChange == mMode);
 | 
						|
  MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
 | 
						|
  MOZ_ASSERT(IsActive());
 | 
						|
 | 
						|
  MOZ_ALWAYS_TRUE(
 | 
						|
      mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteObjectStore(
 | 
						|
          aObjectStoreId));
 | 
						|
 | 
						|
  const auto foundIt =
 | 
						|
      std::find_if(mObjectStores.begin(), mObjectStores.end(),
 | 
						|
                   [aObjectStoreId](const auto& objectStore) {
 | 
						|
                     return objectStore->Id() == aObjectStoreId;
 | 
						|
                   });
 | 
						|
  if (foundIt != mObjectStores.end()) {
 | 
						|
    auto& objectStore = *foundIt;
 | 
						|
    objectStore->NoteDeletion();
 | 
						|
 | 
						|
    RefPtr<IDBObjectStore>* deletedObjectStore =
 | 
						|
        mDeletedObjectStores.AppendElement();
 | 
						|
    deletedObjectStore->swap(objectStore);
 | 
						|
 | 
						|
    mObjectStores.RemoveElementAt(foundIt);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::RenameObjectStore(const int64_t aObjectStoreId,
 | 
						|
                                       const nsAString& aName) const {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(aObjectStoreId);
 | 
						|
  MOZ_ASSERT(Mode::VersionChange == mMode);
 | 
						|
  MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
 | 
						|
  MOZ_ASSERT(IsActive());
 | 
						|
 | 
						|
  MOZ_ALWAYS_TRUE(
 | 
						|
      mBackgroundActor.mVersionChangeBackgroundActor->SendRenameObjectStore(
 | 
						|
          aObjectStoreId, nsString(aName)));
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::CreateIndex(
 | 
						|
    IDBObjectStore* const aObjectStore,
 | 
						|
    const indexedDB::IndexMetadata& aMetadata) const {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(aObjectStore);
 | 
						|
  MOZ_ASSERT(aMetadata.id());
 | 
						|
  MOZ_ASSERT(Mode::VersionChange == mMode);
 | 
						|
  MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
 | 
						|
  MOZ_ASSERT(IsActive());
 | 
						|
 | 
						|
  MOZ_ALWAYS_TRUE(
 | 
						|
      mBackgroundActor.mVersionChangeBackgroundActor->SendCreateIndex(
 | 
						|
          aObjectStore->Id(), aMetadata));
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::DeleteIndex(IDBObjectStore* const aObjectStore,
 | 
						|
                                 const int64_t aIndexId) const {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(aObjectStore);
 | 
						|
  MOZ_ASSERT(aIndexId);
 | 
						|
  MOZ_ASSERT(Mode::VersionChange == mMode);
 | 
						|
  MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
 | 
						|
  MOZ_ASSERT(IsActive());
 | 
						|
 | 
						|
  MOZ_ALWAYS_TRUE(
 | 
						|
      mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteIndex(
 | 
						|
          aObjectStore->Id(), aIndexId));
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::RenameIndex(IDBObjectStore* const aObjectStore,
 | 
						|
                                 const int64_t aIndexId,
 | 
						|
                                 const nsAString& aName) const {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(aObjectStore);
 | 
						|
  MOZ_ASSERT(aIndexId);
 | 
						|
  MOZ_ASSERT(Mode::VersionChange == mMode);
 | 
						|
  MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor);
 | 
						|
  MOZ_ASSERT(IsActive());
 | 
						|
 | 
						|
  MOZ_ALWAYS_TRUE(
 | 
						|
      mBackgroundActor.mVersionChangeBackgroundActor->SendRenameIndex(
 | 
						|
          aObjectStore->Id(), aIndexId, nsString(aName)));
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::AbortInternal(const nsresult aAbortCode,
 | 
						|
                                   RefPtr<DOMException> aError) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(NS_FAILED(aAbortCode));
 | 
						|
  MOZ_ASSERT(!IsCommittingOrFinished());
 | 
						|
 | 
						|
  const bool isVersionChange = mMode == Mode::VersionChange;
 | 
						|
  const bool needToSendAbort = !mStarted;
 | 
						|
 | 
						|
  mAbortCode = aAbortCode;
 | 
						|
  mReadyState = ReadyState::Finished;
 | 
						|
  mError = std::move(aError);
 | 
						|
 | 
						|
  if (isVersionChange) {
 | 
						|
    // If a version change transaction is aborted, we must revert the world
 | 
						|
    // back to its previous state unless we're being invalidated after the
 | 
						|
    // transaction already completed.
 | 
						|
    if (!mDatabase->IsInvalidated()) {
 | 
						|
      mDatabase->RevertToPreviousState();
 | 
						|
    }
 | 
						|
 | 
						|
    // We do the reversion only for the mObjectStores/mDeletedObjectStores but
 | 
						|
    // not for the mIndexes/mDeletedIndexes of each IDBObjectStore because it's
 | 
						|
    // time-consuming(O(m*n)) and mIndexes/mDeletedIndexes won't be used anymore
 | 
						|
    // in IDBObjectStore::(Create|Delete)Index() and IDBObjectStore::Index() in
 | 
						|
    // which all the executions are returned earlier by
 | 
						|
    // !transaction->IsActive().
 | 
						|
 | 
						|
    const nsTArray<ObjectStoreSpec>& specArray =
 | 
						|
        mDatabase->Spec()->objectStores();
 | 
						|
 | 
						|
    if (specArray.IsEmpty()) {
 | 
						|
      // This case is specially handled as a performance optimization, it is
 | 
						|
      // equivalent to the else block.
 | 
						|
      mObjectStores.Clear();
 | 
						|
    } else {
 | 
						|
      const auto validIds = TransformToHashtable<nsUint64HashKey>(
 | 
						|
          specArray, [](const auto& spec) {
 | 
						|
            const int64_t objectStoreId = spec.metadata().id();
 | 
						|
            MOZ_ASSERT(objectStoreId);
 | 
						|
            return static_cast<uint64_t>(objectStoreId);
 | 
						|
          });
 | 
						|
 | 
						|
      mObjectStores.RemoveLastElements(
 | 
						|
          mObjectStores.end() -
 | 
						|
          std::remove_if(mObjectStores.begin(), mObjectStores.end(),
 | 
						|
                         [&validIds](const auto& objectStore) {
 | 
						|
                           return !validIds.Contains(
 | 
						|
                               uint64_t(objectStore->Id()));
 | 
						|
                         }));
 | 
						|
 | 
						|
      std::copy_if(std::make_move_iterator(mDeletedObjectStores.begin()),
 | 
						|
                   std::make_move_iterator(mDeletedObjectStores.end()),
 | 
						|
                   MakeBackInserter(mObjectStores),
 | 
						|
                   [&validIds](const auto& deletedObjectStore) {
 | 
						|
                     const int64_t objectStoreId = deletedObjectStore->Id();
 | 
						|
                     MOZ_ASSERT(objectStoreId);
 | 
						|
                     return validIds.Contains(uint64_t(objectStoreId));
 | 
						|
                   });
 | 
						|
    }
 | 
						|
    mDeletedObjectStores.Clear();
 | 
						|
  }
 | 
						|
 | 
						|
  // Fire the abort event if there are no outstanding requests. Otherwise the
 | 
						|
  // abort event will be fired when all outstanding requests finish.
 | 
						|
  if (needToSendAbort) {
 | 
						|
    SendAbort(aAbortCode);
 | 
						|
  }
 | 
						|
 | 
						|
  if (isVersionChange) {
 | 
						|
    mDatabase->Close();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::Abort(IDBRequest* const aRequest) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(aRequest);
 | 
						|
 | 
						|
  if (IsCommittingOrFinished()) {
 | 
						|
    // Already started (and maybe finished) the commit or abort so there is
 | 
						|
    // nothing to do here.
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  ErrorResult rv;
 | 
						|
  RefPtr<DOMException> error = aRequest->GetError(rv);
 | 
						|
 | 
						|
  // TODO: Do we deliberately ignore rv here? Isn't there a static analysis that
 | 
						|
  // prevents that?
 | 
						|
 | 
						|
  AbortInternal(aRequest->GetErrorCode(), std::move(error));
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::Abort(const nsresult aErrorCode) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  if (IsCommittingOrFinished()) {
 | 
						|
    // Already started (and maybe finished) the commit or abort so there is
 | 
						|
    // nothing to do here.
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  AbortInternal(aErrorCode, DOMException::Create(aErrorCode));
 | 
						|
}
 | 
						|
 | 
						|
// Specified by https://w3c.github.io/IndexedDB/#dom-idbtransaction-abort.
 | 
						|
void IDBTransaction::Abort(ErrorResult& aRv) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  if (IsCommittingOrFinished()) {
 | 
						|
    aRv = NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  mReadyState = ReadyState::Inactive;
 | 
						|
 | 
						|
  AbortInternal(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR, nullptr);
 | 
						|
 | 
						|
  mAbortedByScript.Flip();
 | 
						|
}
 | 
						|
 | 
						|
// Specified by https://w3c.github.io/IndexedDB/#dom-idbtransaction-commit.
 | 
						|
void IDBTransaction::Commit(ErrorResult& aRv) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  if (mReadyState != ReadyState::Active || !mNotedActiveTransaction) {
 | 
						|
    aRv = NS_ERROR_DOM_INVALID_STATE_ERR;
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(!mSentCommitOrAbort);
 | 
						|
 | 
						|
  MOZ_ASSERT(mReadyState == ReadyState::Active);
 | 
						|
  mReadyState = ReadyState::Committing;
 | 
						|
  if (NS_WARN_IF(NS_FAILED(mAbortCode))) {
 | 
						|
    SendAbort(mAbortCode);
 | 
						|
    aRv = mAbortCode;
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
#ifdef DEBUG
 | 
						|
  mWasExplicitlyCommitted.Flip();
 | 
						|
#endif
 | 
						|
 | 
						|
  SendCommit(false);
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::FireCompleteOrAbortEvents(const nsresult aResult) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(!mFiredCompleteOrAbort);
 | 
						|
 | 
						|
  mReadyState = ReadyState::Finished;
 | 
						|
 | 
						|
#ifdef DEBUG
 | 
						|
  mFiredCompleteOrAbort.Flip();
 | 
						|
#endif
 | 
						|
 | 
						|
  // Make sure we drop the WorkerRef when this function completes.
 | 
						|
  const auto scopeExit = MakeScopeExit([&] { mWorkerRef = nullptr; });
 | 
						|
 | 
						|
  RefPtr<Event> event;
 | 
						|
  if (NS_SUCCEEDED(aResult)) {
 | 
						|
    event = CreateGenericEvent(this, nsDependentString(kCompleteEventType),
 | 
						|
                               eDoesNotBubble, eNotCancelable);
 | 
						|
    MOZ_ASSERT(event);
 | 
						|
 | 
						|
    // If we hit this assertion, it probably means transaction object on the
 | 
						|
    // parent process doesn't propagate error properly.
 | 
						|
    MOZ_ASSERT(NS_SUCCEEDED(mAbortCode));
 | 
						|
  } else {
 | 
						|
    if (aResult == NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR) {
 | 
						|
      mDatabase->SetQuotaExceeded();
 | 
						|
    }
 | 
						|
 | 
						|
    if (!mError && !mAbortedByScript) {
 | 
						|
      mError = DOMException::Create(aResult);
 | 
						|
    }
 | 
						|
 | 
						|
    event = CreateGenericEvent(this, nsDependentString(kAbortEventType),
 | 
						|
                               eDoesBubble, eNotCancelable);
 | 
						|
    MOZ_ASSERT(event);
 | 
						|
 | 
						|
    if (NS_SUCCEEDED(mAbortCode)) {
 | 
						|
      mAbortCode = aResult;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (NS_SUCCEEDED(mAbortCode)) {
 | 
						|
    IDB_LOG_MARK_CHILD_TRANSACTION("Firing 'complete' event",
 | 
						|
                                   "IDBTransaction 'complete' event",
 | 
						|
                                   mLoggingSerialNumber);
 | 
						|
  } else {
 | 
						|
    IDB_LOG_MARK_CHILD_TRANSACTION(
 | 
						|
        "Firing 'abort' event with error 0x%" PRIx32,
 | 
						|
        "IDBTransaction 'abort' event (0x%" PRIx32 ")", mLoggingSerialNumber,
 | 
						|
        static_cast<uint32_t>(mAbortCode));
 | 
						|
  }
 | 
						|
 | 
						|
  IgnoredErrorResult rv;
 | 
						|
  DispatchEvent(*event, rv);
 | 
						|
  if (rv.Failed()) {
 | 
						|
    NS_WARNING("DispatchEvent failed!");
 | 
						|
  }
 | 
						|
 | 
						|
  // Normally, we note inactive transaction here instead of
 | 
						|
  // IDBTransaction::ClearBackgroundActor() because here is the earliest place
 | 
						|
  // to know that it becomes non-blocking to allow the scheduler to start the
 | 
						|
  // preemption as soon as it can.
 | 
						|
  // Note: If the IDBTransaction object is held by the script,
 | 
						|
  // ClearBackgroundActor() will be done in ~IDBTransaction() until garbage
 | 
						|
  // collected after its window is closed which prevents us to preempt its
 | 
						|
  // window immediately after committed.
 | 
						|
  MaybeNoteInactiveTransaction();
 | 
						|
}
 | 
						|
 | 
						|
int64_t IDBTransaction::NextObjectStoreId() {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(Mode::VersionChange == mMode);
 | 
						|
 | 
						|
  return mNextObjectStoreId++;
 | 
						|
}
 | 
						|
 | 
						|
int64_t IDBTransaction::NextIndexId() {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(Mode::VersionChange == mMode);
 | 
						|
 | 
						|
  return mNextIndexId++;
 | 
						|
}
 | 
						|
 | 
						|
int64_t IDBTransaction::NextRequestId() {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  return mNextRequestId++;
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::InvalidateCursorCaches() {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  for (const auto& cursor : mCursors) {
 | 
						|
    cursor->InvalidateCachedResponses();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::RegisterCursor(IDBCursor& aCursor) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  mCursors.AppendElement(WrapNotNullUnchecked(&aCursor));
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::UnregisterCursor(IDBCursor& aCursor) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  DebugOnly<bool> removed = mCursors.RemoveElement(&aCursor);
 | 
						|
  MOZ_ASSERT(removed);
 | 
						|
}
 | 
						|
 | 
						|
nsIGlobalObject* IDBTransaction::GetParentObject() const {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  return mDatabase->GetParentObject();
 | 
						|
}
 | 
						|
 | 
						|
IDBTransactionMode IDBTransaction::GetMode(ErrorResult& aRv) const {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  switch (mMode) {
 | 
						|
    case Mode::ReadOnly:
 | 
						|
      return IDBTransactionMode::Readonly;
 | 
						|
 | 
						|
    case Mode::ReadWrite:
 | 
						|
      return IDBTransactionMode::Readwrite;
 | 
						|
 | 
						|
    case Mode::ReadWriteFlush:
 | 
						|
      return IDBTransactionMode::Readwriteflush;
 | 
						|
 | 
						|
    case Mode::Cleanup:
 | 
						|
      return IDBTransactionMode::Cleanup;
 | 
						|
 | 
						|
    case Mode::VersionChange:
 | 
						|
      return IDBTransactionMode::Versionchange;
 | 
						|
 | 
						|
    case Mode::Invalid:
 | 
						|
    default:
 | 
						|
      MOZ_CRASH("Bad mode!");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
IDBTransactionDurability IDBTransaction::GetDurability(ErrorResult& aRv) const {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  switch (mDurability) {
 | 
						|
    case Durability::Default:
 | 
						|
      return IDBTransactionDurability::Default;
 | 
						|
 | 
						|
    case Durability::Strict:
 | 
						|
      return IDBTransactionDurability::Strict;
 | 
						|
 | 
						|
    case Durability::Relaxed:
 | 
						|
      return IDBTransactionDurability::Relaxed;
 | 
						|
 | 
						|
    default:
 | 
						|
      MOZ_CRASH("Bad mode!");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
DOMException* IDBTransaction::GetError() const {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  return mError;
 | 
						|
}
 | 
						|
 | 
						|
RefPtr<DOMStringList> IDBTransaction::ObjectStoreNames() const {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  if (mMode == Mode::VersionChange) {
 | 
						|
    return mDatabase->ObjectStoreNames();
 | 
						|
  }
 | 
						|
 | 
						|
  auto list = MakeRefPtr<DOMStringList>();
 | 
						|
  list->StringArray() = mObjectStoreNames.Clone();
 | 
						|
  return list;
 | 
						|
}
 | 
						|
 | 
						|
RefPtr<IDBObjectStore> IDBTransaction::ObjectStore(const nsAString& aName,
 | 
						|
                                                   ErrorResult& aRv) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  if (IsCommittingOrFinished()) {
 | 
						|
    aRv.ThrowInvalidStateError("Transaction is already committing or done.");
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  auto* const spec = [this, &aName]() -> ObjectStoreSpec* {
 | 
						|
    if (IDBTransaction::Mode::VersionChange == mMode ||
 | 
						|
        mObjectStoreNames.Contains(aName)) {
 | 
						|
      return mDatabase->LookupModifiableObjectStoreSpec(
 | 
						|
          [&aName](const auto& objectStore) {
 | 
						|
            return objectStore.metadata().name() == aName;
 | 
						|
          });
 | 
						|
    }
 | 
						|
    return nullptr;
 | 
						|
  }();
 | 
						|
 | 
						|
  if (!spec) {
 | 
						|
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<IDBObjectStore> objectStore;
 | 
						|
 | 
						|
  const auto foundIt = std::find_if(
 | 
						|
      mObjectStores.cbegin(), mObjectStores.cend(),
 | 
						|
      [desiredId = spec->metadata().id()](const auto& existingObjectStore) {
 | 
						|
        return existingObjectStore->Id() == desiredId;
 | 
						|
      });
 | 
						|
  if (foundIt != mObjectStores.cend()) {
 | 
						|
    objectStore = *foundIt;
 | 
						|
  } else {
 | 
						|
    objectStore = IDBObjectStore::Create(
 | 
						|
        SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, *spec);
 | 
						|
    MOZ_ASSERT(objectStore);
 | 
						|
 | 
						|
    mObjectStores.AppendElement(objectStore);
 | 
						|
  }
 | 
						|
 | 
						|
  return objectStore;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMPL_ADDREF_INHERITED(IDBTransaction, DOMEventTargetHelper)
 | 
						|
NS_IMPL_RELEASE_INHERITED(IDBTransaction, DOMEventTargetHelper)
 | 
						|
 | 
						|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBTransaction)
 | 
						|
  NS_INTERFACE_MAP_ENTRY(nsIRunnable)
 | 
						|
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 | 
						|
 | 
						|
NS_IMPL_CYCLE_COLLECTION_CLASS(IDBTransaction)
 | 
						|
 | 
						|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBTransaction,
 | 
						|
                                                  DOMEventTargetHelper)
 | 
						|
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDatabase)
 | 
						|
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
 | 
						|
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObjectStores)
 | 
						|
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeletedObjectStores)
 | 
						|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 | 
						|
 | 
						|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBTransaction,
 | 
						|
                                                DOMEventTargetHelper)
 | 
						|
  // Don't unlink mDatabase!
 | 
						|
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
 | 
						|
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mObjectStores)
 | 
						|
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDeletedObjectStores)
 | 
						|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 | 
						|
 | 
						|
JSObject* IDBTransaction::WrapObject(JSContext* const aCx,
 | 
						|
                                     JS::Handle<JSObject*> aGivenProto) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  return IDBTransaction_Binding::Wrap(aCx, this, std::move(aGivenProto));
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  aVisitor.mCanHandle = true;
 | 
						|
  aVisitor.SetParentTarget(mDatabase, false);
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
IDBTransaction::Run() {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  // TODO: Instead of checking for Finished and Committing states here, we could
 | 
						|
  // remove the transaction from the pending IDB transactions list on
 | 
						|
  // abort/commit.
 | 
						|
 | 
						|
  if (ReadyState::Finished == mReadyState) {
 | 
						|
    // There are three cases where mReadyState is set to Finished: In
 | 
						|
    // FileCompleteOrAbortEvents, AbortInternal and in CommitIfNotStarted. We
 | 
						|
    // shouldn't get here after CommitIfNotStarted again.
 | 
						|
    MOZ_ASSERT(mFiredCompleteOrAbort || IsAborted());
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  if (ReadyState::Committing == mReadyState) {
 | 
						|
    MOZ_ASSERT(mSentCommitOrAbort);
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
  // We're back at the event loop, no longer newborn, so
 | 
						|
  // return to Inactive state:
 | 
						|
  // https://w3c.github.io/IndexedDB/#cleanup-indexed-database-transactions.
 | 
						|
  MOZ_ASSERT(ReadyState::Active == mReadyState);
 | 
						|
  mReadyState = ReadyState::Inactive;
 | 
						|
 | 
						|
  CommitIfNotStarted();
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
void IDBTransaction::CommitIfNotStarted() {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
 | 
						|
  MOZ_ASSERT(ReadyState::Inactive == mReadyState);
 | 
						|
 | 
						|
  // Maybe commit if there were no requests generated.
 | 
						|
  if (!mStarted) {
 | 
						|
    MOZ_ASSERT(!mPendingRequestCount);
 | 
						|
    mReadyState = ReadyState::Finished;
 | 
						|
 | 
						|
    SendCommit(true);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace mozilla::dom
 |