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

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"

// Local includes
#include "LSInitializationTypes.h"
#include "LSObject.h"
#include "ReportInternalError.h"

// Global includes
#include <cinttypes>
#include <cstdlib>
#include <cstring>
#include <new>
#include <tuple>
#include <type_traits>
#include <utility>
#include "ErrorList.h"
#include "MainThreadUtils.h"
#include "mozIStorageAsyncConnection.h"
#include "mozIStorageConnection.h"
#include "mozIStorageFunction.h"
#include "mozIStorageService.h"
#include "mozIStorageStatement.h"
#include "mozIStorageValueArray.h"
#include "mozStorageCID.h"
#include "mozStorageHelper.h"
#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/DebugOnly.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/OriginAttributes.h"
#include "mozilla/Preferences.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Result.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/StoragePrincipalHelper.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/Utf8.h"
#include "mozilla/Variant.h"
#include "mozilla/dom/ClientManagerService.h"
#include "mozilla/dom/FlippedOnce.h"
#include "mozilla/dom/LSSnapshot.h"
#include "mozilla/dom/LSValue.h"
#include "mozilla/dom/LSWriteOptimizer.h"
#include "mozilla/dom/LSWriteOptimizerImpl.h"
#include "mozilla/dom/LocalStorageCommon.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/dom/PBackgroundLSDatabase.h"
#include "mozilla/dom/PBackgroundLSDatabaseParent.h"
#include "mozilla/dom/PBackgroundLSObserverParent.h"
#include "mozilla/dom/PBackgroundLSRequestParent.h"
#include "mozilla/dom/PBackgroundLSSharedTypes.h"
#include "mozilla/dom/PBackgroundLSSimpleRequestParent.h"
#include "mozilla/dom/PBackgroundLSSnapshotParent.h"
#include "mozilla/dom/SnappyUtils.h"
#include "mozilla/dom/StorageDBUpdater.h"
#include "mozilla/dom/StorageUtils.h"
#include "mozilla/dom/ipc/IdType.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/DirectoryLock.h"
#include "mozilla/dom/quota/DirectoryLockInlines.h"
#include "mozilla/dom/quota/FirstInitializationAttemptsImpl.h"
#include "mozilla/dom/quota/HashKeys.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/StorageHelpers.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/UsageInfo.h"
#include "mozilla/glean/DomLocalstorageMetrics.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/PBackgroundParent.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/storage/Variant.h"
#include "NotifyUtils.h"
#include "nsBaseHashtable.h"
#include "nsCOMPtr.h"
#include "nsClassHashtable.h"
#include "nsTHashMap.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsHashKeys.h"
#include "nsIBinaryInputStream.h"
#include "nsIBinaryOutputStream.h"
#include "nsIDirectoryEnumerator.h"
#include "nsIEventTarget.h"
#include "nsIFile.h"
#include "nsIInputStream.h"
#include "nsIObjectInputStream.h"
#include "nsIObjectOutputStream.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsIOutputStream.h"
#include "nsIRunnable.h"
#include "nsISerialEventTarget.h"
#include "nsISupports.h"
#include "nsIThread.h"
#include "nsITimer.h"
#include "nsIVariant.h"
#include "nsInterfaceHashtable.h"
#include "nsLiteralString.h"
#include "nsNetUtil.h"
#include "nsPointerHashKeys.h"
#include "nsPrintfCString.h"
#include "nsRefPtrHashtable.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsStringFlags.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "nsTHashSet.h"
#include "nsTLiteralString.h"
#include "nsTStringRepr.h"
#include "nsThreadUtils.h"
#include "nsVariant.h"
#include "nsXPCOM.h"
#include "nsXULAppAPI.h"
#include "nscore.h"
#include "prenv.h"
#include "prtime.h"

#define LS_LOG_TEST() MOZ_LOG_TEST(GetLocalStorageLogger(), LogLevel::Info)
#define LS_LOG(_args) MOZ_LOG(GetLocalStorageLogger(), LogLevel::Info, _args)

#if defined(MOZ_WIDGET_ANDROID)
#  define LS_MOBILE
#endif

namespace mozilla::dom {

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

namespace {

struct ArchivedOriginInfo;
class ArchivedOriginScope;
class Connection;
class ConnectionThread;
class Database;
class Observer;
class PrepareDatastoreOp;
class PreparedDatastore;
class QuotaClient;
class Snapshot;

using ArchivedOriginHashtable =
    nsClassHashtable<nsCStringHashKey, ArchivedOriginInfo>;

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


// Major schema version. Bump for almost everything.
const uint32_t kMajorSchemaVersion = 5;

// Minor schema version. Should almost always be 0 (maybe bump on release
// branches if we have to).
const uint32_t kMinorSchemaVersion = 0;

// The schema version we store in the SQLite database is a (signed) 32-bit
// integer. The major version is left-shifted 4 bits so the max value is
// 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF.
static_assert(kMajorSchemaVersion <= 0xFFFFFFF,
              "Major version needs to fit in 28 bits.");
static_assert(kMinorSchemaVersion <= 0xF,
              "Minor version needs to fit in 4 bits.");

const int32_t kSQLiteSchemaVersion =
    int32_t((kMajorSchemaVersion << 4) + kMinorSchemaVersion);

// 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 LS_MOBILE
    512;
#else
    1024;
#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 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 database name for LocalStorage data in a per-origin directory.
 */

constexpr auto kDataFileName = u"data.sqlite"_ns;

/**
 * The journal corresponding to kDataFileName.  (We don't use WAL mode.)
 * Currently only needed in QuotaClient::InitOrigin and only in DEBUG builds.
 * See the corresponding comment in QuotaClient::InitOrigin.
 */

#ifdef DEBUG
constexpr auto kJournalFileName = u"data.sqlite-journal"_ns;
#endif

/**
 * This file contains the current usage of the LocalStorage database as defined
 * by the mozLength totals of all keys and values for the database, which
 * differs from the actual size on disk.  We store this value in a separate
 * file as a cache so that we can initialize the QuotaClient faster.
 * In the future, this file will be eliminated and the information will be
 * stored in PROFILE/storage.sqlite or similar QuotaManager-wide storage.
 *
 * The file contains a binary verification cookie (32-bits) followed by the
 * actual usage (64-bits).
 */

constexpr auto kUsageFileName = u"usage"_ns;

/**
 * Following a QuotaManager idiom, this journal file's existence is a marker
 * that the usage file was in the process of being updated and is currently
 * invalid.  This file is created prior to updating the usage file and only
 * deleted after the usage file has been written and closed and any pending
 * database transactions have been committed.  Note that this idiom is expected
 * to work if Gecko crashes in the middle of a write, but is not expected to be
 * foolproof in the face of a system crash, as we do not explicitly attempt to
 * fsync the directory containing the journal file.
 *
 * If the journal file is found to exist at origin initialization time, the
 * usage will be re-computed from the current state of DATA_FILE_NAME.
 */

constexpr auto kUsageJournalFileName = u"usage-journal"_ns;

static const uint32_t kUsageFileSize = 12;
static const uint32_t kUsageFileCookie = 0x420a420a;

/**
 * How long between the first moment we know we have data to be written on a
 * `Connection` and when we should actually perform the write.  This helps
 * limit disk churn under silly usage patterns and is historically consistent
 * with the previous, legacy implementation.
 *
 * Note that flushing happens downstream of Snapshot checkpointing and its
 * batch mechanism which helps avoid wasteful IPC in the case of silly content
 * code.
 */

const uint32_t kFlushTimeoutMs = 5000;

const bool kDefaultShadowWrites = false;
const uint32_t kDefaultSnapshotPrefill = 16384;
const uint32_t kDefaultSnapshotGradualPrefill = 4096;
const bool kDefaultClientValidation = true;
/**
 * Should all mutations also be reflected in the "shadow" database, which is
 * the legacy webappsstore.sqlite database.  When this is enabled, users can
 * downgrade their version of Firefox and/or otherwise fall back to the legacy
 * implementation without loss of data.  (Older versions of Firefox will
 * recognize the presence of ls-archive.sqlite and purge it and the other
 * LocalStorage directories so privacy is maintained.)
 */

const char kShadowWritesPref[] = "dom.storage.shadow_writes";
/**
 * Byte budget for sending data down to the LSSnapshot instance when it is first
 * created.  If there is less data than this (measured by tallying the string
 * length of the keys and values), all data is sent, otherwise partial data is
 * sent.  See `Snapshot`.
 */

const char kSnapshotPrefillPref[] = "dom.storage.snapshot_prefill";
/**
 * When a specific value is requested by an LSSnapshot that is not already fully
 * populated, gradual prefill is used. This preference specifies the number of
 * bytes to be used to send values beyond the specific value that is requested.
 * (The size of the explicitly requested value does not impact this preference.)
 * Setting the value to 0 disables gradual prefill. Tests may set this value to
 * -1 which is converted to INT_MAX in order to cause gradual prefill to send
 * all values not previously sent.
 */

const char kSnapshotGradualPrefillPref[] =
    "dom.storage.snapshot_gradual_prefill";

const char kClientValidationPref[] = "dom.storage.client_validation";

/**
 * The amount of time a PreparedDatastore instance should stick around after a
 * preload is triggered in order to give time for the page to use LocalStorage
 * without triggering worst-case synchronous jank.
 */

const uint32_t kPreparedDatastoreTimeoutMs = 20000;

/**
 * Cold storage for LocalStorage data extracted from webappsstore.sqlite at
 * LSNG first-run that has not yet been migrated to its own per-origin directory
 * by use.
 *
 * In other words, at first run, LSNG copies the contents of webappsstore.sqlite
 * into this database.  As requests are made for that LocalStorage data, the
 * contents are removed from this database and placed into per-origin QM
 * storage.  So the contents of this database are always old, unused
 * LocalStorage data that we can potentially get rid of at some point in the
 * future.
 */

#define LS_ARCHIVE_FILE_NAME u"ls-archive.sqlite"
/**
 * The legacy LocalStorage database.  Its contents are maintained as our
 * "shadow" database so that LSNG can be disabled without loss of user data.
 */

#define WEB_APPS_STORE_FILE_NAME u"webappsstore.sqlite"

// Shadow database Write Ahead Log's maximum size is 512KB
const uint32_t kShadowMaxWALSize = 512 * 1024;

bool IsOnGlobalConnectionThread();

void AssertIsOnGlobalConnectionThread();

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


int32_t MakeSchemaVersion(uint32_t aMajorSchemaVersion,
                          uint32_t aMinorSchemaVersion) {
  return int32_t((aMajorSchemaVersion << 4) + aMinorSchemaVersion);
}

nsCString GetArchivedOriginHashKey(const nsACString& aOriginSuffix,
                                   const nsACString& aOriginNoSuffix) {
  return aOriginSuffix + ":"_ns + aOriginNoSuffix;
}

nsresult CreateDataTable(mozIStorageConnection* aConnection) {
  return aConnection->ExecuteSimpleSQL(
      "CREATE TABLE data"
      "( key TEXT PRIMARY KEY"
      ", utf16_length INTEGER NOT NULL"
      ", conversion_type INTEGER NOT NULL"
      ", compression_type INTEGER NOT NULL"
      ", last_access_time INTEGER NOT NULL DEFAULT 0"
      ", value BLOB NOT NULL"
      ");"_ns);
}

nsresult CreateTables(mozIStorageConnection* aConnection) {
  MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
  MOZ_ASSERT(aConnection);

  // Table `database`
  QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
      "CREATE TABLE database"
      "( origin TEXT NOT NULL"
      ", usage INTEGER NOT NULL DEFAULT 0"
      ", last_vacuum_time INTEGER NOT NULL DEFAULT 0"
      ", last_analyze_time INTEGER NOT NULL DEFAULT 0"
      ", last_vacuum_size INTEGER NOT NULL DEFAULT 0"
      ");"_ns)));

  // Table `data`
  QM_TRY(MOZ_TO_RESULT(CreateDataTable(aConnection)));

  QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(kSQLiteSchemaVersion)));

  return NS_OK;
}

nsresult UpgradeSchemaFrom1_0To2_0(mozIStorageConnection* aConnection) {
  AssertIsOnIOThread();
  MOZ_ASSERT(aConnection);

  QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
      "ALTER TABLE database ADD COLUMN usage INTEGER NOT NULL DEFAULT 0;"_ns)));

  QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
      "UPDATE database "
      "SET usage = (SELECT total(utf16Length(key) + utf16Length(value)) "
      "FROM data);"_ns)));

  QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeSchemaVersion(2, 0))));

  return NS_OK;
}

nsresult UpgradeSchemaFrom2_0To3_0(mozIStorageConnection* aConnection) {
  AssertIsOnIOThread();
  MOZ_ASSERT(aConnection);

  QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
      "ALTER TABLE data ADD COLUMN utf16Length INTEGER NOT NULL DEFAULT 0;"_ns)));

  QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
      "UPDATE data SET utf16Length = utf16Length(value);"_ns)));

  QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeSchemaVersion(3, 0))));

  return NS_OK;
}

nsresult UpgradeSchemaFrom3_0To4_0(mozIStorageConnection* aConnection) {
  AssertIsOnIOThread();
  MOZ_ASSERT(aConnection);

  QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeSchemaVersion(4, 0))));

  return NS_OK;
}

nsresult UpgradeSchemaFrom4_0To5_0(mozIStorageConnection* aConnection) {
  AssertIsOnIOThread();
  MOZ_ASSERT(aConnection);

  // Recreate data table in new format following steps at
  // https://www.sqlite.org/lang_altertable.html
  // section "Making Other Kinds Of Table Schema Changes"
  QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
      "CREATE TABLE migrated_data"
      "( key TEXT PRIMARY KEY"
      ", utf16_length INTEGER NOT NULL"
      ", conversion_type INTEGER NOT NULL"
      ", compression_type INTEGER NOT NULL"
      ", last_access_time INTEGER NOT NULL DEFAULT 0"
      ", value BLOB NOT NULL"
      ");"_ns)));

  // Reinsert old data, all legacy data is UTF8
  static_assert(1u ==
                static_cast<uint8_t>(LSValue::ConversionType::UTF16_UTF8));
  QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
      "INSERT INTO migrated_data (key, utf16_length, conversion_type, "
      "compression_type, last_access_time, value) "
      "SELECT key, utf16Length, 1, compressed, lastAccessTime, value "
      "FROM data;"_ns)));

  QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL("DROP TABLE data;"_ns)));

  // Rename to data
  QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
      "ALTER TABLE migrated_data RENAME TO data;"_ns)));

  QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeSchemaVersion(5, 0))));

  return NS_OK;
}

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

  QM_TRY(MOZ_TO_RESULT(
      aConnection->ExecuteSimpleSQL("PRAGMA synchronous = FULL;"_ns)));

#ifndef LS_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  // LS_MOBILE

  return NS_OK;
}

Result<nsCOMPtr<mozIStorageConnection>, nsresult> CreateStorageConnection(
    nsIFile& aDBFile, nsIFile& aUsageFile, const nsACString& aOrigin) {
  MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());

  // XXX Common logic should be refactored out of this method and
  // cache::DBAction::OpenDBConnection, and maybe other similar functions.

  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, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                                     nsCOMPtr<mozIStorageConnection>,
                                     storageService, OpenDatabase, &aDBFile,
                                     mozIStorageService::CONNECTION_DEFAULT));

  QM_TRY(MOZ_TO_RESULT(SetDefaultPragmas(connection)));

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

  QM_TRY(OkIf(schemaVersion <= kSQLiteSchemaVersion), Err(NS_ERROR_FAILURE));

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

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

      // We have to set the auto_vacuum mode before opening a transaction.
      QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL(
#ifdef LS_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
          )));
    }

    bool vacuumNeeded = false;

    if (newDatabase) {
      mozStorageTransaction transaction(
          connection,
          /* aCommitOnComplete */ false,
          mozIStorageConnection::TRANSACTION_IMMEDIATE);

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

      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

      QM_TRY_INSPECT(
          const auto& stmt,
          MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
              nsCOMPtr<mozIStorageStatement>, connection, CreateStatement,
              "INSERT INTO database (origin) VALUES (:origin)"_ns));

      QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByName("origin"_ns, aOrigin)));

      QM_TRY(MOZ_TO_RESULT(stmt->Execute()));

      QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
    } else {
      // This logic needs to change next time we change the schema!
      static_assert(kSQLiteSchemaVersion == int32_t((5 << 4) + 0),
                    "Upgrade function needed due to schema version increase.");

      while (schemaVersion != kSQLiteSchemaVersion) {
        mozStorageTransaction transaction(
            connection,
            /* aCommitOnComplete */ false,
            mozIStorageConnection::TRANSACTION_IMMEDIATE);

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

        if (schemaVersion == MakeSchemaVersion(1, 0)) {
          QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom1_0To2_0(connection)));
        } else if (schemaVersion == MakeSchemaVersion(2, 0)) {
          QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom2_0To3_0(connection)));
        } else if (schemaVersion == MakeSchemaVersion(3, 0)) {
          QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom3_0To4_0(connection)));
        } else if (schemaVersion == MakeSchemaVersion(4, 0)) {
          QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom4_0To5_0(connection)));
          vacuumNeeded = true;
        } else {
          LS_WARNING(
              "Unable to open LocalStorage database, no upgrade path is "
              "available!");
          return Err(NS_ERROR_FAILURE);
        }

        QM_TRY(MOZ_TO_RESULT(transaction.Commit()));

        QM_TRY_UNWRAP(schemaVersion, MOZ_TO_RESULT_INVOKE_MEMBER(
                                         connection, GetSchemaVersion));
      }

      MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion);
    }

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

    if (newDatabase) {
      // Windows caches the file size, let's force it to stat the file again.
      QM_TRY_INSPECT(const bool& exists,
                     MOZ_TO_RESULT_INVOKE_MEMBER(aDBFile, Exists));
      Unused << exists;

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

      MOZ_ASSERT(fileSize > 0);

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

      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->BindInt64ByName("time"_ns, vacuumTime)));

      QM_TRY(
          MOZ_TO_RESULT(vacuumTimeStmt->BindInt64ByName("size"_ns, fileSize)));

      QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->Execute()));
    }
  }

  return connection;
}

template <typename CorruptedFileHandler>
Result<nsCOMPtr<mozIStorageConnection>, nsresult>
CreateStorageConnectionWithRecovery(
    nsIFile& aDBFile, nsIFile& aUsageFile, const nsACString& aOrigin,
    CorruptedFileHandler&& aCorruptedFileHandler) {
  QM_TRY_RETURN(QM_OR_ELSE_WARN_IF(
      // Expression.
      CreateStorageConnection(aDBFile, aUsageFile, aOrigin),
      // Predicate.
      IsDatabaseCorruptionError,
      // Fallback.
      ([&aDBFile, &aUsageFile, &aOrigin,
        &aCorruptedFileHandler](const nsresult rv)
           -> Result<nsCOMPtr<mozIStorageConnection>, nsresult> {
        // Remove the usage file first (it might not exist at all due
        // to corrupted state, which is ignored here).

        // Usually we only use QM_OR_ELSE_LOG_VERBOSE(_IF) with Remove and
        // NS_ERROR_FILE_NOT_FOUND check, but we're already in the rare case
        // of corruption here, so the use of QM_OR_ELSE_WARN_IF is ok here.
        QM_TRY(QM_OR_ELSE_WARN_IF(
            // Expression.
            MOZ_TO_RESULT(aUsageFile.Remove(false)),
            // Predicate.
            ([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }),
            // Fallback.
            ErrToDefaultOk<>));

        // Call the corrupted file handler before trying to remove the
        // database file, which might fail.
        aCorruptedFileHandler();

        // Nuke the database file.
        QM_TRY(MOZ_TO_RESULT(aDBFile.Remove(false)));

        QM_TRY_RETURN(CreateStorageConnection(aDBFile, aUsageFile, aOrigin));
      })));
}

Result<nsCOMPtr<mozIStorageConnection>, nsresult> GetStorageConnection(
    const nsAString& aDatabaseFilePath) {
  AssertIsOnGlobalConnectionThread();
  MOZ_ASSERT(!aDatabaseFilePath.IsEmpty());
  MOZ_ASSERT(StringEndsWith(aDatabaseFilePath, u".sqlite"_ns));

  QM_TRY_INSPECT(const auto& databaseFile, QM_NewLocalFile(aDatabaseFilePath));

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

  QM_TRY(OkIf(exists), Err(NS_ERROR_FAILURE));

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

  QM_TRY_UNWRAP(auto connection,
                MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                    nsCOMPtr<mozIStorageConnection>, ss, OpenDatabase,
                    databaseFile, mozIStorageService::CONNECTION_DEFAULT));

  QM_TRY(MOZ_TO_RESULT(SetDefaultPragmas(connection)));

  return connection;
}

Result<nsCOMPtr<nsIFile>, nsresult> GetArchiveFile(
    const nsAString& aStoragePath) {
  AssertIsOnIOThread();
  MOZ_ASSERT(!aStoragePath.IsEmpty());

  QM_TRY_UNWRAP(auto archiveFile, QM_NewLocalFile(aStoragePath));

  QM_TRY(MOZ_TO_RESULT(
      archiveFile->Append(nsLiteralString(LS_ARCHIVE_FILE_NAME))));

  return archiveFile;
}

Result<nsCOMPtr<mozIStorageConnection>, nsresult>
CreateArchiveStorageConnection(const nsAString& aStoragePath) {
  AssertIsOnIOThread();
  MOZ_ASSERT(!aStoragePath.IsEmpty());

  QM_TRY_INSPECT(const auto& archiveFile, GetArchiveFile(aStoragePath));

  // QuotaManager ensures this file always exists.
  DebugOnly<bool> exists;
  MOZ_ASSERT(NS_SUCCEEDED(archiveFile->Exists(&exists)));
  MOZ_ASSERT(exists);

  QM_TRY_INSPECT(const bool& isDirectory,
                 MOZ_TO_RESULT_INVOKE_MEMBER(archiveFile, IsDirectory));

  if (isDirectory) {
    LS_WARNING("ls-archive is not a file!");
    return nsCOMPtr<mozIStorageConnection>{};
  }

  QM_TRY_INSPECT(const auto& ss,
                 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.
          MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
              nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
              archiveFile, mozIStorageService::CONNECTION_DEFAULT),
          // Predicate.
          IsDatabaseCorruptionError,
          // Fallback. Don't throw an error, leave a corrupted ls-archive
          // database as it is.
          ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));

  if (connection) {
    const nsresult rv = StorageDBUpdater::Update(connection);
    if (NS_FAILED(rv)) {
      // Don't throw an error, leave a non-updateable ls-archive database as
      // it is.
      return nsCOMPtr<mozIStorageConnection>{};
    }
  }

  return connection;
}

Result<nsCOMPtr<nsIFile>, nsresult> GetShadowFile(const nsAString& aBasePath) {
  MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
  MOZ_ASSERT(!aBasePath.IsEmpty());

  QM_TRY_UNWRAP(auto archiveFile, QM_NewLocalFile(aBasePath));

  QM_TRY(MOZ_TO_RESULT(
      archiveFile->Append(nsLiteralString(WEB_APPS_STORE_FILE_NAME))));

  return archiveFile;
}

nsresult SetShadowJournalMode(mozIStorageConnection* aConnection) {
  MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
  MOZ_ASSERT(aConnection);

  // 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(nsAutoCString, *stmt,
                                                   GetUTF8String, 0));

  if (journalMode.Equals(journalModeWAL)) {
    // WAL mode successfully enabled. Set limits on its size here.

    // Set the threshold for auto-checkpointing the WAL. We don't want giant
    // logs slowing down us.
    QM_TRY_INSPECT(const auto& stmt, CreateAndExecuteSingleStepStatement(
                                         *aConnection, "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);

    // Note there is a default journal_size_limit set by mozStorage.
    QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
        "PRAGMA wal_autocheckpoint = "_ns +
        IntToCString(static_cast<int32_t>(kShadowMaxWALSize / pageSize)))));
  } else {
    QM_TRY(MOZ_TO_RESULT(
        aConnection->ExecuteSimpleSQL(journalModeQueryStart + "truncate"_ns)));
  }

  return NS_OK;
}

Result<nsCOMPtr<mozIStorageConnection>, nsresult> CreateShadowStorageConnection(
    const nsAString& aBasePath) {
  MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
  MOZ_ASSERT(!aBasePath.IsEmpty());

  QM_TRY_INSPECT(const auto& shadowFile, GetShadowFile(aBasePath));

  QM_TRY_INSPECT(const auto& ss,
                 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.
          MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
              nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
              shadowFile, mozIStorageService::CONNECTION_DEFAULT),
          // Predicate.
          IsDatabaseCorruptionError,
          // Fallback.
          ([&shadowFile, &ss](const nsresult rv)
               -> Result<nsCOMPtr<mozIStorageConnection>, nsresult> {
            QM_TRY(MOZ_TO_RESULT(shadowFile->Remove(false)));

            QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
                shadowFile, mozIStorageService::CONNECTION_DEFAULT));
          })));

  QM_TRY(MOZ_TO_RESULT(SetShadowJournalMode(connection)));

  // XXX Depending on whether the *first* call to OpenUnsharedDatabase above
  // failed, we (a) might or (b) might not be dealing with a fresh database
  // here. This is confusing, since in a failure of case (a) we would do the
  // same thing again. Probably, the control flow should be changed here so that
  // it's clear we only delete & create a fresh database once. If we still have
  // a failure then, we better give up. Or, if we really want to handle that,
  // the number of 2 retries seems arbitrary, and we should better do this in
  // some loop until a maximum number of retries is reached.
  //
  // Compare this with QuotaManager::CreateLocalStorageArchiveConnection, which
  // actually tracks if the file was removed before, but it's also more
  // complicated than it should be. Maybe these two methods can be merged (which
  // would mean that a parameter must be added that indicates whether it's
  // handling the shadow file or not).
  QM_TRY(QM_OR_ELSE_WARN(
      // Expression.
      MOZ_TO_RESULT(StorageDBUpdater::Update(connection)),
      // Fallback.
      ([&connection, &shadowFile, &ss](const nsresult) -> Result<Ok, nsresult> {
        QM_TRY(MOZ_TO_RESULT(connection->Close()));
        QM_TRY(MOZ_TO_RESULT(shadowFile->Remove(false)));

        QM_TRY_UNWRAP(connection, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                                      nsCOMPtr<mozIStorageConnection>, ss,
                                      OpenUnsharedDatabase, shadowFile,
                                      mozIStorageService::CONNECTION_DEFAULT));

        QM_TRY(MOZ_TO_RESULT(SetShadowJournalMode(connection)));

        QM_TRY(
            MOZ_TO_RESULT(StorageDBUpdater::CreateCurrentSchema(connection)));

        return Ok{};
      })));

  return connection;
}

Result<nsCOMPtr<mozIStorageConnection>, nsresult> GetShadowStorageConnection(
    const nsAString& aBasePath) {
  AssertIsOnIOThread();
  MOZ_ASSERT(!aBasePath.IsEmpty());

  QM_TRY_INSPECT(const auto& shadowFile, GetShadowFile(aBasePath));

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

  QM_TRY(OkIf(exists), Err(NS_ERROR_FAILURE));

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

  QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
      nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase, shadowFile,
      mozIStorageService::CONNECTION_DEFAULT));
}

nsresult AttachShadowDatabase(const nsAString& aBasePath,
                              mozIStorageConnection* aConnection) {
  AssertIsOnGlobalConnectionThread();
  MOZ_ASSERT(!aBasePath.IsEmpty());
  MOZ_ASSERT(aConnection);

  QM_TRY_INSPECT(const auto& shadowFile, GetShadowFile(aBasePath));

#ifdef DEBUG
  {
    QM_TRY_INSPECT(const bool& exists,
                   MOZ_TO_RESULT_INVOKE_MEMBER(shadowFile, Exists));

    MOZ_ASSERT(exists);
  }
#endif

  QM_TRY_INSPECT(const auto& path, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                                       nsString, shadowFile, GetPath));

  QM_TRY_INSPECT(const auto& stmt,
                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                     nsCOMPtr<mozIStorageStatement>, aConnection,
                     CreateStatement, "ATTACH DATABASE :path AS shadow;"_ns));

  QM_TRY(MOZ_TO_RESULT(stmt->BindStringByName("path"_ns, path)));

  QM_TRY(MOZ_TO_RESULT(stmt->Execute()));

  return NS_OK;
}

nsresult DetachShadowDatabase(mozIStorageConnection* aConnection) {
  AssertIsOnGlobalConnectionThread();
  MOZ_ASSERT(aConnection);

  QM_TRY(MOZ_TO_RESULT(
      aConnection->ExecuteSimpleSQL("DETACH DATABASE shadow"_ns)));

  return NS_OK;
}

Result<nsCOMPtr<nsIFile>, nsresult> GetUsageFile(
    const nsAString& aDirectoryPath) {
  MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
  MOZ_ASSERT(!aDirectoryPath.IsEmpty());

  QM_TRY_UNWRAP(auto usageFile, QM_NewLocalFile(aDirectoryPath));

  QM_TRY(MOZ_TO_RESULT(usageFile->Append(kUsageFileName)));

  return usageFile;
}

Result<nsCOMPtr<nsIFile>, nsresult> GetUsageJournalFile(
    const nsAString& aDirectoryPath) {
  MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
  MOZ_ASSERT(!aDirectoryPath.IsEmpty());

  QM_TRY_UNWRAP(auto usageJournalFile, QM_NewLocalFile(aDirectoryPath));

  QM_TRY(MOZ_TO_RESULT(usageJournalFile->Append(kUsageJournalFileName)));

  return usageJournalFile;
}

// Checks if aFile exists and is a file. Returns true if it exists and is a
// file, false if it doesn't exist, and an error if it exists but isn't a file.
Result<bool, nsresult> ExistsAsFile(nsIFile& aFile) {
  enum class ExistsAsFileResult { DoesNotExist, IsDirectory, IsFile };

  // This is an optimization to check both properties in one OS case, rather
  // than calling Exists first, and then IsDirectory. IsDirectory also checks
  // if the path exists. QM_OR_ELSE_WARN_IF is not used here since we just want
  // to log NS_ERROR_FILE_NOT_FOUND result and not spam the reports.
  QM_TRY_INSPECT(
      const auto& res,
      QM_OR_ELSE_LOG_VERBOSE_IF(
          // Expression.
          MOZ_TO_RESULT_INVOKE_MEMBER(aFile, IsDirectory)
              .map([](const bool isDirectory) {
                return isDirectory ? ExistsAsFileResult::IsDirectory
                                   : ExistsAsFileResult::IsFile;
              }),
          // Predicate.
          ([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }),
          // Fallback.
          ErrToOk<ExistsAsFileResult::DoesNotExist>));

  QM_TRY(OkIf(res != ExistsAsFileResult::IsDirectory), Err(NS_ERROR_FAILURE));

  return res == ExistsAsFileResult::IsFile;
}

nsresult UpdateUsageFile(nsIFile* aUsageFile, nsIFile* aUsageJournalFile,
                         int64_t aUsage) {
  MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
  MOZ_ASSERT(aUsageFile);
  MOZ_ASSERT(aUsageJournalFile);
  MOZ_ASSERT(aUsage >= 0);

  QM_TRY_INSPECT(const bool& usageJournalFileExists,
                 ExistsAsFile(*aUsageJournalFile));
  if (!usageJournalFileExists) {
    QM_TRY(MOZ_TO_RESULT(
        aUsageJournalFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644)));
  }

  QM_TRY_INSPECT(const auto& stream, NS_NewLocalFileOutputStream(aUsageFile));

  nsCOMPtr<nsIBinaryOutputStream> binaryStream =
      NS_NewObjectOutputStream(stream);

  QM_TRY(MOZ_TO_RESULT(binaryStream->Write32(kUsageFileCookie)));

  QM_TRY(MOZ_TO_RESULT(binaryStream->Write64(aUsage)));

#if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
  QM_TRY(MOZ_TO_RESULT(stream->Flush()));
#endif

  QM_TRY(MOZ_TO_RESULT(stream->Close()));

  return NS_OK;
}

Result<UsageInfo, nsresult> LoadUsageFile(nsIFile& aUsageFile) {
  AssertIsOnIOThread();

  QM_TRY_INSPECT(const int64_t& fileSize,
                 MOZ_TO_RESULT_INVOKE_MEMBER(aUsageFile, GetFileSize));

  QM_TRY(OkIf(fileSize == kUsageFileSize), Err(NS_ERROR_FILE_CORRUPTED));

  QM_TRY_UNWRAP(auto stream, NS_NewLocalFileInputStream(&aUsageFile));

  QM_TRY_INSPECT(const auto& bufferedStream,
                 NS_NewBufferedInputStream(stream.forget(), 16));

  const nsCOMPtr<nsIBinaryInputStream> binaryStream =
      NS_NewObjectInputStream(bufferedStream);

  QM_TRY_INSPECT(const uint32_t& cookie,
                 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));

  QM_TRY(OkIf(cookie == kUsageFileCookie), Err(NS_ERROR_FILE_CORRUPTED));

  QM_TRY_INSPECT(const uint64_t& usage,
                 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));

  return UsageInfo{DatabaseUsageType(Some(usage))};
}

/*******************************************************************************
 * Non-actor class declarations
 ******************************************************************************/


/**
 * Coalescing manipulation queue used by `Datastore`.  Used by `Datastore` to
 * update `Datastore::mOrderedItems` efficiently/for code simplification.
 * (Datastore does not actually depend on the coalescing, as mutations are
 * applied atomically when a Snapshot Checkpoints, and with `Datastore::mValues`
 * being updated at the same time the mutations are applied to Datastore's
 * mWriteOptimizer.)
 */

class DatastoreWriteOptimizer final : public LSWriteOptimizer<LSValue> {
 public:
  void ApplyAndReset(nsTArray<LSItemInfo>& aOrderedItems);
};

/**
 * Coalescing manipulation queue used by `Connection`.  Used by `Connection` to
 * buffer and coalesce manipulations applied to the Datastore in batches by
 * Snapshot Checkpointing until flushed to disk.
 */

class ConnectionWriteOptimizer final : public LSWriteOptimizer<LSValue> {
 public:
  // Returns the usage as the success value.
  Result<int64_t, nsresult> Perform(Connection* aConnection,
                                    bool aShadowWrites);

 private:
  /**
   * Handlers for specific mutations.  Each method knows how to `Perform` the
   * manipulation against a `Connection` and the "shadow" database (legacy
   * webappsstore.sqlite database that exists so LSNG can be disabled/safely
   * downgraded from.)
   */

  nsresult PerformInsertOrUpdate(Connection* aConnection, bool aShadowWrites,
                                 const nsAString& aKey, const LSValue& aValue);

  nsresult PerformDelete(Connection* aConnection, bool aShadowWrites,
                         const nsAString& aKey);

  nsresult PerformTruncate(Connection* aConnection, bool aShadowWrites);
};

class DatastoreOperationBase : public Runnable {
  nsCOMPtr<nsIEventTarget> mOwningEventTarget;
  nsresult mResultCode;
  Atomic<bool> mMayProceedOnNonOwningThread;
  bool mMayProceed;

 public:
  nsIEventTarget* OwningEventTarget() const {
    MOZ_ASSERT(mOwningEventTarget);

    return mOwningEventTarget;
  }

  bool IsOnOwningThread() const {
    MOZ_ASSERT(mOwningEventTarget);

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

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

  nsresult ResultCode() const { return mResultCode; }

  void SetFailureCode(nsresult aErrorCode) {
    MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
    MOZ_ASSERT(NS_FAILED(aErrorCode));

    mResultCode = aErrorCode;
  }

  void MaybeSetFailureCode(nsresult aErrorCode) {
    MOZ_ASSERT(NS_FAILED(aErrorCode));

    if (NS_SUCCEEDED(mResultCode)) {
      mResultCode = aErrorCode;
    }
  }

  void NoteComplete() {
    AssertIsOnOwningThread();

    mMayProceed = false;
    mMayProceedOnNonOwningThread = false;
  }

  bool MayProceed() const {
    AssertIsOnOwningThread();

    return mMayProceed;
  }

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

 protected:
  DatastoreOperationBase()
      : Runnable("dom::DatastoreOperationBase"),
        mOwningEventTarget(GetCurrentSerialEventTarget()),
        mResultCode(NS_OK),
        mMayProceedOnNonOwningThread(true),
        mMayProceed(true) {}

  ~DatastoreOperationBase() override { MOZ_ASSERT(!mMayProceed); }
};

class ConnectionDatastoreOperationBase : public DatastoreOperationBase {
 protected:
  RefPtr<Connection> mConnection;
  /**
   * This boolean flag is used by the CloseOp to avoid creating empty databases.
   */

  const bool mEnsureStorageConnection;

 public:
  // This callback will be called on the background thread before releasing the
  // final reference to this request object. Subclasses may perform any
  // additional cleanup here but must always call the base class implementation.
  virtual void Cleanup();

 protected:
  ConnectionDatastoreOperationBase(Connection* aConnection,
                                   bool aEnsureStorageConnection = true);

  ~ConnectionDatastoreOperationBase();

  // Must be overridden in subclasses. Called on the target thread to allow the
  // subclass to perform necessary datastore operations. A successful return
  // value will trigger an OnSuccess callback on the background thread while
  // while a failure value will trigger an OnFailure callback.
  virtual nsresult DoDatastoreWork() = 0;

  // Methods that subclasses may implement.
  virtual void OnSuccess();

  virtual void OnFailure(nsresult aResultCode);

 private:
  void RunOnConnectionThread();

  void RunOnOwningThread();

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

class Connection final : public CachingDatabaseConnection {
  friend class ConnectionThread;

  class GetOrCreateTemporaryOriginDirectoryHelper;

  class FlushOp;
  class CloseOp;

  RefPtr<ConnectionThread> mConnectionThread;
  RefPtr<QuotaClient> mQuotaClient;
  nsCOMPtr<nsITimer> mFlushTimer;
  UniquePtr<ArchivedOriginScope> mArchivedOriginScope;
  ConnectionWriteOptimizer mWriteOptimizer;
  // XXX Consider changing this to ClientMetadata.
  const OriginMetadata mOriginMetadata;
  nsString mDirectoryPath;
  /**
   * Propagated from PrepareDatastoreOp. PrepareDatastoreOp may defer the
   * creation of the localstorage client directory and database on the
   * QuotaManager IO thread in its DatabaseWork method to
   * Connection::EnsureStorageConnection, in which case the method needs to know
   * it is responsible for taking those actions (without redundantly performing
   * the existence checks).
   */

  const bool mDatabaseWasNotAvailable;
  bool mHasCreatedDatabase;
  bool mFlushScheduled;
#ifdef DEBUG
  bool mInUpdateBatch;
  bool mFinished;
#endif

 public:
  NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Connection)

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

  QuotaClient* GetQuotaClient() const {
    MOZ_ASSERT(mQuotaClient);

    return mQuotaClient;
  }

  ArchivedOriginScope* GetArchivedOriginScope() const {
    return mArchivedOriginScope.get();
  }

  const nsCString& Origin() const { return mOriginMetadata.mOrigin; }

  const nsString& DirectoryPath() const { return mDirectoryPath; }

  void GetFinishInfo(bool& aDatabaseWasNotAvailable,
                     bool& aHasCreatedDatabase) const {
    AssertIsOnOwningThread();
    MOZ_ASSERT(mFinished);

    aDatabaseWasNotAvailable = mDatabaseWasNotAvailable;
    aHasCreatedDatabase = mHasCreatedDatabase;
  }

  //////////////////////////////////////////////////////////////////////////////
  // Methods which can only be called on the owning thread.

  // This method is used to asynchronously execute a connection datastore
  // operation on the connection thread.
  void Dispatch(ConnectionDatastoreOperationBase* aOp);

  // This method is used to asynchronously close the storage connection on the
  // connection thread.
  void Close(nsIRunnable* aCallback);

  void SetItem(const nsString& aKey, const LSValue& aValue, int64_t aDelta,
               bool aIsNewItem);

  void RemoveItem(const nsString& aKey, int64_t aDelta);

  void Clear(int64_t aDelta);

  void BeginUpdateBatch();

  void EndUpdateBatch();

  //////////////////////////////////////////////////////////////////////////////
  // Methods which can only be called on the connection thread.

  nsresult EnsureStorageConnection();

  mozIStorageConnection* StorageConnection() const {
    AssertIsOnGlobalConnectionThread();

    return &MutableStorageConnection();
  }

  void CloseStorageConnection();

  nsresult BeginWriteTransaction();

  nsresult CommitWriteTransaction();

  nsresult RollbackWriteTransaction();

 private:
  // Only created by ConnectionThread.
  Connection(ConnectionThread* aConnectionThread,
             const OriginMetadata& aOriginMetadata,
             UniquePtr<ArchivedOriginScope>&& aArchivedOriginScope,
             bool aDatabaseWasNotAvailable);

  ~Connection();

  void ScheduleFlush();

  void Flush();

  static void FlushTimerCallback(nsITimer* aTimer, void* aClosure);
};

/**
 * Helper to invoke GetOrCreateTemporaryOriginDirectory on the QuotaManager IO
 * thread from the LocalStorage connection thread when creating a database
 * connection on demand. This is necessary because we attempt to defer the
 * creation of the origin directory and the database until absolutely needed,
 * but the directory creation must happen on the QM IO thread for invariant
 * reasons. (We can't just use a mutex because there could be logic on the IO
 * thread that also wants to deal with the same origin, so we need to queue a
 * runnable and wait our turn.)
 */

class Connection::GetOrCreateTemporaryOriginDirectoryHelper final
    : public Runnable {
  mozilla::Monitor mMonitor MOZ_UNANNOTATED;
  const OriginMetadata mOriginMetadata;
  nsString mOriginDirectoryPath;
  nsresult mIOThreadResultCode;
  bool mWaiting;

 public:
  explicit GetOrCreateTemporaryOriginDirectoryHelper(
      const OriginMetadata& aOriginMetadata)
      : Runnable(
            "dom::localstorage::Connection::"
            "GetOrCreateTemporaryOriginDirectoryHelper"),
        mMonitor("GetOrCreateTemporaryOriginDirectoryHelper::mMonitor"),
        mOriginMetadata(aOriginMetadata),
        mIOThreadResultCode(NS_OK),
        mWaiting(true) {
    AssertIsOnGlobalConnectionThread();
  }

  Result<nsString, nsresult> BlockAndReturnOriginDirectoryPath();

 private:
  ~GetOrCreateTemporaryOriginDirectoryHelper() = default;

  nsresult RunOnIOThread();

  NS_DECL_NSIRUNNABLE
};

class Connection::FlushOp final : public ConnectionDatastoreOperationBase {
  ConnectionWriteOptimizer mWriteOptimizer;
  bool mShadowWrites;

 public:
  FlushOp(Connection* aConnection, ConnectionWriteOptimizer&& aWriteOptimizer);

 private:
  nsresult DoDatastoreWork() override;

  void Cleanup() override;
};

class Connection::CloseOp final : public ConnectionDatastoreOperationBase {
  nsCOMPtr<nsIRunnable> mCallback;

 public:
  CloseOp(Connection* aConnection, nsIRunnable* aCallback)
      : ConnectionDatastoreOperationBase(aConnection,
                                         /* aEnsureStorageConnection */ false),
        mCallback(aCallback) {}

 private:
  nsresult DoDatastoreWork() override;

  void Cleanup() override;
};

class ConnectionThread final {
  friend class Connection;

  nsCOMPtr<nsIThread> mThread;
  nsRefPtrHashtable<nsCStringHashKey, Connection> mConnections;

 public:
  ConnectionThread();

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

  bool IsOnConnectionThread();

  void AssertIsOnConnectionThread();

  already_AddRefed<Connection> CreateConnection(
      const OriginMetadata& aOriginMetadata,
      UniquePtr<ArchivedOriginScope>&& aArchivedOriginScope,
      bool aDatabaseWasNotAvailable);

  void Shutdown();

  NS_INLINE_DECL_REFCOUNTING(ConnectionThread)

 private:
  ~ConnectionThread();
};

/**
 * Canonical state of Storage for an origin, containing all keys and their
 * values in the parent process.  Specifically, this is the state that will
 * be handed out to freshly created Snapshots and that will be persisted to disk
 * when the Connection's flush completes.  State is mutated in batches as
 * Snapshot instances Checkpoint their mutations locally accumulated in the
 * child LSSnapshots.
 */

class Datastore final
    : public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
  RefPtr<ClientDirectoryLock> mDirectoryLock;
  RefPtr<Connection> mConnection;
  RefPtr<QuotaObject> mQuotaObject;
  nsCOMPtr<nsIRunnable> mCompleteCallback;
  /**
   * PrepareDatastoreOps register themselves with the Datastore at
   * and unregister in PrepareDatastoreOp::Cleanup.
   */

  nsTHashSet<PrepareDatastoreOp*> mPrepareDatastoreOps;
  /**
   * PreparedDatastore instances register themselves with their associated
   * Datastore at construction time and unregister at destruction time.  They
   * hang around for kPreparedDatastoreTimeoutMs in order to keep the Datastore
   * from closing itself via MaybeClose(), thereby giving the document enough
   * time to load and access LocalStorage.
   */

  nsTHashSet<PreparedDatastore*> mPreparedDatastores;
  /**
   * A database is live (and in this hashtable) if it has a live LSDatabase
   * actor.  There is at most one Database per origin per content process.  Each
   * Database corresponds to an LSDatabase in its associated content process.
   */

  nsTHashSet<Database*> mDatabases;
  /**
   * A database is active if it has a non-null `mSnapshot`.  As long as there
   * are any active databases final deltas can't be calculated and
   * `UpdateUsage()` can't be invoked.
   */

  nsTHashSet<Database*> mActiveDatabases;
  /**
   * Non-authoritative hashtable representation of mOrderedItems for efficient
   * lookup.
   */

  nsTHashMap<nsStringHashKey, LSValue> mValues;
  /**
   * The authoritative ordered state of the Datastore; mValue also exists as an
   * unordered hashtable for efficient lookup.
   */

  nsTArray<LSItemInfo> mOrderedItems;
  nsTArray<int64_t> mPendingUsageDeltas;
  DatastoreWriteOptimizer mWriteOptimizer;
  const OriginMetadata mOriginMetadata;
  const uint32_t mPrivateBrowsingId;
  int64_t mUsage;
  int64_t mUpdateBatchUsage;
  int64_t mSizeOfKeys;
  int64_t mSizeOfItems;
  bool mClosed;
  bool mInUpdateBatch;
  bool mHasLivePrivateDatastore;

 public:
  // Created by PrepareDatastoreOp.
  Datastore(const OriginMetadata& aOriginMetadata, uint32_t aPrivateBrowsingId,
            int64_t aUsage, int64_t aSizeOfKeys, int64_t aSizeOfItems,
            RefPtr<ClientDirectoryLock>&& aDirectoryLock,
            RefPtr<Connection>&& aConnection,
            RefPtr<QuotaObject>&& aQuotaObject,
            nsTHashMap<nsStringHashKey, LSValue>& aValues,
            nsTArray<LSItemInfo>&& aOrderedItems);

  Maybe<ClientDirectoryLock&> MaybeDirectoryLockRef() const {
    AssertIsOnBackgroundThread();

    return ToMaybeRef(mDirectoryLock.get());
  }

  const nsCString& Origin() const { return mOriginMetadata.mOrigin; }

  uint32_t PrivateBrowsingId() const { return mPrivateBrowsingId; }

  bool IsPersistent() const {
    // Private-browsing is forbidden from touching disk.
    return mPrivateBrowsingId == 0;
  }

  void Close();

  bool IsClosed() const {
    AssertIsOnBackgroundThread();

    return mClosed;
  }

  void WaitForConnectionToComplete(nsIRunnable* aCallback);

  void NoteLivePrepareDatastoreOp(PrepareDatastoreOp* aPrepareDatastoreOp);

  void NoteFinishedPrepareDatastoreOp(PrepareDatastoreOp* aPrepareDatastoreOp);

  void NoteLivePrivateDatastore();

  void NoteFinishedPrivateDatastore();

  void NoteLivePreparedDatastore(PreparedDatastore* aPreparedDatastore);

  void NoteFinishedPreparedDatastore(PreparedDatastore* aPreparedDatastore);

  bool HasOtherProcessDatabases(Database* aDatabase);

  void NoteLiveDatabase(Database* aDatabase);

  void NoteFinishedDatabase(Database* aDatabase);

  void NoteActiveDatabase(Database* aDatabase);

  void NoteInactiveDatabase(Database* aDatabase);

  void GetSnapshotLoadInfo(const nsAString& aKey, bool& aAddKeyToUnknownItems,
                           nsTHashtable<nsStringHashKey>& aLoadedItems,
                           nsTArray<LSItemInfo>& aItemInfos,
                           uint32_t& aNextLoadIndex,
                           LSSnapshot::LoadState& aLoadState);

  uint32_t GetLength() const { return mValues.Count(); }

  const nsTArray<LSItemInfo>& GetOrderedItems() const { return mOrderedItems; }

  void GetItem(const nsAString& aKey, LSValue& aValue) const;

  void GetKeys(nsTArray<nsString>& aKeys) const;

  //////////////////////////////////////////////////////////////////////////////
  // Mutation Methods
  //
  // These are only called during Snapshot::Checkpoint

  /**
   * Used by Snapshot::Checkpoint to set a key/value pair as part of an
   * explicit batch.
   */

  void SetItem(Database* aDatabase, const nsString& aKey,
               const LSValue& aValue);

  void RemoveItem(Database* aDatabase, const nsString& aKey);

  void Clear(Database* aDatabase);

  void BeginUpdateBatch(int64_t aSnapshotUsage);

  int64_t EndUpdateBatch(int64_t aSnapshotPeakUsage);

  int64_t GetUsage() const { return mUsage; }

  int64_t AttemptToUpdateUsage(int64_t aMinSize, bool aInitial);

  bool HasOtherProcessObservers(Database* aDatabase);

  void NotifyOtherProcessObservers(Database* aDatabase,
                                   const nsString& aDocumentURI,
                                   const nsString& aKey,
                                   const LSValue& aOldValue,
                                   const LSValue& aNewValue);

  void NoteChangedObserverArray(const nsTArray<NotNull<Observer*>>& aObservers);

  void Stringify(nsACString& aResult) const;

  NS_INLINE_DECL_REFCOUNTING(Datastore)

 private:
  // Reference counted.
  ~Datastore();

  bool UpdateUsage(int64_t aDelta);

  void MaybeClose();

  void ConnectionClosedCallback();

  void CleanupMetadata();

  void NotifySnapshots(Database* aDatabase, const nsAString& aKey,
                       const LSValue& aOldValue, bool aAffectsOrder);

  void NoteChangedDatabaseMap();
};

class PrivateDatastore {
  const NotNull<RefPtr<Datastore>> mDatastore;

 public:
  explicit PrivateDatastore(MovingNotNull<RefPtr<Datastore>> aDatastore)
      : mDatastore(std::move(aDatastore)) {
    AssertIsOnBackgroundThread();

    mDatastore->NoteLivePrivateDatastore();
  }

  ~PrivateDatastore() { mDatastore->NoteFinishedPrivateDatastore(); }

  const Datastore& DatastoreRef() const {
    AssertIsOnBackgroundThread();

    return *mDatastore;
  }

  Datastore& MutableDatastoreRef() const {
    AssertIsOnBackgroundThread();

    return *mDatastore;
  }
};

class PreparedDatastore {
  RefPtr<Datastore> mDatastore;
  nsCOMPtr<nsITimer> mTimer;
  const Maybe<ContentParentId> mContentParentId;
  // Strings share buffers if possible, so it's not a problem to duplicate the
  // origin here.
  const nsCString mOrigin;
  uint64_t mDatastoreId;
  bool mForPreload;
  bool mInvalidated;

 public:
  PreparedDatastore(Datastore* aDatastore,
                    const Maybe<ContentParentId>& aContentParentId,
                    const nsACString& aOrigin, uint64_t aDatastoreId,
                    bool aForPreload)
      : mDatastore(aDatastore),
        mTimer(NS_NewTimer()),
        mContentParentId(aContentParentId),
        mOrigin(aOrigin),
        mDatastoreId(aDatastoreId),
        mForPreload(aForPreload),
        mInvalidated(false) {
    AssertIsOnBackgroundThread();
    MOZ_ASSERT(aDatastore);
    MOZ_ASSERT(mTimer);

    aDatastore->NoteLivePreparedDatastore(this);

    MOZ_ALWAYS_SUCCEEDS(mTimer->InitWithNamedFuncCallback(
        TimerCallback, this, kPreparedDatastoreTimeoutMs,
        nsITimer::TYPE_ONE_SHOT, "PreparedDatastore::TimerCallback"));
  }

  ~PreparedDatastore() {
    MOZ_ASSERT(mDatastore);
    MOZ_ASSERT(mTimer);

    mTimer->Cancel();

    mDatastore->NoteFinishedPreparedDatastore(this);
  }

  const Datastore& DatastoreRef() const {
    AssertIsOnBackgroundThread();
    MOZ_ASSERT(mDatastore);

    return *mDatastore;
  }

  Datastore& MutableDatastoreRef() const {
    AssertIsOnBackgroundThread();
    MOZ_ASSERT(mDatastore);

    return *mDatastore;
  }

  const Maybe<ContentParentId>& GetContentParentId() const {
    return mContentParentId;
  }

  const nsCString& Origin() const { return mOrigin; }

  void Invalidate() {
    AssertIsOnBackgroundThread();

    mInvalidated = true;

    if (mForPreload) {
      mTimer->Cancel();

      MOZ_ALWAYS_SUCCEEDS(mTimer->InitWithNamedFuncCallback(
          TimerCallback, this, 0, nsITimer::TYPE_ONE_SHOT,
          "PreparedDatastore::TimerCallback"));
    }
  }

  bool IsInvalidated() const {
    AssertIsOnBackgroundThread();

    return mInvalidated;
  }

 private:
  void Destroy();

  static void TimerCallback(nsITimer* aTimer, void* aClosure);
};

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


class Database final
    : public PBackgroundLSDatabaseParent,
      public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
  RefPtr<Datastore> mDatastore;
  Snapshot* mSnapshot;
  const PrincipalInfo mPrincipalInfo;
  const Maybe<ContentParentId> mContentParentId;
  // Strings share buffers if possible, so it's not a problem to duplicate the
  // origin here.
  nsCString mOrigin;
  mozilla::glean::TimerId mRequestAllowToCloseTimerId;
  uint32_t mPrivateBrowsingId;
  bool mAllowedToClose;
  bool mActorDestroyed;
  bool mRequestedAllowToClose;
#ifdef DEBUG
  bool mActorWasAlive;
#endif

 public:
  // Created in AllocPBackgroundLSDatabaseParent.
  Database(const PrincipalInfo& aPrincipalInfo,
           const Maybe<ContentParentId>& aContentParentId,
           const nsACString& aOrigin, uint32_t aPrivateBrowsingId);

  void AssertIsOnOwningThread() const {
    AssertIsOnBackgroundThread();
    NS_ASSERT_OWNINGTHREAD(mozilla::dom::Database);
  }

  Datastore* GetDatastore() const {
    AssertIsOnOwningThread();
    return mDatastore;
  }

  Maybe<Datastore&> MaybeDatastoreRef() const {
    AssertIsOnOwningThread();

    return ToMaybeRef(mDatastore.get());
  }

  const PrincipalInfo& GetPrincipalInfo() const { return mPrincipalInfo; }

  const Maybe<ContentParentId>& ContentParentIdRef() const {
    return mContentParentId;
  }

  uint32_t PrivateBrowsingId() const { return mPrivateBrowsingId; }

  const nsCString& Origin() const { return mOrigin; }

  void SetActorAlive(Datastore* aDatastore);

  void RegisterSnapshot(Snapshot* aSnapshot);

  void UnregisterSnapshot(Snapshot* aSnapshot);

  Snapshot* GetSnapshot() const {
    AssertIsOnOwningThread();
    return mSnapshot;
  }

  void RequestAllowToClose();

  void ForceKill();

  void Stringify(nsACString& aResult) const;

  NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Database, override)

 private:
  // Reference counted.
  ~Database();

  void AllowToClose();

  // IPDL methods are only called by IPDL.
  void ActorDestroy(ActorDestroyReason aWhy) override;

  mozilla::ipc::IPCResult RecvAllowToClose() override;

  PBackgroundLSSnapshotParent* AllocPBackgroundLSSnapshotParent(
      const nsAString& aDocumentURI, const nsAString& aKey,
      const bool& aIncreasePeakUsage, const int64_t& aMinSize,
      LSSnapshotInitInfo* aInitInfo) override;

  mozilla::ipc::IPCResult RecvPBackgroundLSSnapshotConstructor(
      PBackgroundLSSnapshotParent* aActor, const nsAString& aDocumentURI,
      const nsAString& aKey, const bool& aIncreasePeakUsage,
      const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) override;

  bool DeallocPBackgroundLSSnapshotParent(
      PBackgroundLSSnapshotParent* aActor) override;
};

/**
 * Attempts to capture the state of the underlying Datastore at the time of its
 * creation so run-to-completion semantics can be honored.
 *
 * Rather than simply duplicate the contents of `DataStore::mValues` and
 * `Datastore::mOrderedItems` at the time of their creation, the Snapshot tracks
 * mutations to the Datastore as they happen, saving off the state of values as
 * they existed when the Snapshot was created.  In other words, given an initial
 * Datastore state of { foo: 'bar', bar: 'baz' }, the Snapshot won't store those
 * values until it hears via `SaveItem` that "foo" is being over-written.  At
 * that time, it will save off foo='bar' in mValues.
 *
 * ## Quota Allocation ##
 *
 * ## States ##
 *
 */

class Snapshot final : public PBackgroundLSSnapshotParent {
  /**
   * The Database that owns this snapshot.  There is a 1:1 relationship between
   * snapshots and databases.
   */

  RefPtr<Database> mDatabase;
  RefPtr<Datastore> mDatastore;
  /**
   * The set of keys for which values have been sent to the child LSSnapshot.
   * Cleared once all values have been sent as indicated by
   * mLoadedItems.Count()==mTotalLength and therefore mLoadedAllItems should be
   * true.  No requests should be received for keys already in this set, and
   * this is enforced by fatal IPC error (unless fuzzing).
   */

  nsTHashtable<nsStringHashKey> mLoadedItems;
  /**
   * The set of keys for which a RecvLoadValueAndMoreItems request was received
   * but there was no such key, and so null was returned.  The child LSSnapshot
   * will also cache these values, so redundant requests are also handled with
   * fatal process termination just like for mLoadedItems.  Also cleared when
   * mLoadedAllItems becomes true because then the child can infer that all
   * other values must be null.  (Note: this could also be done when
   * mLoadKeysReceived is true as a further optimization, but is not.)
   */

  nsTHashSet<nsString> mUnknownItems;
  /**
   * Values that have changed in mDatastore as reported by SaveItem
   * notifications that are not yet known to the child LSSnapshot.
   *
   * The naive way to snapshot the state of mDatastore would be to duplicate its
   * internal mValues at the time of our creation, but that is wasteful if few
   * changes are made to the Datastore's state.  So we only track values that
   * are changed/evicted from the Datastore as they happen, as reported to us by
   * SaveItem notifications.
   */

  nsTHashMap<nsStringHashKey, LSValue> mValues;
  /**
   * Latched state of mDatastore's keys during a SaveItem notification with
--> --------------------

--> maximum size reached

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

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

¤ Dauer der Verarbeitung: 0.47 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.