Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/indexedDB/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 32 kB image not shown  

Quelle  IDBTransaction.cpp   Sprache: 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,
                               JSCallingLocation&& aCallerLocation,
                               CreatedFromFactoryFunction /*aDummy*/)
    : DOMEventTargetHelper(aDatabase),
      mDatabase(aDatabase),
      mObjectStoreNames(aObjectStoreNames.Clone()),
      mLoggingSerialNumber(GetIndexedDBThreadLocal()->NextTransactionSN(aMode)),
      mNextObjectStoreId(0),
      mNextIndexId(0),
      mNextRequestId(0),
      mAbortCode(NS_OK),
      mPendingRequestCount(0),
      mCallerLocation(std::move(aCallerLocation)),
      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;

  // XXX: What should we have as durability hint here?
  auto transaction = MakeSafeRefPtr<IDBTransaction>(
      aDatabase, emptyObjectStoreNames, Mode::VersionChange,
      Durability::Default, JSCallingLocation(aOpenRequest->GetCallerLocation()),
      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);

  auto transaction = MakeSafeRefPtr<IDBTransaction>(
      aDatabase, aObjectStoreNames, aMode, aDurability,
      JSCallingLocation::Get(aCx), 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;
  }
}

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;
  }

  autoconst 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

Messung V0.5
C=92 H=98 G=94

¤ Dauer der Verarbeitung: 0.8 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.