Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  ActorsParent.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 "ActorsParent.h"

#include <inttypes.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <cstdint>
#include <functional>
#include <iterator>
#include <new>
#include <numeric>
#include <tuple>
#include <type_traits>
#include <utility>
#include "ActorsParentCommon.h"
#include "CrashAnnotations.h"
#include "DatabaseFileInfo.h"
#include "DatabaseFileManager.h"
#include "DatabaseFileManagerImpl.h"
#include "DBSchema.h"
#include "ErrorList.h"
#include "IDBCursorType.h"
#include "IDBObjectStore.h"
#include "IDBTransaction.h"
#include "IndexedDBCommon.h"
#include "IndexedDatabaseInlines.h"
#include "IndexedDatabaseManager.h"
#include "IndexedDBCipherKeyManager.h"
#include "KeyPath.h"
#include "MainThreadUtils.h"
#include "ProfilerHelpers.h"
#include "ReportInternalError.h"
#include "SafeRefPtr.h"
#include "SchemaUpgrades.h"
#include "chrome/common/ipc_channel.h"
#include "ipc/IPCMessageUtils.h"
#include "js/RootingAPI.h"
#include "js/StructuredClone.h"
#include "js/Value.h"
#include "jsapi.h"
#include "mozIStorageAsyncConnection.h"
#include "mozIStorageConnection.h"
#include "mozIStorageFunction.h"
#include "mozIStorageProgressHandler.h"
#include "mozIStorageService.h"
#include "mozIStorageStatement.h"
#include "mozIStorageValueArray.h"
#include "mozStorageCID.h"
#include "mozStorageHelper.h"
#include "mozilla/Algorithm.h"
#include "mozilla/ArrayAlgorithm.h"
#include "mozilla/ArrayIterator.h"
#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/Casting.h"
#include "mozilla/CondVar.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/InitializedOnce.h"
#include "mozilla/Logging.h"
#include "mozilla/MacroForEach.h"
#include "mozilla/Maybe.h"
#include "mozilla/Monitor.h"
#include "mozilla/Mutex.h"
#include "mozilla/NotNull.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/RefCountType.h"
#include "mozilla/RefCounted.h"
#include "mozilla/RemoteLazyInputStreamParent.h"
#include "mozilla/RemoteLazyInputStreamStorage.h"
#include "mozilla/Result.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/SnappyCompressOutputStream.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/Variant.h"
#include "mozilla/dom/BlobImpl.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/FileBlobImpl.h"
#include "mozilla/dom/FlippedOnce.h"
#include "mozilla/dom/IDBCursorBinding.h"
#include "mozilla/dom/IDBFactory.h"
#include "mozilla/dom/IPCBlob.h"
#include "mozilla/dom/IPCBlobUtils.h"
#include "mozilla/dom/IndexedDatabase.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/dom/PContentParent.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/indexedDB/IDBResult.h"
#include "mozilla/dom/indexedDB/Key.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBCursor.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBCursorParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabase.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBFactory.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryRequestParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBRequest.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBRequestParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBSharedTypes.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBTransactionParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBVersionChangeTransactionParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsParent.h"
#include "mozilla/dom/ipc/IdType.h"
#include "mozilla/dom/quota/Assertions.h"
#include "mozilla/dom/quota/CachingDatabaseConnection.h"
#include "mozilla/dom/quota/CheckedUnsafePtr.h"
#include "mozilla/dom/quota/Client.h"
#include "mozilla/dom/quota/ClientDirectoryLock.h"
#include "mozilla/dom/quota/ClientImpl.h"
#include "mozilla/dom/quota/DebugOnlyMacro.h"
#include "mozilla/dom/quota/DirectoryLock.h"
#include "mozilla/dom/quota/DirectoryLockInlines.h"
#include "mozilla/dom/quota/DecryptingInputStream_impl.h"
#include "mozilla/dom/quota/EncryptingOutputStream_impl.h"
#include "mozilla/dom/quota/ErrorHandling.h"
#include "mozilla/dom/quota/FileStreams.h"
#include "mozilla/dom/quota/OriginScope.h"
#include "mozilla/dom/quota/PersistenceScope.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/dom/quota/PrincipalUtils.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/QuotaObject.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "mozilla/dom/quota/ThreadUtils.h"
#include "mozilla/dom/quota/UniversalDirectoryLock.h"
#include "mozilla/dom/quota/UsageInfo.h"
#include "mozilla/fallible.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/InputStreamParams.h"
#include "mozilla/ipc/PBackgroundParent.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/mozalloc.h"
#include "mozilla/storage/Variant.h"
#include "NotifyUtils.h"
#include "nsBaseHashtable.h"
#include "nsCOMPtr.h"
#include "nsClassHashtable.h"
#include "nsContentUtils.h"
#include "nsTHashMap.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsEscape.h"
#include "nsHashKeys.h"
#include "nsIAsyncInputStream.h"
#include "nsID.h"
#include "nsIDUtils.h"
#include "nsIDirectoryEnumerator.h"
#include "nsIEventTarget.h"
#include "nsIFile.h"
#include "nsIFileProtocolHandler.h"
#include "nsIFileStreams.h"
#include "nsIFileURL.h"
#include "nsIInputStream.h"
#include "nsIOutputStream.h"
#include "nsIProtocolHandler.h"
#include "nsIRunnable.h"
#include "nsISupports.h"
#include "nsISupportsPriority.h"
#include "nsISupportsUtils.h"
#include "nsIThread.h"
#include "nsIThreadInternal.h"
#include "nsITimer.h"
#include "nsIURIMutator.h"
#include "nsIVariant.h"
#include "nsLiteralString.h"
#include "nsNetCID.h"
#include "nsPrintfCString.h"
#include "nsProxyRelease.h"
#include "nsServiceManagerUtils.h"
#include "nsStreamUtils.h"
#include "nsString.h"
#include "nsStringFlags.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "nsTHashSet.h"
#include "nsTHashtable.h"
#include "nsTLiteralString.h"
#include "nsTStringRepr.h"
#include "nsThreadPool.h"
#include "nsThreadUtils.h"
#include "nscore.h"
#include "prinrval.h"
#include "prio.h"
#include "prsystem.h"
#include "prthread.h"
#include "prtime.h"
#include "prtypes.h"
#include "snappy/snappy.h"

struct JSContext;
class JSObject;
template <class T>
class nsPtrHashKey;

#define IDB_DEBUG_LOG(_args) \
  MOZ_LOG(IndexedDatabaseManager::GetLoggingModule(), LogLevel::Debug, _args)

#if defined(MOZ_WIDGET_ANDROID)
#  define IDB_MOBILE
#endif

// Helper macros to reduce assertion verbosity
// AUUF == ASSERT_UNREACHABLE_UNLESS_FUZZING
#ifdef DEBUG
#  ifdef FUZZING
#    define NS_AUUF_OR_WARN(...) NS_WARNING(__VA_ARGS__)
#  else
#    define NS_AUUF_OR_WARN(...) MOZ_ASSERT(false, __VA_ARGS__)
#  endif
#  define NS_AUUF_OR_WARN_IF(cond) \
    [](bool aCond) {               \
      if (MOZ_UNLIKELY(aCond)) {   \
        NS_AUUF_OR_WARN(#cond);    \
      }                            \
      return aCond;                \
    }((cond))
#else
#  define NS_AUUF_OR_WARN(...) \
    do {                       \
    } while (false)
#  define NS_AUUF_OR_WARN_IF(cond) static_cast<bool>(cond)
#endif

namespace mozilla {

namespace dom::indexedDB {

using namespace mozilla::dom::quota;
using namespace mozilla::ipc;
using mozilla::dom::quota::Client;

namespace {

class ConnectionPool;
class Database;
struct DatabaseActorInfo;
class DatabaseFile;
class DatabaseLoggingInfo;
class DatabaseMaintenance;
class Factory;
class Maintenance;
class OpenDatabaseOp;
class TransactionBase;
class TransactionDatabaseOperationBase;
class VersionChangeTransaction;
template <bool StatementHasIndexKeyBindings>
struct ValuePopulateResponseHelper;

/*******************************************************************************
 * Constants
 ******************************************************************************/


const int32_t kStorageProgressGranularity = 1000;

// Changing the value here will override the page size of new databases only.
// A journal mode change and VACUUM are needed to change existing databases, so
// the best way to do that is to use the schema version upgrade mechanism.
const uint32_t kSQLitePageSizeOverride =
#ifdef IDB_MOBILE
    2048;
#else
    4096;
#endif

static_assert(kSQLitePageSizeOverride == /* mozStorage default */ 0 ||
                  (kSQLitePageSizeOverride % 2 == 0 &&
                   kSQLitePageSizeOverride >= 512 &&
                   kSQLitePageSizeOverride <= 65536),
              "Must be 0 (disabled) or a power of 2 between 512 and 65536!");

// Set to -1 to use SQLite's default, 0 to disable, or some positive number to
// enforce a custom limit.
const int32_t kMaxWALPages = 5000;  // 20MB on desktop, 10MB on mobile.

// Set to some multiple of the page size to grow the database in larger chunks.
const uint32_t kSQLiteGrowthIncrement = kSQLitePageSizeOverride * 2;

static_assert(kSQLiteGrowthIncrement >= 0 &&
                  kSQLiteGrowthIncrement % kSQLitePageSizeOverride == 0 &&
                  kSQLiteGrowthIncrement < uint32_t(INT32_MAX),
              "Must be 0 (disabled) or a positive multiple of the page size!");

// The maximum number of threads that can be used for database activity at a
// single time. Please keep in sync with the constants in
// test_connection_idle_maintenance*.js tests
const uint32_t kMaxConnectionThreadCount = 20;

static_assert(kMaxConnectionThreadCount, "Must have at least one thread!");

// The maximum number of threads to keep when idle. Until we switch to the STS
// pool, we can reduce the number of idle threads kept around thanks to the
// grace timeout.
const uint32_t kMaxIdleConnectionThreadCount = 1;

static_assert(kMaxConnectionThreadCount >= kMaxIdleConnectionThreadCount,
              "Idle thread limit must be less than total thread limit!");

// The length of time that wanted idle threads will stay alive before being shut
// down.
const uint32_t kConnectionThreadMaxIdleMS = 30 * 1000;  // 30 seconds

// The length of time that excess idle threads will stay alive before being shut
// down.
const uint32_t kConnectionThreadGraceIdleMS = 500;  // 0.5 seconds

// The length of time that database connections will be held open after all
// transactions have completed before doing idle maintenance. Please keep in
// sync with the timeouts in test_connection_idle_maintenance*.js tests
const uint32_t kConnectionIdleMaintenanceMS = 2 * 1000;  // 2 seconds

// The length of time that database connections will be held open after all
// transactions and maintenance have completed.
const uint32_t kConnectionIdleCloseMS = 10 * 1000;  // 10 seconds

#define SAVEPOINT_CLAUSE "SAVEPOINT sp;"_ns

// For efficiency reasons, kEncryptedStreamBlockSize must be a multiple of large
// 4k disk sectors.
static_assert(kEncryptedStreamBlockSize % 4096 == 0);
// Similarly, the file copy buffer size must be a multiple of the encrypted
// block size.
static_assert(kFileCopyBufferSize % kEncryptedStreamBlockSize == 0);

constexpr auto kFileManagerDirectoryNameSuffix = u".files"_ns;
constexpr auto kSQLiteSuffix = u".sqlite"_ns;
constexpr auto kSQLiteJournalSuffix = u".sqlite-journal"_ns;
constexpr auto kSQLiteSHMSuffix = u".sqlite-shm"_ns;
constexpr auto kSQLiteWALSuffix = u".sqlite-wal"_ns;

// The following constants define all names of binding parameters in statements,
// where they are bound by name. This should include all parameter names which
// are bound by name. Binding may be done by index when the statement definition
// and binding are done in the same local scope, and no other reasons prevent
// using the indexes (e.g. multiple statement variants with differing number or
// order of parameters). Neither the styles of specifying parameter names
// (literally vs. via these constants) nor the binding styles (by index vs. by
// name) should not be mixed for the same statement. The decision must be made
// for each statement based on the proximity of statement and binding calls.
constexpr auto kStmtParamNameCurrentKey = "current_key"_ns;
constexpr auto kStmtParamNameRangeBound = "range_bound"_ns;
constexpr auto kStmtParamNameObjectStorePosition = "object_store_position"_ns;
constexpr auto kStmtParamNameLowerKey = "lower_key"_ns;
constexpr auto kStmtParamNameUpperKey = "upper_key"_ns;
constexpr auto kStmtParamNameKey = "key"_ns;
constexpr auto kStmtParamNameObjectStoreId = "object_store_id"_ns;
constexpr auto kStmtParamNameIndexId = "index_id"_ns;
// TODO: Maybe the uses of kStmtParamNameId should be replaced by more
// specific constants such as kStmtParamNameObjectStoreId.
constexpr auto kStmtParamNameId = "id"_ns;
constexpr auto kStmtParamNameValue = "value"_ns;
constexpr auto kStmtParamNameObjectDataKey = "object_data_key"_ns;
constexpr auto kStmtParamNameIndexDataValues = "index_data_values"_ns;
constexpr auto kStmtParamNameData = "data"_ns;
constexpr auto kStmtParamNameFileIds = "file_ids"_ns;
constexpr auto kStmtParamNameValueLocale = "value_locale"_ns;
constexpr auto kStmtParamNameLimit = "limit"_ns;

// The following constants define some names of columns in tables, which are
// referred to in remote locations, e.g. in calls to
// GetBindingClauseForKeyRange.
constexpr auto kColumnNameKey = "key"_ns;
constexpr auto kColumnNameValue = "value"_ns;
constexpr auto kColumnNameAliasSortKey = "sort_column"_ns;

// SQL fragments used at multiple locations.
constexpr auto kOpenLimit = " LIMIT "_ns;

// The deletion marker file is created before RemoveDatabaseFilesAndDirectory
// begins deleting a database. It is removed as the last step of deletion. If a
// deletion marker file is found when initializing the origin, the deletion
// routine is run again to ensure that the database and all of its related files
// are removed. The primary goal of this mechanism is to avoid situations where
// a database has been partially deleted, leading to inconsistent state for the
// origin.
constexpr auto kIdbDeletionMarkerFilePrefix = u"idb-deleting-"_ns;

const uint32_t kDeleteTimeoutMs = 1000;

#ifdef DEBUG

const int32_t kDEBUGThreadPriority = nsISupportsPriority::PRIORITY_NORMAL;
const uint32_t kDEBUGThreadSleepMS = 0;

// Set to a non-zero number to enable debugging of transaction event targets.
// It will cause sleeping after every transaction runnable!
//
// This can be useful for discovering race conditions related to switching to
// another thread. Such races are usually avoided by using MozPromise or
// RunAfterProcessingCurrentEvent. Chaos mode doesn't always help with
// uncovering these issues, and only a precisely targeted sleep call can
// simulate the problem.
const uint32_t kDEBUGTransactionThreadSleepMS = 0;

// Make sure that we notice if we ever accidentally check in a non-zero value.
#  ifdef MOZILLA_OFFICIAL
static_assert(kDEBUGTransactionThreadSleepMS == 0);
#  endif

#endif

/*******************************************************************************
 * Metadata classes
 ******************************************************************************/


// Can be instantiated either on the QuotaManager IO thread or on a
// versionchange transaction thread. These threads can never race so this is
// totally safe.
struct FullIndexMetadata {
  IndexMetadata mCommonMetadata = {0,     nsString(), KeyPath(0), nsCString(),
                                   falsefalse,      false};

  FlippedOnce<false> mDeleted;

  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullIndexMetadata)

 private:
  ~FullIndexMetadata() = default;
};

using IndexTable = nsTHashMap<nsUint64HashKey, SafeRefPtr<FullIndexMetadata>>;

// Can be instantiated either on the QuotaManager IO thread or on a
// versionchange transaction thread. These threads can never race so this is
// totally safe.
struct FullObjectStoreMetadata {
  ObjectStoreMetadata mCommonMetadata;
  IndexTable mIndexes;

  // The auto increment ids are touched on both the background thread and the
  // transaction I/O thread, and they must be kept in sync, so we need a mutex
  // to protect them.
  struct AutoIncrementIds {
    int64_t next;
    int64_t committed;
  };
  DataMutex<AutoIncrementIds> mAutoIncrementIds;

  FlippedOnce<false> mDeleted;

  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullObjectStoreMetadata);

  bool HasLiveIndexes() const;

  FullObjectStoreMetadata(ObjectStoreMetadata aCommonMetadata,
                          const AutoIncrementIds& aAutoIncrementIds)
      : mCommonMetadata{std::move(aCommonMetadata)},
        mAutoIncrementIds{AutoIncrementIds{aAutoIncrementIds},
                          "FullObjectStoreMetadata"} {}

 private:
  ~FullObjectStoreMetadata() = default;
};

using ObjectStoreTable =
    nsTHashMap<nsUint64HashKey, SafeRefPtr<FullObjectStoreMetadata>>;

static_assert(
    std::is_same_v<IndexOrObjectStoreId,
                   std::remove_cv_t<std::remove_reference_t<
                       decltype(std::declval<const ObjectStoreGetParams&>()
                                    .objectStoreId())>>>);
static_assert(
    std::is_same_v<
        IndexOrObjectStoreId,
        std::remove_cv_t<std::remove_reference_t<
            decltype(std::declval<const IndexGetParams&>().objectStoreId())>>>);

struct FullDatabaseMetadata final : AtomicSafeRefCounted<FullDatabaseMetadata> {
  DatabaseMetadata mCommonMetadata;
  nsCString mDatabaseId;
  nsString mFilePath;
  ObjectStoreTable mObjectStores;

  IndexOrObjectStoreId mNextObjectStoreId = 0;
  IndexOrObjectStoreId mNextIndexId = 0;

 public:
  explicit FullDatabaseMetadata(const DatabaseMetadata& aCommonMetadata)
      : mCommonMetadata(aCommonMetadata) {
    AssertIsOnBackgroundThread();
  }

  [[nodiscard]] SafeRefPtr<FullDatabaseMetadata> Duplicate() const;

  MOZ_DECLARE_REFCOUNTED_TYPENAME(FullDatabaseMetadata)
};

template <class Enumerable>
auto MatchMetadataNameOrId(const Enumerable& aEnumerable,
                           IndexOrObjectStoreId aId,
                           Maybe<const nsAString&> aName = Nothing()) {
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(aId);

  const auto it = std::find_if(
      aEnumerable.cbegin(), aEnumerable.cend(),
      [aId, aName](const auto& entry) {
        MOZ_ASSERT(entry.GetKey() != 0);

        const auto& value = entry.GetData();
        MOZ_ASSERT(value);

        return !value->mDeleted &&
               (aId == value->mCommonMetadata.id() ||
                (aName && *aName == value->mCommonMetadata.name()));
      });

  return ToMaybeRef(it != aEnumerable.cend() ? it->GetData().unsafeGetRawPtr()
                                             : nullptr);
}

/*******************************************************************************
 * SQLite functions
 ******************************************************************************/


// WARNING: the hash function used for the database name must not change.
// That's why this function exists separately from mozilla::HashString(), even
// though it is (at the time of writing) equivalent. See bug 780408 and bug
// 940315 for details.
uint32_t HashName(const nsAString& aName) {
  struct Helper {
    static uint32_t RotateBitsLeft32(uint32_t aValue, uint8_t aBits) {
      MOZ_ASSERT(aBits < 32);
      return (aValue << aBits) | (aValue >> (32 - aBits));
    }
  };

  static const uint32_t kGoldenRatioU32 = 0x9e3779b9u;

  return std::accumulate(aName.BeginReading(), aName.EndReading(), uint32_t(0),
                         [](uint32_t hash, char16_t ch) {
                           return kGoldenRatioU32 *
                                  (Helper::RotateBitsLeft32(hash, 5) ^ ch);
                         });
}

nsresult ClampResultCode(nsresult aResultCode) {
  if (NS_SUCCEEDED(aResultCode) ||
      NS_ERROR_GET_MODULE(aResultCode) == NS_ERROR_MODULE_DOM_INDEXEDDB) {
    return aResultCode;
  }

  switch (aResultCode) {
    case NS_ERROR_FILE_NO_DEVICE_SPACE:
      return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
    case NS_ERROR_STORAGE_CONSTRAINT:
      return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
    default:
#ifdef DEBUG
      nsPrintfCString message("Converting non-IndexedDB error code (0x%" PRIX32
                              ") to "
                              "NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR",
                              static_cast<uint32_t>(aResultCode));
      NS_WARNING(message.get());
#else
        ;
#endif
  }

  IDB_REPORT_INTERNAL_ERR();
  return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}

Result<nsCOMPtr<nsIFileURL>, nsresult> GetDatabaseFileURL(
    nsIFile& aDatabaseFile, const int64_t aDirectoryLockId,
    const Maybe<CipherKey>& aMaybeKey) {
  MOZ_ASSERT(aDirectoryLockId >= -1);

  QM_TRY_INSPECT(
      const auto& protocolHandler,
      MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIProtocolHandler>,
                              MOZ_SELECT_OVERLOAD(do_GetService),
                              NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file"));

  QM_TRY_INSPECT(const auto& fileHandler,
                 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIFileProtocolHandler>,
                                         MOZ_SELECT_OVERLOAD(do_QueryInterface),
                                         protocolHandler));

  QM_TRY_INSPECT(const auto& mutator, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                                          nsCOMPtr<nsIURIMutator>, fileHandler,
                                          NewFileURIMutator, &aDatabaseFile));

  // aDirectoryLockId should only be -1 when we are called
  // - from DatabaseFileManager::InitDirectory when the temporary storage
  //    hasn't been initialized yet. At that time, the in-memory objects (e.g.
  //    OriginInfo) are only being created so it doesn't make sense to tunnel
  //    quota information to QuotaVFS to get corresponding QuotaObject instances
  //    for SQLite files.
  // - from DeleteDatabaseOp::LoadPreviousVersion, since this might require
  //   temporarily exceeding the quota limit before the database can be
  //   deleted.
  const nsCString directoryLockIdClause =
      "&directoryLockId="_ns + IntToCString(aDirectoryLockId);

  const auto keyClause = [&aMaybeKey] {
    nsAutoCString keyClause;
    if (aMaybeKey) {
      keyClause.AssignLiteral("&key=");
      for (uint8_t byte : IndexedDBCipherStrategy::SerializeKey(*aMaybeKey)) {
        keyClause.AppendPrintf("%02x", byte);
      }
    }
    return keyClause;
  }();

  QM_TRY_UNWRAP(auto result, ([&mutator, &directoryLockIdClause, &keyClause] {
                  nsCOMPtr<nsIFileURL> result;
                  nsresult rv = NS_MutateURI(mutator)
                                    .SetQuery("cache=private"_ns +
                                              directoryLockIdClause + keyClause)
                                    .Finalize(result);
                  return NS_SUCCEEDED(rv)
                             ? Result<nsCOMPtr<nsIFileURL>, nsresult>{result}
                             : Err(rv);
                }()));

  return result;
}

nsLiteralCString GetDefaultSynchronousMode() {
  return IndexedDatabaseManager::FullSynchronous() ? "FULL"_ns : "NORMAL"_ns;
}

nsresult SetDefaultPragmas(mozIStorageConnection& aConnection) {
  MOZ_ASSERT(!NS_IsMainThread());

  static constexpr auto kBuiltInPragmas =
      // We use foreign keys in DEBUG builds only because there is a performance
      // cost to using them.
      "PRAGMA foreign_keys = "
#ifdef DEBUG
      "ON"
#else
      "OFF"
#endif
      ";"

      // The "INSERT OR REPLACE" statement doesn't fire the update trigger,
      // instead it fires only the insert trigger. This confuses the update
      // refcount function. This behavior changes with enabled recursive
      // triggers, so the statement fires the delete trigger first and then the
      // insert trigger.
      "PRAGMA recursive_triggers = ON;"

      // We aggressively truncate the database file when idle so don't bother
      // overwriting the WAL with 0 during active periods.
      "PRAGMA secure_delete = OFF;"_ns;

  QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(kBuiltInPragmas)));

  QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(nsAutoCString{
      "PRAGMA synchronous = "_ns + GetDefaultSynchronousMode() + ";"_ns})));

#ifndef IDB_MOBILE
  if (kSQLiteGrowthIncrement) {
    // This is just an optimization so ignore the failure if the disk is
    // currently too full.
    QM_TRY(QM_OR_ELSE_WARN_IF(
        // Expression.
        MOZ_TO_RESULT(
            aConnection.SetGrowthIncrement(kSQLiteGrowthIncrement, ""_ns)),
        // Predicate.
        IsSpecificError<NS_ERROR_FILE_TOO_BIG>,
        // Fallback.
        ErrToDefaultOk<>));
  }
#endif  // IDB_MOBILE

  return NS_OK;
}

nsresult SetJournalMode(mozIStorageConnection& aConnection) {
  MOZ_ASSERT(!NS_IsMainThread());

  // Try enabling WAL mode. This can fail in various circumstances so we have to
  // check the results here.
  constexpr auto journalModeQueryStart = "PRAGMA journal_mode = "_ns;
  constexpr auto journalModeWAL = "wal"_ns;

  QM_TRY_INSPECT(const auto& stmt,
                 CreateAndExecuteSingleStepStatement(
                     aConnection, journalModeQueryStart + journalModeWAL));

  QM_TRY_INSPECT(
      const auto& journalMode,
      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, *stmt, GetUTF8String, 0));

  if (journalMode.Equals(journalModeWAL)) {
    // WAL mode successfully enabled. Maybe set limits on its size here.
    if (kMaxWALPages >= 0) {
      QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
          "PRAGMA wal_autocheckpoint = "_ns + IntToCString(kMaxWALPages))));
    }
  } else {
    NS_WARNING("Failed to set WAL mode, falling back to normal journal mode.");
#ifdef IDB_MOBILE
    QM_TRY(MOZ_TO_RESULT(
        aConnection.ExecuteSimpleSQL(journalModeQueryStart + "truncate"_ns)));
#endif
  }

  return NS_OK;
}

Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult> OpenDatabase(
    mozIStorageService& aStorageService, nsIFileURL& aFileURL,
    const uint32_t aTelemetryId = 0) {
  const nsAutoCString telemetryFilename =
      aTelemetryId ? "indexedDB-"_ns + IntToCString(aTelemetryId) +
                         NS_ConvertUTF16toUTF8(kSQLiteSuffix)
                   : nsAutoCString();

  QM_TRY_UNWRAP(auto connection,
                MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                    nsCOMPtr<mozIStorageConnection>, aStorageService,
                    OpenDatabaseWithFileURL, &aFileURL, telemetryFilename,
                    mozIStorageService::CONNECTION_INTERRUPTIBLE));

  return WrapMovingNotNull(std::move(connection));
}

Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
OpenDatabaseAndHandleBusy(mozIStorageService& aStorageService,
                          nsIFileURL& aFileURL,
                          const uint32_t aTelemetryId = 0) {
  MOZ_ASSERT(!NS_IsMainThread());
  MOZ_ASSERT(!IsOnBackgroundThread());

  using ConnectionType = Maybe<MovingNotNull<nsCOMPtr<mozIStorageConnection>>>;

  QM_TRY_UNWRAP(auto connection,
                QM_OR_ELSE_WARN_IF(
                    // Expression
                    OpenDatabase(aStorageService, aFileURL, aTelemetryId)
                        .map([](auto connection) -> ConnectionType {
                          return Some(std::move(connection));
                        }),
                    // Predicate.
                    IsSpecificError<NS_ERROR_STORAGE_BUSY>,
                    // Fallback.
                    ErrToDefaultOk<ConnectionType>));

  if (connection.isNothing()) {
#ifdef DEBUG
    {
      nsCString path;
      MOZ_ALWAYS_SUCCEEDS(aFileURL.GetFileName(path));

      nsPrintfCString message(
          "Received NS_ERROR_STORAGE_BUSY when attempting to open database "
          "'%s', retrying for up to 10 seconds",
          path.get());
      NS_WARNING(message.get());
    }
#endif

    // Another thread must be checkpointing the WAL. Wait up to 10 seconds for
    // that to complete.
    const TimeStamp start = TimeStamp::NowLoRes();

    do {
      PR_Sleep(PR_MillisecondsToInterval(100));

      QM_TRY_UNWRAP(connection,
                    QM_OR_ELSE_WARN_IF(
                        // Expression.
                        OpenDatabase(aStorageService, aFileURL, aTelemetryId)
                            .map([](auto connection) -> ConnectionType {
                              return Some(std::move(connection));
                            }),
                        // Predicate.
                        ([&start](nsresult aValue) {
                          return aValue == NS_ERROR_STORAGE_BUSY &&
                                 TimeStamp::NowLoRes() - start <=
                                     TimeDuration::FromSeconds(10);
                        }),
                        // Fallback.
                        ErrToDefaultOk<ConnectionType>));
    } while (connection.isNothing());
  }

  return connection.extract();
}

// Returns true if a given nsIFile exists and is a directory. Returns false if
// it doesn't exist. Returns an error if it exists, but is not a directory, or
// any other error occurs.
Result<bool, nsresult> ExistsAsDirectory(nsIFile& aDirectory) {
  QM_TRY_INSPECT(const bool& exists,
                 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Exists));

  if (exists) {
    QM_TRY_INSPECT(const bool& isDirectory,
                   MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, IsDirectory));

    QM_TRY(OkIf(isDirectory), Err(NS_ERROR_FAILURE));
  }

  return exists;
}

constexpr nsresult mapNoDeviceSpaceError(nsresult aRv) {
  if (aRv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
    // mozstorage translates SQLITE_FULL to
    // NS_ERROR_FILE_NO_DEVICE_SPACE, which we know better as
    // NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR.
    return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
  }
  return aRv;
}

Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
CreateStorageConnection(nsIFile& aDBFile, nsIFile& aFMDirectory,
                        const nsAString& aName, const nsACString& aOrigin,
                        const int64_t aDirectoryLockId,
                        const uint32_t aTelemetryId,
                        const Maybe<CipherKey>& aMaybeKey) {
  AssertIsOnIOThread();
  MOZ_ASSERT(aDirectoryLockId >= -1);

  AUTO_PROFILER_LABEL("CreateStorageConnection", DOM);

  QM_TRY_INSPECT(const auto& dbFileUrl,
                 GetDatabaseFileURL(aDBFile, aDirectoryLockId, aMaybeKey));

  QM_TRY_INSPECT(const auto& storageService,
                 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
                                         MOZ_SELECT_OVERLOAD(do_GetService),
                                         MOZ_STORAGE_SERVICE_CONTRACTID));

  QM_TRY_UNWRAP(
      auto connection,
      QM_OR_ELSE_WARN_IF(
          // Expression.
          OpenDatabaseAndHandleBusy(*storageService, *dbFileUrl, aTelemetryId)
              .map([](auto connection) -> nsCOMPtr<mozIStorageConnection> {
                return std::move(connection).unwrapBasePtr();
              }),
          // Predicate.
          ([&aName](nsresult aValue) {
            // If we're just opening the database during origin initialization,
            // then we don't want to erase any files. The failure here will fail
            // origin initialization too.
            return IsDatabaseCorruptionError(aValue) && !aName.IsVoid();
          }),
          // Fallback.
          ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));

  if (!connection) {
    // XXX Shouldn't we also update quota usage?

    // Nuke the database file.
    QM_TRY(MOZ_TO_RESULT(aDBFile.Remove(false)));
    QM_TRY_INSPECT(const bool& existsAsDirectory,
                   ExistsAsDirectory(aFMDirectory));

    if (existsAsDirectory) {
      QM_TRY(MOZ_TO_RESULT(aFMDirectory.Remove(true)));
    }

    QM_TRY_UNWRAP(connection, OpenDatabaseAndHandleBusy(
                                  *storageService, *dbFileUrl, aTelemetryId));
  }

  QM_TRY(MOZ_TO_RESULT(SetDefaultPragmas(*connection)));
  QM_TRY(MOZ_TO_RESULT(connection->EnableModule("filesystem"_ns)));

  // Check to make sure that the database schema is correct.
  QM_TRY_INSPECT(const int32_t& schemaVersion,
                 MOZ_TO_RESULT_INVOKE_MEMBER(connection, GetSchemaVersion));

  // Unknown schema will fail origin initialization too.
  QM_TRY(OkIf(schemaVersion || !aName.IsVoid()),
         Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), [](const auto&) {
           IDB_WARNING("Unable to open IndexedDB database, schema is not set!");
         });

  QM_TRY(
      OkIf(schemaVersion <= kSQLiteSchemaVersion),
      Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), [](const auto&) {
        IDB_WARNING("Unable to open IndexedDB database, schema is too high!");
      });

  bool journalModeSet = false;

  if (schemaVersion != kSQLiteSchemaVersion) {
    const bool newDatabase = !schemaVersion;

    if (newDatabase) {
      // Set the page size first.
      const auto sqlitePageSizeOverride =
          aMaybeKey ? 8192 : kSQLitePageSizeOverride;
      if (sqlitePageSizeOverride) {
        QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL(nsPrintfCString(
            "PRAGMA page_size = %" PRIu32 ";", sqlitePageSizeOverride))));
      }

      // We have to set the auto_vacuum mode before opening a transaction.
      QM_TRY((MOZ_TO_RESULT_INVOKE_MEMBER(
                  connection, ExecuteSimpleSQL,
#ifdef IDB_MOBILE
                  // Turn on full auto_vacuum mode to reclaim disk space on
                  // mobile devices (at the cost of some COMMIT speed).
                  "PRAGMA auto_vacuum = FULL;"_ns
#else
                  // Turn on incremental auto_vacuum mode on desktop builds.
                  "PRAGMA auto_vacuum = INCREMENTAL;"_ns
#endif
                  )
                  .mapErr(mapNoDeviceSpaceError)));

      QM_TRY(MOZ_TO_RESULT(SetJournalMode(*connection)));

      journalModeSet = true;
    } else {
#ifdef DEBUG
      // Disable foreign key support while upgrading. This has to be done before
      // starting a transaction.
      MOZ_ALWAYS_SUCCEEDS(
          connection->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns));
#endif
    }

    bool vacuumNeeded = false;

    mozStorageTransaction transaction(
        connection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);

    QM_TRY(MOZ_TO_RESULT(transaction.Start()));

    if (newDatabase) {
      QM_TRY(MOZ_TO_RESULT(CreateTables(*connection)));

#ifdef DEBUG
      {
        QM_TRY_INSPECT(
            const int32_t& schemaVersion,
            MOZ_TO_RESULT_INVOKE_MEMBER(connection, GetSchemaVersion),
            QM_ASSERT_UNREACHABLE);
        MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion);
      }
#endif

      // The parameter names are not used, parameters are bound by index only
      // locally in the same function.
      QM_TRY_INSPECT(
          const auto& stmt,
          MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
              nsCOMPtr<mozIStorageStatement>, connection, CreateStatement,
              "INSERT INTO database (name, origin) "
              "VALUES (:name, :origin)"_ns));

      QM_TRY(MOZ_TO_RESULT(stmt->BindStringByIndex(0, aName)));
      QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByIndex(1, aOrigin)));
      QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
    } else {
      QM_TRY_UNWRAP(vacuumNeeded, MaybeUpgradeSchema(*connection, schemaVersion,
                                                     aFMDirectory, aOrigin));
    }

    QM_TRY(MOZ_TO_RESULT_INVOKE_MEMBER(transaction, Commit)
               .mapErr(mapNoDeviceSpaceError));

#ifdef DEBUG
    if (!newDatabase) {
      // Re-enable foreign key support after doing a foreign key check.
      QM_TRY_INSPECT(const bool& foreignKeyError,
                     CreateAndExecuteSingleStepStatement<
                         SingleStepResult::ReturnNullIfNoResult>(
                         *connection, "PRAGMA foreign_key_check;"_ns),
                     QM_ASSERT_UNREACHABLE);

      MOZ_ASSERT(!foreignKeyError, "Database has inconsisistent foreign keys!");

      MOZ_ALWAYS_SUCCEEDS(
          connection->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns));
    }
#endif

    if (kSQLitePageSizeOverride && !newDatabase) {
      QM_TRY_INSPECT(const auto& stmt,
                     CreateAndExecuteSingleStepStatement(
                         *connection, "PRAGMA page_size;"_ns));

      QM_TRY_INSPECT(const int32_t& pageSize,
                     MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0));
      MOZ_ASSERT(pageSize >= 512 && pageSize <= 65536);

      if (kSQLitePageSizeOverride != uint32_t(pageSize)) {
        // We must not be in WAL journal mode to change the page size.
        QM_TRY(MOZ_TO_RESULT(
            connection->ExecuteSimpleSQL("PRAGMA journal_mode = DELETE;"_ns)));

        QM_TRY_INSPECT(const auto& stmt,
                       CreateAndExecuteSingleStepStatement(
                           *connection, "PRAGMA journal_mode;"_ns));

        QM_TRY_INSPECT(const auto& journalMode,
                       MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, *stmt,
                                                         GetUTF8String, 0));

        if (journalMode.EqualsLiteral("delete")) {
          // Successfully set to rollback journal mode so changing the page size
          // is possible with a VACUUM.
          QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL(nsPrintfCString(
              "PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride))));

          // We will need to VACUUM in order to change the page size.
          vacuumNeeded = true;
        } else {
          NS_WARNING(
              "Failed to set journal_mode for database, unable to "
              "change the page size!");
        }
      }
    }

    if (vacuumNeeded) {
      QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL("VACUUM;"_ns)));
    }

    if (newDatabase || vacuumNeeded) {
      if (journalModeSet) {
        // Make sure we checkpoint to get an accurate file size.
        QM_TRY(MOZ_TO_RESULT(
            connection->ExecuteSimpleSQL("PRAGMA wal_checkpoint(FULL);"_ns)));
      }

      QM_TRY_INSPECT(const int64_t& fileSize,
                     MOZ_TO_RESULT_INVOKE_MEMBER(aDBFile, GetFileSize));
      MOZ_ASSERT(fileSize > 0);

      PRTime vacuumTime = PR_Now();
      MOZ_ASSERT(vacuumTime);

      // The parameter names are not used, parameters are bound by index only
      // locally in the same function.
      QM_TRY_INSPECT(
          const auto& vacuumTimeStmt,
          MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr<mozIStorageStatement>,
                                            connection, CreateStatement,
                                            "UPDATE database "
                                            "SET last_vacuum_time = :time"
                                            ", last_vacuum_size = :size;"_ns));

      QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->BindInt64ByIndex(0, vacuumTime)));
      QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->BindInt64ByIndex(1, fileSize)));
      QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->Execute()));
    }
  }

  if (!journalModeSet) {
    QM_TRY(MOZ_TO_RESULT(SetJournalMode(*connection)));
  }

  return WrapMovingNotNullUnchecked(std::move(connection));
}

nsCOMPtr<nsIFile> GetFileForPath(const nsAString& aPath) {
  MOZ_ASSERT(!aPath.IsEmpty());

  QM_TRY_RETURN(QM_NewLocalFile(aPath), nullptr);
}

Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
GetStorageConnection(nsIFile& aDatabaseFile, const int64_t aDirectoryLockId,
                     const uint32_t aTelemetryId,
                     const Maybe<CipherKey>& aMaybeKey) {
  MOZ_ASSERT(!NS_IsMainThread());
  MOZ_ASSERT(!IsOnBackgroundThread());
  MOZ_ASSERT(aDirectoryLockId >= 0);

  AUTO_PROFILER_LABEL("GetStorageConnection", DOM);

  QM_TRY_INSPECT(const bool& exists,
                 MOZ_TO_RESULT_INVOKE_MEMBER(aDatabaseFile, Exists));

  QM_TRY(OkIf(exists), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
         IDB_REPORT_INTERNAL_ERR_LAMBDA);

  QM_TRY_INSPECT(
      const auto& dbFileUrl,
      GetDatabaseFileURL(aDatabaseFile, aDirectoryLockId, aMaybeKey));

  QM_TRY_INSPECT(const auto& storageService,
                 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
                                         MOZ_SELECT_OVERLOAD(do_GetService),
                                         MOZ_STORAGE_SERVICE_CONTRACTID));

  QM_TRY_UNWRAP(
      nsCOMPtr<mozIStorageConnection> connection,
      OpenDatabaseAndHandleBusy(*storageService, *dbFileUrl, aTelemetryId));

  QM_TRY(MOZ_TO_RESULT(SetDefaultPragmas(*connection)));

  QM_TRY(MOZ_TO_RESULT(SetJournalMode(*connection)));

  return WrapMovingNotNullUnchecked(std::move(connection));
}

Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
GetStorageConnection(const nsAString& aDatabaseFilePath,
                     const int64_t aDirectoryLockId,
                     const uint32_t aTelemetryId,
                     const Maybe<CipherKey>& aMaybeKey) {
  MOZ_ASSERT(!NS_IsMainThread());
  MOZ_ASSERT(!IsOnBackgroundThread());
  MOZ_ASSERT(!aDatabaseFilePath.IsEmpty());
  MOZ_ASSERT(StringEndsWith(aDatabaseFilePath, kSQLiteSuffix));
  MOZ_ASSERT(aDirectoryLockId >= 0);

  nsCOMPtr<nsIFile> dbFile = GetFileForPath(aDatabaseFilePath);

  QM_TRY(OkIf(dbFile), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
         IDB_REPORT_INTERNAL_ERR_LAMBDA);

  return GetStorageConnection(*dbFile, aDirectoryLockId, aTelemetryId,
                              aMaybeKey);
}

/*******************************************************************************
 * ConnectionPool declarations
 ******************************************************************************/


class DatabaseConnection final : public CachingDatabaseConnection {
  friend class ConnectionPool;

  enum class CheckpointMode { Full, Restart, Truncate };

 public:
  class AutoSavepoint;
  class UpdateRefcountFunction;

 private:
  InitializedOnce<const NotNull<SafeRefPtr<DatabaseFileManager>>> mFileManager;
  RefPtr<UpdateRefcountFunction> mUpdateRefcountFunction;
  RefPtr<QuotaObject> mQuotaObject;
  RefPtr<QuotaObject> mJournalQuotaObject;
  IDBTransaction::Durability mLastDurability;
  bool mInReadTransaction;
  bool mInWriteTransaction;

#ifdef DEBUG
  uint32_t mDEBUGSavepointCount;
#endif

 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DatabaseConnection)

  UpdateRefcountFunction* GetUpdateRefcountFunction() const {
    AssertIsOnConnectionThread();

    return mUpdateRefcountFunction;
  }

  nsresult BeginWriteTransaction(const IDBTransaction::Durability aDurability);

  nsresult CommitWriteTransaction();

  void RollbackWriteTransaction();

  void FinishWriteTransaction();

  nsresult StartSavepoint();

  nsresult ReleaseSavepoint();

  nsresult RollbackSavepoint();

  nsresult Checkpoint() {
    AssertIsOnConnectionThread();

    return CheckpointInternal(CheckpointMode::Full);
  }

  void DoIdleProcessing(bool aNeedsCheckpoint,
                        const Atomic<bool>& aInterrupted);

  void Close();

  nsresult DisableQuotaChecks();

  void EnableQuotaChecks();

 private:
  DatabaseConnection(
      MovingNotNull<nsCOMPtr<mozIStorageConnection>> aStorageConnection,
      MovingNotNull<SafeRefPtr<DatabaseFileManager>> aFileManager);

  ~DatabaseConnection();

  nsresult Init();

  nsresult CheckpointInternal(CheckpointMode aMode);

  Result<uint32_t, nsresult> GetFreelistCount(
      CachedStatement& aCachedStatement);

  /**
   * On success, returns whether some pages were freed.
   */

  Result<bool, nsresult> ReclaimFreePagesWhileIdle(
      CachedStatement& aFreelistStatement, CachedStatement& aRollbackStatement,
      uint32_t aFreelistCount, bool aNeedsCheckpoint,
      const Atomic<bool>& aInterrupted);

  Result<int64_t, nsresult> GetFileSize(const nsAString& aPath);
};

class MOZ_STACK_CLASS DatabaseConnection::AutoSavepoint final {
  DatabaseConnection* mConnection;
#ifdef DEBUG
  const TransactionBase* mDEBUGTransaction;
#endif

 public:
  AutoSavepoint();
  ~AutoSavepoint();

  nsresult Start(const TransactionBase& aTransaction);

  nsresult Commit();
};

class DatabaseConnection::UpdateRefcountFunction final
    : public mozIStorageFunction {
  class FileInfoEntry;

  enum class UpdateType { Increment, Decrement };

  DatabaseConnection* const mConnection;
  DatabaseFileManager& mFileManager;
  nsClassHashtable<nsUint64HashKey, FileInfoEntry> mFileInfoEntries;
  nsTHashMap<nsUint64HashKey, NotNull<FileInfoEntry*>> mSavepointEntriesIndex;

  nsTArray<int64_t> mJournalsToCreateBeforeCommit;
  nsTArray<int64_t> mJournalsToRemoveAfterCommit;
  nsTArray<int64_t> mJournalsToRemoveAfterAbort;

  bool mInSavepoint;

 public:
  NS_DECL_ISUPPORTS_ONEVENTTARGET
  NS_DECL_MOZISTORAGEFUNCTION

  UpdateRefcountFunction(DatabaseConnection* aConnection,
                         DatabaseFileManager& aFileManager);

  nsresult WillCommit();

  void DidCommit();

  void DidAbort();

  void StartSavepoint();

  void ReleaseSavepoint();

  void RollbackSavepoint();

  void Reset();

 private:
  ~UpdateRefcountFunction() = default;

  nsresult ProcessValue(mozIStorageValueArray* aValues, int32_t aIndex,
                        UpdateType aUpdateType);

  nsresult CreateJournals();

  nsresult RemoveJournals(const nsTArray<int64_t>& aJournals);
};

class DatabaseConnection::UpdateRefcountFunction::FileInfoEntry final {
  SafeRefPtr<DatabaseFileInfo> mFileInfo;
  int32_t mDelta;
  int32_t mSavepointDelta;

 public:
  explicit FileInfoEntry(SafeRefPtr<DatabaseFileInfo> aFileInfo)
      : mFileInfo(std::move(aFileInfo)), mDelta(0), mSavepointDelta(0) {
    MOZ_COUNT_CTOR(DatabaseConnection::UpdateRefcountFunction::FileInfoEntry);
  }

  void IncDeltas(bool aUpdateSavepointDelta) {
    ++mDelta;
    if (aUpdateSavepointDelta) {
      ++mSavepointDelta;
    }
  }
  void DecDeltas(bool aUpdateSavepointDelta) {
    --mDelta;
    if (aUpdateSavepointDelta) {
      --mSavepointDelta;
    }
  }
  void DecBySavepointDelta() { mDelta -= mSavepointDelta; }
  SafeRefPtr<DatabaseFileInfo> ReleaseFileInfo() {
    return std::move(mFileInfo);
  }
  void MaybeUpdateDBRefs() {
    if (mDelta) {
      mFileInfo->UpdateDBRefs(mDelta);
    }
  }

  int32_t Delta() const { return mDelta; }
  int32_t SavepointDelta() const { return mSavepointDelta; }

  ~FileInfoEntry() {
    MOZ_COUNT_DTOR(DatabaseConnection::UpdateRefcountFunction::FileInfoEntry);
  }
};

class ConnectionPool final {
 public:
  class FinishCallback;

 private:
  class ConnectionRunnable;
  class CloseConnectionRunnable;
  struct DatabaseInfo;
  struct DatabaseCompleteCallback;
  class FinishCallbackWrapper;
  class IdleConnectionRunnable;

#ifdef DEBUG
  class TransactionRunnable;
#endif
  class TransactionInfo;
  struct TransactionInfoPair;

  struct IdleResource {
    TimeStamp mIdleTime;

    IdleResource(const IdleResource& aOther) = delete;
    IdleResource(IdleResource&& aOther) noexcept
        : IdleResource(aOther.mIdleTime) {}
    IdleResource& operator=(const IdleResource& aOther) = delete;
    IdleResource& operator=(IdleResource&& aOther) = delete;

   protected:
    explicit IdleResource(const TimeStamp& aIdleTime);

    ~IdleResource();
  };

  struct IdleDatabaseInfo final : public IdleResource {
    InitializedOnce<const NotNull<DatabaseInfo*>> mDatabaseInfo;

   public:
    explicit IdleDatabaseInfo(DatabaseInfo& aDatabaseInfo);

    IdleDatabaseInfo(const IdleDatabaseInfo& aOther) = delete;
    IdleDatabaseInfo(IdleDatabaseInfo&& aOther) noexcept
        : IdleResource(std::move(aOther)),
          mDatabaseInfo{std::move(aOther.mDatabaseInfo)} {
      MOZ_ASSERT(mDatabaseInfo);

      MOZ_COUNT_CTOR(ConnectionPool::IdleDatabaseInfo);
    }
    IdleDatabaseInfo& operator=(const IdleDatabaseInfo& aOther) = delete;
    IdleDatabaseInfo& operator=(IdleDatabaseInfo&& aOther) = delete;

    ~IdleDatabaseInfo();

    bool operator==(const IdleDatabaseInfo& aOther) const {
      return *mDatabaseInfo == *aOther.mDatabaseInfo;
    }

    bool operator==(const DatabaseInfo* aDatabaseInfo) const {
      return *mDatabaseInfo == aDatabaseInfo;
    }

    bool operator<(const IdleDatabaseInfo& aOther) const {
      return mIdleTime < aOther.mIdleTime;
    }
  };

  struct PerformingIdleMaintenanceDatabaseInfo {
    const NotNull<DatabaseInfo*> mDatabaseInfo;
    RefPtr<IdleConnectionRunnable> mIdleConnectionRunnable;

    PerformingIdleMaintenanceDatabaseInfo(
        DatabaseInfo& aDatabaseInfo,
        RefPtr<IdleConnectionRunnable> aIdleConnectionRunnable);

    PerformingIdleMaintenanceDatabaseInfo(
        const PerformingIdleMaintenanceDatabaseInfo& aOther) = delete;
    PerformingIdleMaintenanceDatabaseInfo(
        PerformingIdleMaintenanceDatabaseInfo&& aOther) noexcept
        : mDatabaseInfo{aOther.mDatabaseInfo},
          mIdleConnectionRunnable{std::move(aOther.mIdleConnectionRunnable)} {
      MOZ_COUNT_CTOR(ConnectionPool::PerformingIdleMaintenanceDatabaseInfo);
    }
    PerformingIdleMaintenanceDatabaseInfo& operator=(
        const PerformingIdleMaintenanceDatabaseInfo& aOther) = delete;
    PerformingIdleMaintenanceDatabaseInfo& operator=(
        PerformingIdleMaintenanceDatabaseInfo&& aOther) = delete;

    ~PerformingIdleMaintenanceDatabaseInfo();

    bool operator==(const DatabaseInfo* aDatabaseInfo) const {
      return mDatabaseInfo == aDatabaseInfo;
    }
  };

  // This mutex guards mDatabases, see below.
  Mutex mDatabasesMutex MOZ_UNANNOTATED;

  nsCOMPtr<nsIThreadPool> mIOTarget;
  nsTArray<IdleDatabaseInfo> mIdleDatabases;
  nsTArray<PerformingIdleMaintenanceDatabaseInfo>
      mDatabasesPerformingIdleMaintenance;
  nsCOMPtr<nsITimer> mIdleTimer;
  TimeStamp mTargetIdleTime;

  // Only modifed on the owning thread, but read on multiple threads. Therefore
  // all modifications and all reads off the owning thread must be protected by
  // mDatabasesMutex.
  nsClassHashtable<nsCStringHashKey, DatabaseInfo> mDatabases;

  nsClassHashtable<nsUint64HashKey, TransactionInfo> mTransactions;
  nsTArray<NotNull<TransactionInfo*>> mQueuedTransactions;

  nsTArray<UniquePtr<DatabaseCompleteCallback>> mCompleteCallbacks;

  uint64_t mNextTransactionId;
  FlippedOnce<false> mShutdownRequested;
  FlippedOnce<false> mShutdownComplete;

 public:
  ConnectionPool();

  void AssertIsOnOwningThread() const {
    NS_ASSERT_OWNINGTHREAD(ConnectionPool);
  }

  Result<RefPtr<DatabaseConnection>, nsresult> GetOrCreateConnection(
      const Database& aDatabase);

  uint64_t Start(const nsID& aBackgroundChildLoggingId,
                 const nsACString& aDatabaseId, int64_t aLoggingSerialNumber,
                 const nsTArray<nsString>& aObjectStoreNames,
                 bool aIsWriteTransaction,
                 TransactionDatabaseOperationBase* aTransactionOp);

  void Dispatch(uint64_t aTransactionId, nsIRunnable* aRunnable);

  void Finish(uint64_t aTransactionId, FinishCallback* aCallback);

  void CloseDatabaseWhenIdle(const nsACString& aDatabaseId) {
    Unused << CloseDatabaseWhenIdleInternal(aDatabaseId);
  }

  void WaitForDatabaseToComplete(const nsCString& aDatabaseId,
                                 nsIRunnable* aCallback);

  void Shutdown();

  NS_INLINE_DECL_REFCOUNTING(ConnectionPool)

 private:
  ~ConnectionPool();

  static void IdleTimerCallback(nsITimer* aTimer, void* aClosure);

  static uint32_t SerialNumber() { return ++sSerialNumber; }

  static uint32_t sSerialNumber;

  void Cleanup();

  void AdjustIdleTimer();

  void CancelIdleTimer();

  void CloseIdleDatabases();

  bool ScheduleTransaction(TransactionInfo& aTransactionInfo,
                           bool aFromQueuedTransactions);

  void NoteFinishedTransaction(uint64_t aTransactionId);

  void ScheduleQueuedTransactions();

  void NoteIdleDatabase(DatabaseInfo& aDatabaseInfo);

  void NoteClosedDatabase(DatabaseInfo& aDatabaseInfo);

  bool MaybeFireCallback(DatabaseCompleteCallback* aCallback);

  void PerformIdleDatabaseMaintenance(DatabaseInfo& aDatabaseInfo);

  void CloseDatabase(DatabaseInfo& aDatabaseInfo) const;

  bool CloseDatabaseWhenIdleInternal(const nsACString& aDatabaseId);
};

class ConnectionPool::ConnectionRunnable : public Runnable {
 protected:
  DatabaseInfo& mDatabaseInfo;
  nsCOMPtr<nsIEventTarget> mOwningEventTarget;

  explicit ConnectionRunnable(DatabaseInfo& aDatabaseInfo);

  ~ConnectionRunnable() override = default;
};

class ConnectionPool::IdleConnectionRunnable final : public ConnectionRunnable {
  const bool mNeedsCheckpoint;
  Atomic<bool> mInterrupted;

 public:
  IdleConnectionRunnable(DatabaseInfo& aDatabaseInfo, bool aNeedsCheckpoint)
      : ConnectionRunnable(aDatabaseInfo), mNeedsCheckpoint(aNeedsCheckpoint) {}

  NS_INLINE_DECL_REFCOUNTING_INHERITED(IdleConnectionRunnable,
                                       ConnectionRunnable)

  void Interrupt() { mInterrupted = true; }

 private:
  ~IdleConnectionRunnable() override = default;

  NS_DECL_NSIRUNNABLE
};

class ConnectionPool::CloseConnectionRunnable final
    : public ConnectionRunnable {
 public:
  explicit CloseConnectionRunnable(DatabaseInfo& aDatabaseInfo)
      : ConnectionRunnable(aDatabaseInfo) {}

  NS_INLINE_DECL_REFCOUNTING_INHERITED(CloseConnectionRunnable,
                                       ConnectionRunnable)

 private:
  ~CloseConnectionRunnable() override = default;

  NS_DECL_NSIRUNNABLE
};

struct ConnectionPool::DatabaseInfo final {
  friend class mozilla::DefaultDelete<DatabaseInfo>;

  RefPtr<ConnectionPool> mConnectionPool;
  const nsCString mDatabaseId;
  RefPtr<DatabaseConnection> mConnection;
  nsClassHashtable<nsStringHashKey, TransactionInfoPair> mBlockingTransactions;
  nsTArray<NotNull<TransactionInfo*>> mTransactionsScheduledDuringClose;
  nsTArray<NotNull<TransactionInfo*>> mScheduledWriteTransactions;
  Maybe<TransactionInfo&> mRunningWriteTransaction;
  RefPtr<TaskQueue> mEventTarget;
  uint32_t mReadTransactionCount;
  uint32_t mWriteTransactionCount;
  bool mNeedsCheckpoint;
  bool mIdle;
  FlippedOnce<false> mCloseOnIdle;
  bool mClosing;

#ifdef DEBUG
  nsISerialEventTarget* mDEBUGConnectionEventTarget;
#endif

  DatabaseInfo(ConnectionPool* aConnectionPool, const nsACString& aDatabaseId);

  void AssertIsOnConnectionThread() const {
    MOZ_ASSERT(mDEBUGConnectionEventTarget);
    MOZ_ASSERT(GetCurrentSerialEventTarget() == mDEBUGConnectionEventTarget);
  }

  uint64_t TotalTransactionCount() const {
    return mReadTransactionCount + mWriteTransactionCount;
  }

  nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable);

 private:
  ~DatabaseInfo();

  DatabaseInfo(const DatabaseInfo&) = delete;
  DatabaseInfo& operator=(const DatabaseInfo&) = delete;
};

struct ConnectionPool::DatabaseCompleteCallback final {
  friend class DefaultDelete<DatabaseCompleteCallback>;

  nsCString mDatabaseId;
  nsCOMPtr<nsIRunnable> mCallback;

  DatabaseCompleteCallback(const nsCString& aDatabaseIds,
                           nsIRunnable* aCallback);

 private:
  ~DatabaseCompleteCallback();
};

class NS_NO_VTABLE ConnectionPool::FinishCallback : public nsIRunnable {
 public:
  // Called on the owning thread before any additional transactions are
  // unblocked.
  virtual void TransactionFinishedBeforeUnblock() = 0;

  // Called on the owning thread after additional transactions may have been
  // unblocked.
  virtual void TransactionFinishedAfterUnblock() = 0;

 protected:
  FinishCallback() = default;

  virtual ~FinishCallback() = default;
};

class ConnectionPool::FinishCallbackWrapper final : public Runnable {
  RefPtr<ConnectionPool> mConnectionPool;
  RefPtr<FinishCallback> mCallback;
  nsCOMPtr<nsIEventTarget> mOwningEventTarget;
  uint64_t mTransactionId;
  bool mHasRunOnce;

 public:
  FinishCallbackWrapper(ConnectionPool* aConnectionPool,
                        uint64_t aTransactionId, FinishCallback* aCallback);

  NS_INLINE_DECL_REFCOUNTING_INHERITED(FinishCallbackWrapper, Runnable)

 private:
  ~FinishCallbackWrapper() override;

  NS_DECL_NSIRUNNABLE
};

#ifdef DEBUG

class ConnectionPool::TransactionRunnable final : public Runnable {
 public:
  explicit TransactionRunnable(nsCOMPtr<nsIRunnable> aRunnable);

 private:
  NS_DECL_NSIRUNNABLE

  nsCOMPtr<nsIRunnable> mRunnable;
};

#endif

class ConnectionPool::TransactionInfo final {
  friend class mozilla::DefaultDelete<TransactionInfo>;

  nsTHashSet<TransactionInfo*> mBlocking;
  nsTArray<NotNull<TransactionInfo*>> mBlockingOrdered;

 public:
  DatabaseInfo& mDatabaseInfo;
  const nsID mBackgroundChildLoggingId;
  const nsCString mDatabaseId;
  const uint64_t mTransactionId;
  const int64_t mLoggingSerialNumber;
  const nsTArray<nsString> mObjectStoreNames;
  nsTHashSet<TransactionInfo*> mBlockedOn;
  nsTArray<nsCOMPtr<nsIRunnable>> mQueuedRunnables;
  const bool mIsWriteTransaction;
  bool mRunning;

#ifdef DEBUG
  FlippedOnce<false> mFinished;
#endif

  TransactionInfo(DatabaseInfo& aDatabaseInfo,
                  const nsID& aBackgroundChildLoggingId,
                  const nsACString& aDatabaseId, uint64_t aTransactionId,
                  int64_t aLoggingSerialNumber,
                  const nsTArray<nsString>& aObjectStoreNames,
                  bool aIsWriteTransaction,
                  TransactionDatabaseOperationBase* aTransactionOp);

  void AddBlockingTransaction(TransactionInfo& aTransactionInfo);

  void RemoveBlockingTransactions();

 private:
  ~TransactionInfo();

  void MaybeUnblock(TransactionInfo& aTransactionInfo);
};

struct ConnectionPool::TransactionInfoPair final {
  // Multiple reading transactions can block future writes.
  nsTArray<NotNull<TransactionInfo*>> mLastBlockingWrites;
  // But only a single writing transaction can block future reads.
  Maybe<TransactionInfo&> mLastBlockingReads;

#if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING)
  TransactionInfoPair();
  ~TransactionInfoPair();
#endif
};

/*******************************************************************************
 * Actor class declarations
 ******************************************************************************/


template <IDBCursorType CursorType>
class CommonOpenOpHelper;
template <IDBCursorType CursorType>
class IndexOpenOpHelper;
template <IDBCursorType CursorType>
class ObjectStoreOpenOpHelper;
template <IDBCursorType CursorType>
class OpenOpHelper;

class DatabaseOperationBase : public Runnable,
                              public mozIStorageProgressHandler {
  template <IDBCursorType CursorType>
  friend class OpenOpHelper;

 protected:
  class AutoSetProgressHandler;

  using UniqueIndexTable = nsTHashMap<nsUint64HashKey, bool>;

  const nsCOMPtr<nsIEventTarget> mOwningEventTarget;
  const nsID mBackgroundChildLoggingId;
  const uint64_t mLoggingSerialNumber;

 private:
  nsresult mResultCode = NS_OK;
  Atomic<bool> mOperationMayProceed;
  FlippedOnce<false> mActorDestroyed;

 public:
  NS_DECL_ISUPPORTS_INHERITED

  bool IsOnOwningThread() const {
    MOZ_ASSERT(mOwningEventTarget);

    bool current;
    return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(¤t)) &&
           current;
  }

  void AssertIsOnOwningThread() const {
    MOZ_ASSERT(IsOnBackgroundThread());
    MOZ_ASSERT(IsOnOwningThread());
  }

  void NoteActorDestroyed() {
    AssertIsOnOwningThread();

    mActorDestroyed.EnsureFlipped();
    mOperationMayProceed = false;
  }

  bool IsActorDestroyed() const {
    AssertIsOnOwningThread();

    return mActorDestroyed;
  }

  // May be called on any thread, but you should call IsActorDestroyed() if
  // you know you're on the background thread because it is slightly faster.
  bool OperationMayProceed() const { return mOperationMayProceed; }

  const nsID& BackgroundChildLoggingId() const {
    return mBackgroundChildLoggingId;
  }

  uint64_t LoggingSerialNumber() const { return mLoggingSerialNumber; }

  nsresult ResultCode() const { return mResultCode; }

  void SetFailureCode(nsresult aFailureCode) {
    MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
    OverrideFailureCode(aFailureCode);
  }

  void SetFailureCodeIfUnset(nsresult aFailureCode) {
    if (NS_SUCCEEDED(mResultCode)) {
      OverrideFailureCode(aFailureCode);
    }
  }

  bool HasFailed() const { return NS_FAILED(mResultCode); }

 protected:
  DatabaseOperationBase(const nsID& aBackgroundChildLoggingId,
                        uint64_t aLoggingSerialNumber)
      : Runnable("dom::indexedDB::DatabaseOperationBase"),
        mOwningEventTarget(GetCurrentSerialEventTarget()),
        mBackgroundChildLoggingId(aBackgroundChildLoggingId),
        mLoggingSerialNumber(aLoggingSerialNumber),
        mOperationMayProceed(true) {
    AssertIsOnOwningThread();
  }

  ~DatabaseOperationBase() override { MOZ_ASSERT(mActorDestroyed); }

  void OverrideFailureCode(nsresult aFailureCode) {
    MOZ_ASSERT(NS_FAILED(aFailureCode));

    mResultCode = aFailureCode;
  }

  static nsAutoCString MaybeGetBindingClauseForKeyRange(
      const Maybe<SerializedKeyRange>& aOptionalKeyRange,
      const nsACString& aKeyColumnName);

  static nsAutoCString GetBindingClauseForKeyRange(
      const SerializedKeyRange& aKeyRange, const nsACString& aKeyColumnName);

  static uint64_t ReinterpretDoubleAsUInt64(double aDouble);

  static nsresult BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange,
                                          mozIStorageStatement* aStatement);

  static nsresult BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange,
                                          mozIStorageStatement* aStatement,
                                          const nsCString& aLocale);

  static Result<IndexDataValuesAutoArray, nsresult>
  IndexDataValuesFromUpdateInfos(const nsTArray<IndexUpdateInfo>& aUpdateInfos,
                                 const UniqueIndexTable& aUniqueIndexTable);

  static nsresult InsertIndexTableRows(
      DatabaseConnection* aConnection, IndexOrObjectStoreId aObjectStoreId,
      const Key& aObjectStoreKey, const nsTArray<IndexDataValue>& aIndexValues);

  static nsresult DeleteIndexDataTableRows(
      DatabaseConnection* aConnection, const Key& aObjectStoreKey,
      const nsTArray<IndexDataValue>& aIndexValues);

  static nsresult DeleteObjectStoreDataTableRowsWithIndexes(
      DatabaseConnection* aConnection, IndexOrObjectStoreId aObjectStoreId,
      const Maybe<SerializedKeyRange>& aKeyRange);

  static nsresult UpdateIndexValues(
      DatabaseConnection* aConnection, IndexOrObjectStoreId aObjectStoreId,
      const Key& aObjectStoreKey, const nsTArray<IndexDataValue>& aIndexValues);

  static Result<bool, nsresult> ObjectStoreHasIndexes(
      DatabaseConnection& aConnection, IndexOrObjectStoreId aObjectStoreId);

 private:
  template <typename KeyTransformation>
  static nsresult MaybeBindKeyToStatement(
      const Key& aKey, mozIStorageStatement* aStatement,
      const nsACString& aParameterName,
      const KeyTransformation& aKeyTransformation);

  template <typename KeyTransformation>
  static nsresult BindTransformedKeyRangeToStatement(
      const SerializedKeyRange& aKeyRange, mozIStorageStatement* aStatement,
      const KeyTransformation& aKeyTransformation);

  // Not to be overridden by subclasses.
  NS_DECL_MOZISTORAGEPROGRESSHANDLER
};

class MOZ_STACK_CLASS DatabaseOperationBase::AutoSetProgressHandler final {
  Maybe<mozIStorageConnection&> mConnection;
#ifdef DEBUG
  DatabaseOperationBase* mDEBUGDatabaseOp;
#endif

 public:
  AutoSetProgressHandler();

  ~AutoSetProgressHandler();

  nsresult Register(mozIStorageConnection& aConnection,
                    DatabaseOperationBase* aDatabaseOp);

  void Unregister();
};

class TransactionDatabaseOperationBase : public DatabaseOperationBase {
  enum class InternalState {
    Initial,
    DatabaseWork,
    SendingPreprocess,
    WaitingForContinue,
    SendingResults,
    Completed
  };

  InitializedOnce<const NotNull<SafeRefPtr<TransactionBase>>> mTransaction;
  // Unique request id within the context of the transaction, allocated by the
  // transaction in the content process starting from 0. Values less than 0 are
  // impossible and forbidden. Used to support the explicit commit() request.
  const int64_t mRequestId;
  InternalState mInternalState = InternalState::Initial;
  bool mWaitingForContinue = false;
  const bool mTransactionIsAborted;

 protected:
  const int64_t mTransactionLoggingSerialNumber;

#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
 protected:
  // A check only enables when the diagnostic assert turns on. It assumes the
  // mUpdateRefcountFunction is a nullptr because the previous
  // StartTransactionOp failed on the connection thread and the next write
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=92 H=99 G=95

¤ Dauer der Verarbeitung: 0.38 Sekunden  (vorverarbeitet)  ¤

*© 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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge