Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/dom/quota/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 302 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 "CanonicalQuotaObject.h"
#include "ClientUsageArray.h"
#include "Flatten.h"
#include "FirstInitializationAttemptsImpl.h"
#include "InitializationUtils.h"
#include "GroupInfo.h"
#include "GroupInfoPair.h"
#include "NormalOriginOperationBase.h"
#include "OriginOperationBase.h"
#include "OriginOperations.h"
#include "OriginParser.h"
#include "OriginScope.h"
#include "OriginInfo.h"
#include "QuotaCommon.h"
#include "QuotaManager.h"
#include "QuotaPrefs.h"
#include "ResolvableNormalOriginOp.h"
#include "SanitizationUtils.h"
#include "ScopedLogExtraInfo.h"
#include "UsageInfo.h"

// Global includes
#include <cinttypes>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cstdint>
#include <functional>
#include <new>
#include <numeric>
#include <tuple>
#include <type_traits>
#include <utility>
#include "DirectoryLockImpl.h"
#include "ErrorList.h"
#include "MainThreadUtils.h"
#include "mozIStorageAsyncConnection.h"
#include "mozIStorageConnection.h"
#include "mozIStorageService.h"
#include "mozIStorageStatement.h"
#include "mozStorageCID.h"
#include "mozStorageHelper.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/CondVar.h"
#include "mozilla/InitializedOnce.h"
#include "mozilla/Logging.h"
#include "mozilla/MacroForEach.h"
#include "mozilla/Maybe.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/SpinEventLoopUntil.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/SystemPrincipal.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TelemetryHistogramEnums.h"
#include "mozilla/TextUtils.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/Variant.h"
#include "mozilla/dom/FileSystemQuotaClientFactory.h"
#include "mozilla/dom/FlippedOnce.h"
#include "mozilla/dom/IndexedDatabaseManager.h"
#include "mozilla/dom/LocalStorageCommon.h"
#include "mozilla/dom/StorageDBUpdater.h"
#include "mozilla/dom/cache/QuotaClient.h"
#include "mozilla/dom/indexedDB/ActorsParent.h"
#include "mozilla/dom/ipc/IdType.h"
#include "mozilla/dom/localstorage/ActorsParent.h"
#include "mozilla/dom/quota/ArtificialFailure.h"
#include "mozilla/dom/quota/AssertionsImpl.h"
#include "mozilla/dom/quota/CheckedUnsafePtr.h"
#include "mozilla/dom/quota/Client.h"
#include "mozilla/dom/quota/ClientDirectoryLock.h"
#include "mozilla/dom/quota/Config.h"
#include "mozilla/dom/quota/Constants.h"
#include "mozilla/dom/quota/DirectoryLockInlines.h"
#include "mozilla/dom/quota/FileUtils.h"
#include "mozilla/dom/quota/MozPromiseUtils.h"
#include "mozilla/dom/quota/OriginDirectoryLock.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/dom/quota/PrincipalUtils.h"
#include "mozilla/dom/quota/QuotaManagerImpl.h"
#include "mozilla/dom/quota/QuotaManagerService.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "mozilla/dom/quota/ScopedLogExtraInfo.h"
#include "mozilla/dom/quota/StreamUtils.h"
#include "mozilla/dom/quota/UniversalDirectoryLock.h"
#include "mozilla/dom/quota/ThreadUtils.h"
#include "mozilla/dom/simpledb/ActorsParent.h"
#include "mozilla/fallible.h"
#include "mozilla/glean/DomQuotaMetrics.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/net/ExtensionProtocolHandler.h"
#include "mozilla/StorageOriginAttributes.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsBaseHashtable.h"
#include "nsCOMPtr.h"
#include "nsCRTGlue.h"
#include "nsClassHashtable.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsDirectoryServiceUtils.h"
#include "nsError.h"
#include "nsIBinaryInputStream.h"
#include "nsIBinaryOutputStream.h"
#include "nsIConsoleService.h"
#include "nsIDirectoryEnumerator.h"
#include "nsIDUtils.h"
#include "nsIEventTarget.h"
#include "nsIFile.h"
#include "nsIFileStreams.h"
#include "nsIInputStream.h"
#include "nsIObjectInputStream.h"
#include "nsIObjectOutputStream.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsIOutputStream.h"
#include "nsIQuotaManagerServiceInternal.h"
#include "nsIQuotaRequests.h"
#include "nsIQuotaUtilsService.h"
#include "nsIPlatformInfo.h"
#include "nsIPrincipal.h"
#include "nsIRunnable.h"
#include "nsISupports.h"
#include "nsIThread.h"
#include "nsITimer.h"
#include "nsIURI.h"
#include "nsIWidget.h"
#include "nsLiteralString.h"
#include "nsNetUtil.h"
#include "nsPrintfCString.h"
#include "nsStandardURL.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsStringFlags.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "nsTHashtable.h"
#include "nsTLiteralString.h"
#include "nsTPromiseFlatString.h"
#include "nsTStringRepr.h"
#include "nsThreadUtils.h"
#include "nsURLHelper.h"
#include "nsXPCOM.h"
#include "nsXPCOMCID.h"
#include "nsXULAppAPI.h"
#include "prinrval.h"
#include "prio.h"
#include "prtime.h"

// The amount of time, in milliseconds, that our IO thread will stay alive
// after the last event it processes.
#define DEFAULT_THREAD_TIMEOUT_MS 30000

/**
 * If shutdown takes this long, kill actors of a quota client, to avoid reaching
 * the crash timeout.
 */

#define SHUTDOWN_KILL_ACTORS_TIMEOUT_MS 5000

/**
 * Automatically crash the browser if shutdown of a quota client takes this
 * long. We've chosen a value that is long enough that it is unlikely for the
 * problem to be falsely triggered by slow system I/O.  We've also chosen a
 * value long enough so that automated tests should time out and fail if
 * shutdown of a quota client takes too long.  Also, this value is long enough
 * so that testers can notice the timeout; we want to know about the timeouts,
 * not hide them. On the other hand this value is less than 60 seconds which is
 * used by nsTerminator to crash a hung main process.
 */

#define SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS 45000

static_assert(
    SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS > SHUTDOWN_KILL_ACTORS_TIMEOUT_MS,
    "The kill actors timeout must be shorter than the crash browser one.");

// profile-before-change, when we need to shut down quota manager
#define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm"

#define KB *1024ULL
#define MB *1024ULL KB
#define GB *1024ULL MB

namespace mozilla::dom::quota {

using namespace mozilla::ipc;

// We want profiles to be platform-independent so we always need to replace
// the same characters on every platform. Windows has the most extensive set
// of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and
// FILE_PATH_SEPARATOR.
const char QuotaManager::kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\";
const char16_t QuotaManager::kReplaceChars16[] =
    u"" CONTROL_CHARACTERS "/:*?\"<>|\\";

namespace {

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


const uint32_t kSQLitePageSizeOverride = 512;

// Important version history:
// - Bug 1290481 bumped our schema from major.minor 2.0 to 3.0 in Firefox 57
//   which caused Firefox 57 release concerns because the major schema upgrade
//   means anyone downgrading to Firefox 56 will experience a non-operational
//   QuotaManager and all of its clients.
// - Bug 1404344 got very concerned about that and so we decided to effectively
//   rename 3.0 to 2.1, effective in Firefox 57.  This works because post
//   storage.sqlite v1.0, QuotaManager doesn't care about minor storage version
//   increases.  It also works because all the upgrade did was give the DOM
//   Cache API QuotaClient an opportunity to create its newly added .padding
//   files during initialization/upgrade, which isn't functionally necessary as
//   that can be done on demand.

// Major storage version. Bump for backwards-incompatible changes.
// (The next major version should be 4 to distinguish from the Bug 1290481
// downgrade snafu.)
const uint32_t kMajorStorageVersion = 2;

// Minor storage version. Bump for backwards-compatible changes.
const uint32_t kMinorStorageVersion = 3;

// The storage version we store in the SQLite database is a (signed) 32-bit
// integer. The major version is left-shifted 16 bits so the max value is
// 0xFFFF. The minor version occupies the lower 16 bits and its max is 0xFFFF.
static_assert(kMajorStorageVersion <= 0xFFFF,
              "Major version needs to fit in 16 bits.");
static_assert(kMinorStorageVersion <= 0xFFFF,
              "Minor version needs to fit in 16 bits.");

const int32_t kStorageVersion =
    int32_t((kMajorStorageVersion << 16) + kMinorStorageVersion);

// See comments above about why these are a thing.
const int32_t kHackyPreDowngradeStorageVersion = int32_t((3 << 16) + 0);
const int32_t kHackyPostDowngradeStorageVersion = int32_t((2 << 16) + 1);

const char kAboutHomeOriginPrefix[] = "moz-safe-about:home";
const char kIndexedDBOriginPrefix[] = "indexeddb://";
const char kResourceOriginPrefix[] = "resource://";

constexpr auto kStorageName = u"storage"_ns;

#define INDEXEDDB_DIRECTORY_NAME u"indexedDB"
#define ARCHIVES_DIRECTORY_NAME u"archives"
#define PERSISTENT_DIRECTORY_NAME u"persistent"
#define PERMANENT_DIRECTORY_NAME u"permanent"
#define TEMPORARY_DIRECTORY_NAME u"temporary"
#define DEFAULT_DIRECTORY_NAME u"default"
#define PRIVATE_DIRECTORY_NAME u"private"
#define TOBEREMOVED_DIRECTORY_NAME u"to-be-removed"

#define WEB_APPS_STORE_FILE_NAME u"webappsstore.sqlite"
#define LS_ARCHIVE_FILE_NAME u"ls-archive.sqlite"
#define LS_ARCHIVE_TMP_FILE_NAME u"ls-archive-tmp.sqlite"

const int32_t kLocalStorageArchiveVersion = 4;

const char kProfileDoChangeTopic[] = "profile-do-change";
const char kContextualIdentityServiceLoadFinishedTopic[] =
    "contextual-identity-service-load-finished";
const char kPrivateBrowsingObserverTopic[] = "last-pb-context-exited";

const int32_t kCacheVersion = 2;

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


int32_t MakeStorageVersion(uint32_t aMajorStorageVersion,
                           uint32_t aMinorStorageVersion) {
  return int32_t((aMajorStorageVersion << 16) + aMinorStorageVersion);
}

uint32_t GetMajorStorageVersion(int32_t aStorageVersion) {
  return uint32_t(aStorageVersion >> 16);
}

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

  // Table `database`
  QM_TRY(MOZ_TO_RESULT(
      aConnection->ExecuteSimpleSQL("CREATE TABLE database"
                                    "( cache_version INTEGER NOT NULL DEFAULT 0"
                                    ");"_ns)));

#ifdef DEBUG
  {
    QM_TRY_INSPECT(const int32_t& storageVersion,
                   MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));

    MOZ_ASSERT(storageVersion == 0);
  }
#endif

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

  return NS_OK;
}

Result<int32_t, nsresult> LoadCacheVersion(mozIStorageConnection& aConnection) {
  AssertIsOnIOThread();

  QM_TRY_INSPECT(const auto& stmt,
                 CreateAndExecuteSingleStepStatement<
                     SingleStepResult::ReturnNullIfNoResult>(
                     aConnection, "SELECT cache_version FROM database"_ns));

  QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));

  QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
}

nsresult SaveCacheVersion(mozIStorageConnection& aConnection,
                          int32_t aVersion) {
  AssertIsOnIOThread();

  QM_TRY_INSPECT(
      const auto& stmt,
      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
          nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
          "UPDATE database SET cache_version = :version;"_ns));

  QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("version"_ns, aVersion)));

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

  return NS_OK;
}

nsresult CreateCacheTables(mozIStorageConnection& aConnection) {
  AssertIsOnIOThread();

  // Table `cache`
  QM_TRY(MOZ_TO_RESULT(
      aConnection.ExecuteSimpleSQL("CREATE TABLE cache"
                                   "( valid INTEGER NOT NULL DEFAULT 0"
                                   ", build_id TEXT NOT NULL DEFAULT ''"
                                   ");"_ns)));

  // Table `repository`
  QM_TRY(
      MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL("CREATE TABLE repository"
                                                 "( id INTEGER PRIMARY KEY"
                                                 ", name TEXT NOT NULL"
                                                 ");"_ns)));

  // Table `origin`
  QM_TRY(MOZ_TO_RESULT(
      aConnection.ExecuteSimpleSQL("CREATE TABLE origin"
                                   "( repository_id INTEGER NOT NULL"
                                   ", suffix TEXT"
                                   ", group_ TEXT NOT NULL"
                                   ", origin TEXT NOT NULL"
                                   ", client_usages TEXT NOT NULL"
                                   ", usage INTEGER NOT NULL"
                                   ", last_access_time INTEGER NOT NULL"
                                   ", accessed INTEGER NOT NULL"
                                   ", persisted INTEGER NOT NULL"
                                   ", PRIMARY KEY (repository_id, origin)"
                                   ", FOREIGN KEY (repository_id) "
                                   "REFERENCES repository(id) "
                                   ");"_ns)));

#ifdef DEBUG
  {
    QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection));
    MOZ_ASSERT(cacheVersion == 0);
  }
#endif

  QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, kCacheVersion)));

  return NS_OK;
}

OkOrErr InvalidateCache(mozIStorageConnection& aConnection) {
  AssertIsOnIOThread();

  static constexpr auto kDeleteCacheQuery = "DELETE FROM origin;"_ns;
  static constexpr auto kSetInvalidFlagQuery = "UPDATE cache SET valid = 0"_ns;

  QM_TRY(QM_OR_ELSE_WARN(
      // Expression.
      ([&]() -> OkOrErr {
        mozStorageTransaction transaction(&aConnection,
                                          /*aCommitOnComplete */ false);

        QM_TRY(QM_TO_RESULT(transaction.Start()));
        QM_TRY(QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kDeleteCacheQuery)));
        QM_TRY(
            QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery)));
        QM_TRY(QM_TO_RESULT(transaction.Commit()));

        return Ok{};
      }()),
      // Fallback.
      ([&](const QMResult& rv) -> OkOrErr {
        QM_TRY(
            QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery)));

        return Ok{};
      })));

  return Ok{};
}

nsresult UpgradeCacheFrom1To2(mozIStorageConnection& aConnection) {
  AssertIsOnIOThread();

  QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
      "ALTER TABLE origin ADD COLUMN suffix TEXT"_ns)));

  QM_TRY(InvalidateCache(aConnection));

#ifdef DEBUG
  {
    QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection));

    MOZ_ASSERT(cacheVersion == 1);
  }
#endif

  QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, 2)));

  return NS_OK;
}

Result<bool, nsresult> MaybeCreateOrUpgradeCache(
    mozIStorageConnection& aConnection) {
  bool cacheUsable = true;

  QM_TRY_UNWRAP(int32_t cacheVersion, LoadCacheVersion(aConnection));

  if (cacheVersion > kCacheVersion) {
    cacheUsable = false;
  } else if (cacheVersion != kCacheVersion) {
    const bool newCache = !cacheVersion;

    mozStorageTransaction transaction(
        &aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);

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

    if (newCache) {
      QM_TRY(MOZ_TO_RESULT(CreateCacheTables(aConnection)));

#ifdef DEBUG
      {
        QM_TRY_INSPECT(const int32_t& cacheVersion,
                       LoadCacheVersion(aConnection));
        MOZ_ASSERT(cacheVersion == kCacheVersion);
      }
#endif

      QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
          nsLiteralCString("INSERT INTO cache (valid, build_id) "
                           "VALUES (0, '')"))));

      nsCOMPtr<mozIStorageStatement> insertStmt;

      for (const PersistenceType persistenceType : kAllPersistenceTypes) {
        if (insertStmt) {
          MOZ_ALWAYS_SUCCEEDS(insertStmt->Reset());
        } else {
          QM_TRY_UNWRAP(insertStmt, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                                        nsCOMPtr<mozIStorageStatement>,
                                        aConnection, CreateStatement,
                                        "INSERT INTO repository (id, name) "
                                        "VALUES (:id, :name)"_ns));
        }

        QM_TRY(MOZ_TO_RESULT(
            insertStmt->BindInt32ByName("id"_ns, persistenceType)));

        QM_TRY(MOZ_TO_RESULT(insertStmt->BindUTF8StringByName(
            "name"_ns, PersistenceTypeToString(persistenceType))));

        QM_TRY(MOZ_TO_RESULT(insertStmt->Execute()));
      }
    } else {
      // This logic needs to change next time we change the cache!
      static_assert(kCacheVersion == 2,
                    "Upgrade function needed due to cache version increase.");

      while (cacheVersion != kCacheVersion) {
        if (cacheVersion == 1) {
          QM_TRY(MOZ_TO_RESULT(UpgradeCacheFrom1To2(aConnection)));
        } else {
          QM_FAIL(Err(NS_ERROR_FAILURE), []() {
            QM_WARNING(
                "Unable to initialize cache, no upgrade path is "
                "available!");
          });
        }

        QM_TRY_UNWRAP(cacheVersion, LoadCacheVersion(aConnection));
      }

      MOZ_ASSERT(cacheVersion == kCacheVersion);
    }

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

  return cacheUsable;
}

Result<nsCOMPtr<mozIStorageConnection>, nsresult> CreateWebAppsStoreConnection(
    nsIFile& aWebAppsStoreFile, mozIStorageService& aStorageService) {
  AssertIsOnIOThread();

  // Check if the old database exists at all.
  QM_TRY_INSPECT(const bool& exists,
                 MOZ_TO_RESULT_INVOKE_MEMBER(aWebAppsStoreFile, Exists));

  if (!exists) {
    // webappsstore.sqlite doesn't exist, return a null connection.
    return nsCOMPtr<mozIStorageConnection>{};
  }

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

  if (isDirectory) {
    QM_WARNING("webappsstore.sqlite is not a file!");
    return nsCOMPtr<mozIStorageConnection>{};
  }

  QM_TRY_INSPECT(const auto& connection,
                 QM_OR_ELSE_WARN_IF(
                     // Expression.
                     MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                         nsCOMPtr<mozIStorageConnection>, aStorageService,
                         OpenUnsharedDatabase, &aWebAppsStoreFile,
                         mozIStorageService::CONNECTION_DEFAULT),
                     // Predicate.
                     IsDatabaseCorruptionError,
                     // Fallback. Don't throw an error, leave a corrupted
                     // webappsstore database as it is.
                     ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));

  if (connection) {
    // Don't propagate an error, leave a non-updateable webappsstore database as
    // it is.
    QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::Update(connection)),
           nsCOMPtr<mozIStorageConnection>{});
  }

  return connection;
}

Result<nsCOMPtr<nsIFile>, QMResult> GetLocalStorageArchiveFile(
    const nsAString& aDirectoryPath) {
  AssertIsOnIOThread();
  MOZ_ASSERT(!aDirectoryPath.IsEmpty());

  QM_TRY_UNWRAP(auto lsArchiveFile,
                QM_TO_RESULT_TRANSFORM(QM_NewLocalFile(aDirectoryPath)));

  QM_TRY(QM_TO_RESULT(
      lsArchiveFile->Append(nsLiteralString(LS_ARCHIVE_FILE_NAME))));

  return lsArchiveFile;
}

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

  QM_TRY_UNWRAP(auto lsArchiveTmpFile, QM_NewLocalFile(aDirectoryPath));

  QM_TRY(MOZ_TO_RESULT(
      lsArchiveTmpFile->Append(nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME))));

  return lsArchiveTmpFile;
}

Result<bool, nsresult> IsLocalStorageArchiveInitialized(
    mozIStorageConnection& aConnection) {
  AssertIsOnIOThread();

  QM_TRY_RETURN(
      MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, TableExists, "database"_ns));
}

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

#ifdef DEBUG
  {
    QM_TRY_INSPECT(const auto& initialized,
                   IsLocalStorageArchiveInitialized(*aConnection));
    MOZ_ASSERT(!initialized);
  }
#endif

  QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
      "CREATE TABLE database(version INTEGER NOT NULL DEFAULT 0);"_ns)));

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

  QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("version"_ns, 0)));
  QM_TRY(MOZ_TO_RESULT(stmt->Execute()));

  return NS_OK;
}

Result<int32_t, nsresult> LoadLocalStorageArchiveVersion(
    mozIStorageConnection& aConnection) {
  AssertIsOnIOThread();

  QM_TRY_INSPECT(const auto& stmt,
                 CreateAndExecuteSingleStepStatement<
                     SingleStepResult::ReturnNullIfNoResult>(
                     aConnection, "SELECT version FROM database"_ns));

  QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));

  QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
}

nsresult SaveLocalStorageArchiveVersion(mozIStorageConnection* aConnection,
                                        int32_t aVersion) {
  AssertIsOnIOThread();
  MOZ_ASSERT(aConnection);

  nsCOMPtr<mozIStorageStatement> stmt;
  nsresult rv = aConnection->CreateStatement(
      "UPDATE database SET version = :version;"_ns, getter_AddRefs(stmt));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = stmt->BindInt32ByName("version"_ns, aVersion);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = stmt->Execute();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  return NS_OK;
}

template <typename FileFunc, typename DirectoryFunc>
Result<mozilla::Ok, nsresult> CollectEachFileEntry(
    nsIFile& aDirectory, const FileFunc& aFileFunc,
    const DirectoryFunc& aDirectoryFunc) {
  AssertIsOnIOThread();

  return CollectEachFile(
      aDirectory,
      [&aFileFunc, &aDirectoryFunc](
          const nsCOMPtr<nsIFile>& file) -> Result<mozilla::Ok, nsresult> {
        QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));

        switch (dirEntryKind) {
          case nsIFileKind::ExistsAsDirectory:
            return aDirectoryFunc(file);

          case nsIFileKind::ExistsAsFile:
            return aFileFunc(file);

          case nsIFileKind::DoesNotExist:
            // Ignore files that got removed externally while iterating.
            break;
        }

        return Ok{};
      });
}

/******************************************************************************
 * Quota manager class declarations
 ******************************************************************************/


}  // namespace

class QuotaManager::Observer final : public nsIObserver {
  static Observer* sInstance;

  bool mPendingProfileChange;
  bool mShutdownComplete;

 public:
  static nsresult Initialize();

  static nsIObserver* GetInstance();

  static void ShutdownCompleted();

 private:
  Observer() : mPendingProfileChange(false), mShutdownComplete(false) {
    MOZ_ASSERT(NS_IsMainThread());
  }

  ~Observer() { MOZ_ASSERT(NS_IsMainThread()); }

  nsresult Init();

  nsresult Shutdown();

  NS_DECL_ISUPPORTS
  NS_DECL_NSIOBSERVER
};

namespace {

/*******************************************************************************
 * Local class declarations
 ******************************************************************************/


}  // namespace

namespace {

class CollectOriginsHelper final : public Runnable {
  uint64_t mMinSizeToBeFreed;

  Mutex& mMutex;
  CondVar mCondVar;

  // The members below are protected by mMutex.
  nsTArray<RefPtr<OriginDirectoryLock>> mLocks;
  uint64_t mSizeToBeFreed;
  bool mWaiting;

 public:
  CollectOriginsHelper(mozilla::Mutex& aMutex, uint64_t aMinSizeToBeFreed);

  // Blocks the current thread until origins are collected on the main thread.
  // The returned value contains an aggregate size of those origins.
  int64_t BlockAndReturnOriginsForEviction(
      nsTArray<RefPtr<OriginDirectoryLock>>& aLocks);

 private:
  ~CollectOriginsHelper() = default;

  NS_IMETHOD
  Run() override;
};

/*******************************************************************************
 * Other class declarations
 ******************************************************************************/


class RecordTimeDeltaHelper final : public Runnable {
  const Telemetry::HistogramID mHistogram;

  // TimeStamps that are set on the IO thread.
  LazyInitializedOnceNotNull<const TimeStamp> mStartTime;
  LazyInitializedOnceNotNull<const TimeStamp> mEndTime;

  // A TimeStamp that is set on the main thread.
  LazyInitializedOnceNotNull<const TimeStamp> mInitializedTime;

 public:
  explicit RecordTimeDeltaHelper(const Telemetry::HistogramID aHistogram)
      : Runnable("dom::quota::RecordTimeDeltaHelper"), mHistogram(aHistogram) {}

  TimeStamp Start();

  TimeStamp End();

 private:
  ~RecordTimeDeltaHelper() = default;

  NS_DECL_NSIRUNNABLE
};

/*******************************************************************************
 * Helper classes
 ******************************************************************************/


/*******************************************************************************
 * Helper Functions
 ******************************************************************************/


// Return whether the group was actually updated.
Result<bool, nsresult> MaybeUpdateGroupForOrigin(
    OriginMetadata& aOriginMetadata) {
  MOZ_ASSERT(!NS_IsMainThread());

  bool updated = false;

  if (aOriginMetadata.mOrigin.EqualsLiteral(kChromeOrigin)) {
    if (!aOriginMetadata.mGroup.EqualsLiteral(kChromeOrigin)) {
      aOriginMetadata.mGroup.AssignLiteral(kChromeOrigin);
      updated = true;
    }
  } else {
    nsCOMPtr<nsIPrincipal> principal =
        BasePrincipal::CreateContentPrincipal(aOriginMetadata.mOrigin);
    QM_TRY(MOZ_TO_RESULT(principal));

    QM_TRY_INSPECT(const auto& baseDomain,
                   MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, principal,
                                                     GetBaseDomain));

    const nsCString upToDateGroup = baseDomain + aOriginMetadata.mSuffix;

    if (aOriginMetadata.mGroup != upToDateGroup) {
      aOriginMetadata.mGroup = upToDateGroup;
      updated = true;
    }
  }

  return updated;
}

Result<bool, nsresult> MaybeUpdateLastAccessTimeForOrigin(
    FullOriginMetadata& aFullOriginMetadata) {
  MOZ_ASSERT(!NS_IsMainThread());

  if (aFullOriginMetadata.mLastAccessTime == INT64_MIN) {
    QuotaManager* quotaManager = QuotaManager::Get();
    MOZ_ASSERT(quotaManager);

    QM_TRY_INSPECT(const auto& metadataFile,
                   quotaManager->GetOriginDirectory(aFullOriginMetadata));

    QM_TRY(MOZ_TO_RESULT(
        metadataFile->Append(nsLiteralString(METADATA_V2_FILE_NAME))));

    QM_TRY_UNWRAP(int64_t timestamp, MOZ_TO_RESULT_INVOKE_MEMBER(
                                         metadataFile, GetLastModifiedTime));

    // Need to convert from milliseconds to microseconds.
    MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp);
    timestamp *= int64_t(PR_USEC_PER_MSEC);

    aFullOriginMetadata.mLastAccessTime = timestamp;

    return true;
  }

  return false;
}

}  // namespace

void ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr) {
  // Get leaf of file path
  for (const char* p = aFile; *p; ++p) {
    if (*p == '/' && *(p + 1)) {
      aFile = p + 1;
    }
  }

  nsContentUtils::LogSimpleConsoleError(
      NS_ConvertUTF8toUTF16(
          nsPrintfCString("Quota %s: %s:%" PRIu32, aStr, aFile, aLine)),
      "quota"_ns,
      false /* Quota Manager is not active in private browsing mode */,
      true /* Quota Manager runs always in a chrome context */);
}

namespace {

bool gInvalidateQuotaCache = false;
StaticAutoPtr<nsString> gBasePath;
StaticAutoPtr<nsString> gStorageName;
StaticAutoPtr<nsCString> gBuildId;

#ifdef DEBUG
bool gQuotaManagerInitialized = false;
#endif

StaticRefPtr<QuotaManager> gInstance;
mozilla::Atomic<bool> gShutdown(false);

// A time stamp that can only be accessed on the main thread.
TimeStamp gLastOSWake;

// XXX Move to QuotaManager once NormalOriginOperationBase is declared in a
// separate and includable file.
using NormalOriginOpArray =
    nsTArray<CheckedUnsafePtr<NormalOriginOperationBase>>;
StaticAutoPtr<NormalOriginOpArray> gNormalOriginOps;

class StorageOperationBase {
 protected:
  struct OriginProps {
    enum Type { eChrome, eContent, eObsolete, eInvalid };

    NotNull<nsCOMPtr<nsIFile>> mDirectory;
    nsString mLeafName;
    nsCString mSpec;
    OriginAttributes mAttrs;
    int64_t mTimestamp;
    OriginMetadata mOriginMetadata;
    nsCString mOriginalSuffix;

    LazyInitializedOnceEarlyDestructible<const PersistenceType>
        mPersistenceType;
    Type mType;
    bool mNeedsRestore;
    bool mNeedsRestore2;
    bool mIgnore;

   public:
    explicit OriginProps(MovingNotNull<nsCOMPtr<nsIFile>> aDirectory)
        : mDirectory(std::move(aDirectory)),
          mTimestamp(0),
          mType(eContent),
          mNeedsRestore(false),
          mNeedsRestore2(false),
          mIgnore(false) {}

    template <typename PersistenceTypeFunc>
    nsresult Init(PersistenceTypeFunc&& aPersistenceTypeFunc);
  };

  nsTArray<OriginProps> mOriginProps;

  nsCOMPtr<nsIFile> mDirectory;

 public:
  explicit StorageOperationBase(nsIFile* aDirectory) : mDirectory(aDirectory) {
    AssertIsOnIOThread();
  }

  NS_INLINE_DECL_REFCOUNTING(StorageOperationBase)

 protected:
  virtual ~StorageOperationBase() = default;

  nsresult GetDirectoryMetadata(nsIFile* aDirectory, int64_t& aTimestamp,
                                nsACString& aGroup, nsACString& aOrigin,
                                Nullable<bool>& aIsApp);

  // Upgrade helper to load the contents of ".metadata-v2" files from previous
  // schema versions.  Although QuotaManager has a similar GetDirectoryMetadata2
  // method, it is only intended to read current version ".metadata-v2" files.
  // And unlike the old ".metadata" files, the ".metadata-v2" format can evolve
  // because our "storage.sqlite" lets us track the overall version of the
  // storage directory.
  nsresult GetDirectoryMetadata2(nsIFile* aDirectory, int64_t& aTimestamp,
                                 nsACString& aSuffix, nsACString& aGroup,
                                 nsACString& aOrigin, bool& aIsApp);

  int64_t GetOriginLastModifiedTime(const OriginProps& aOriginProps);

  nsresult RemoveObsoleteOrigin(const OriginProps& aOriginProps);

  /**
   * Rename the origin if the origin string generation from nsIPrincipal
   * changed. This consists of renaming the origin in the metadata files and
   * renaming the origin directory itself. For simplicity, the origin in
   * metadata files is not actually updated, but the metadata files are
   * recreated instead.
   *
   * @param  aOriginProps the properties of the origin to check.
   *
   * @return whether origin was renamed.
   */

  Result<bool, nsresult> MaybeRenameOrigin(const OriginProps& aOriginProps);

  nsresult ProcessOriginDirectories();

  virtual nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) = 0;
};

class RepositoryOperationBase : public StorageOperationBase {
 public:
  explicit RepositoryOperationBase(nsIFile* aDirectory)
      : StorageOperationBase(aDirectory) {}

  nsresult ProcessRepository();

 protected:
  virtual ~RepositoryOperationBase() = default;

  template <typename UpgradeMethod>
  nsresult MaybeUpgradeClients(const OriginProps& aOriginsProps,
                               UpgradeMethod aMethod);

 private:
  virtual PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) = 0;

  virtual nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
                                          bool* aRemoved) = 0;

  virtual nsresult PrepareClientDirectory(nsIFile* aFile,
                                          const nsAString& aLeafName,
                                          bool& aRemoved);
};

class CreateOrUpgradeDirectoryMetadataHelper final
    : public RepositoryOperationBase {
  nsCOMPtr<nsIFile> mPermanentStorageDir;

  // The legacy PersistenceType, before the default repository introduction.
  enum class LegacyPersistenceType {
    Persistent = 0,
    Temporary
    // The PersistenceType had also PERSISTENCE_TYPE_INVALID, but we don't need
    // it here.
  };

  LazyInitializedOnce<const LegacyPersistenceType> mLegacyPersistenceType;

 public:
  explicit CreateOrUpgradeDirectoryMetadataHelper(nsIFile* aDirectory)
      : RepositoryOperationBase(aDirectory) {}

  nsresult Init();

 private:
  Maybe<LegacyPersistenceType> LegacyPersistenceTypeFromFile(nsIFile& aFile,
                                                             const fallible_t&);

  PersistenceType PersistenceTypeFromLegacyPersistentSpec(
      const nsCString& aSpec);

  PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) override;

  nsresult MaybeUpgradeOriginDirectory(nsIFile* aDirectory);

  nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
                                  bool* aRemoved) override;

  nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
};

class UpgradeStorageHelperBase : public RepositoryOperationBase {
  LazyInitializedOnce<const PersistenceType> mPersistenceType;

 public:
  explicit UpgradeStorageHelperBase(nsIFile* aDirectory)
      : RepositoryOperationBase(aDirectory) {}

  nsresult Init();

 private:
  PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) override;
};

class UpgradeStorageFrom0_0To1_0Helper final : public UpgradeStorageHelperBase {
 public:
  explicit UpgradeStorageFrom0_0To1_0Helper(nsIFile* aDirectory)
      : UpgradeStorageHelperBase(aDirectory) {}

 private:
  nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
                                  bool* aRemoved) override;

  nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
};

class UpgradeStorageFrom1_0To2_0Helper final : public UpgradeStorageHelperBase {
 public:
  explicit UpgradeStorageFrom1_0To2_0Helper(nsIFile* aDirectory)
      : UpgradeStorageHelperBase(aDirectory) {}

 private:
  nsresult MaybeRemoveMorgueDirectory(const OriginProps& aOriginProps);

  /**
   * Remove the origin directory if appId is present in origin attributes.
   *
   * @param aOriginProps the properties of the origin to check.
   *
   * @return whether the origin directory was removed.
   */

  Result<bool, nsresult> MaybeRemoveAppsData(const OriginProps& aOriginProps);

  nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
                                  bool* aRemoved) override;

  nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
};

class UpgradeStorageFrom2_0To2_1Helper final : public UpgradeStorageHelperBase {
 public:
  explicit UpgradeStorageFrom2_0To2_1Helper(nsIFile* aDirectory)
      : UpgradeStorageHelperBase(aDirectory) {}

 private:
  nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
                                  bool* aRemoved) override;

  nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
};

class UpgradeStorageFrom2_1To2_2Helper final : public UpgradeStorageHelperBase {
 public:
  explicit UpgradeStorageFrom2_1To2_2Helper(nsIFile* aDirectory)
      : UpgradeStorageHelperBase(aDirectory) {}

 private:
  nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
                                  bool* aRemoved) override;

  nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;

  nsresult PrepareClientDirectory(nsIFile* aFile, const nsAString& aLeafName,
                                  bool& aRemoved) override;
};

class RestoreDirectoryMetadata2Helper final : public StorageOperationBase {
  LazyInitializedOnce<const PersistenceType> mPersistenceType;

 public:
  explicit RestoreDirectoryMetadata2Helper(nsIFile* aDirectory)
      : StorageOperationBase(aDirectory) {}

  nsresult Init();

  nsresult RestoreMetadata2File();

 private:
  nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
};

Result<nsAutoString, nsresult> GetPathForStorage(
    nsIFile& aBaseDir, const nsAString& aStorageName) {
  QM_TRY_INSPECT(const auto& storageDir,
                 CloneFileAndAppend(aBaseDir, aStorageName));

  QM_TRY_RETURN(
      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, storageDir, GetPath));
}

int64_t GetLastModifiedTime(PersistenceType aPersistenceType, nsIFile& aFile) {
  AssertIsOnIOThread();

  class MOZ_STACK_CLASS Helper final {
   public:
    static nsresult GetLastModifiedTime(nsIFile* aFile, int64_t* aTimestamp) {
      AssertIsOnIOThread();
      MOZ_ASSERT(aFile);
      MOZ_ASSERT(aTimestamp);

      QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*aFile));

      switch (dirEntryKind) {
        case nsIFileKind::ExistsAsDirectory:
          QM_TRY(CollectEachFile(
              *aFile,
              [&aTimestamp](const nsCOMPtr<nsIFile>& file)
                  -> Result<mozilla::Ok, nsresult> {
                QM_TRY(MOZ_TO_RESULT(GetLastModifiedTime(file, aTimestamp)));

                return Ok{};
              }));
          break;

        case nsIFileKind::ExistsAsFile: {
          QM_TRY_INSPECT(const auto& leafName,
                         MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, aFile,
                                                           GetLeafName));

          // Bug 1595445 will handle unknown files here.

          if (IsOriginMetadata(leafName) || IsTempMetadata(leafName) ||
              IsDotFile(leafName)) {
            return NS_OK;
          }

          QM_TRY_UNWRAP(int64_t timestamp, MOZ_TO_RESULT_INVOKE_MEMBER(
                                               aFile, GetLastModifiedTime));

          // Need to convert from milliseconds to microseconds.
          MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp);
          timestamp *= int64_t(PR_USEC_PER_MSEC);

          if (timestamp > *aTimestamp) {
            *aTimestamp = timestamp;
          }
          break;
        }

        case nsIFileKind::DoesNotExist:
          // Ignore files that got removed externally while iterating.
          break;
      }

      return NS_OK;
    }
  };

  if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
    return PR_Now();
  }

  int64_t timestamp = INT64_MIN;
  nsresult rv = Helper::GetLastModifiedTime(&aFile, ×tamp);
  if (NS_FAILED(rv)) {
    timestamp = PR_Now();
  }

  // XXX if there were no suitable files for getting last modified time
  // (timestamp is still set to INT64_MIN), we should return the current time
  // instead of returning INT64_MIN.

  return timestamp;
}

// Returns a bool indicating whether the directory was newly created.
Result<bool, nsresult> EnsureDirectory(nsIFile& aDirectory) {
  AssertIsOnIOThread();

  // Callers call this function without checking if the directory already
  // exists (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we
  // just want to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the
  // reports.
  QM_TRY_INSPECT(const auto& exists,
                 QM_OR_ELSE_LOG_VERBOSE_IF(
                     // Expression.
                     MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Create,
                                                 nsIFile::DIRECTORY_TYPE, 0755,
                                                 /* aSkipAncestors = */ false)
                         .map([](Ok) { return false; }),
                     // Predicate.
                     IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
                     // Fallback.
                     ErrToOk<true>));

  if (exists) {
    QM_TRY_INSPECT(const bool& isDirectory,
                   MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, IsDirectory));
    QM_TRY(OkIf(isDirectory), Err(NS_ERROR_UNEXPECTED));
  }

  return !exists;
}

void GetJarPrefix(bool aInIsolatedMozBrowser, nsACString& aJarPrefix) {
  aJarPrefix.Truncate();

  // Fallback.
  if (!aInIsolatedMozBrowser) {
    return;
  }

  // AppId is an unused b2g identifier. Let's set it to 0 all the time (see bug
  // 1320404).
  // aJarPrefix = appId + "+" + { 't', 'f' } + "+";
  aJarPrefix.AppendInt(0);  // TODO: this is the appId, to be removed.
  aJarPrefix.Append('+');
  aJarPrefix.Append(aInIsolatedMozBrowser ? 't' : 'f');
  aJarPrefix.Append('+');
}

// This method computes and returns our best guess for the temporary storage
// limit (in bytes), based on disk capacity.
Result<uint64_t, nsresult> GetTemporaryStorageLimit(nsIFile& aStorageDir) {
  // The fixed limit pref can be used to override temporary storage limit
  // calculation.
  if (StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit() >= 0) {
    return static_cast<uint64_t>(
               StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit()) *
           1024;
  }

  constexpr int64_t teraByte = (1024LL * 1024LL * 1024LL * 1024LL);
  constexpr int64_t maxAllowedCapacity = 8LL * teraByte;

  // Check for disk capacity of user's device on which storage directory lives.
  int64_t diskCapacity = maxAllowedCapacity;

  // Log error when default disk capacity is returned due to the error
  QM_WARNONLY_TRY(MOZ_TO_RESULT(aStorageDir.GetDiskCapacity(&diskCapacity)));

  MOZ_ASSERT(diskCapacity >= 0LL);

  // Allow temporary storage to consume up to 50% of disk capacity.
  int64_t capacityLimit = diskCapacity / 2LL;

  // If the disk capacity reported by the operating system is very
  // large and potentially incorrect due to hardware issues,
  // a hardcoded limit is supplied instead.
  QM_WARNONLY_TRY(
      OkIf(capacityLimit < maxAllowedCapacity),
      ([&capacityLimit](const auto&) { capacityLimit = maxAllowedCapacity; }));

  return capacityLimit;
}

bool IsOriginUnaccessed(const FullOriginMetadata& aFullOriginMetadata,
                        const int64_t aRecentTime) {
  if (aFullOriginMetadata.mLastAccessTime > aRecentTime) {
    return false;
  }

  return (aRecentTime - aFullOriginMetadata.mLastAccessTime) / PR_USEC_PER_SEC >
         StaticPrefs::dom_quotaManager_unaccessedForLongTimeThresholdSec();
}

bool IsDirectoryLockBlockedBy(
    const DirectoryLockImpl::PrepareInfo& aPrepareInfo,
    const EnumSet<DirectoryLockCategory>& aCategories) {
  const auto& locks = aPrepareInfo.BlockedOnRef();
  return std::any_of(locks.cbegin(), locks.cend(),
                     [&aCategories](const auto& lock) {
                       return aCategories.contains(lock->Category());
                     });
}

bool IsDirectoryLockBlockedByUninitStorageOperation(
    const DirectoryLockImpl::PrepareInfo& aPrepareInfo) {
  return IsDirectoryLockBlockedBy(aPrepareInfo,
                                  DirectoryLockCategory::UninitStorage);
}

bool IsDirectoryLockBlockedByUninitStorageOrUninitOriginsOperation(
    const DirectoryLockImpl::PrepareInfo& aPrepareInfo) {
  return IsDirectoryLockBlockedBy(aPrepareInfo,
                                  {DirectoryLockCategory::UninitStorage,
                                   DirectoryLockCategory::UninitOrigins});
}

}  // namespace

/*******************************************************************************
 * Exported functions
 ******************************************************************************/


void InitializeQuotaManager() {
  MOZ_ASSERT(XRE_IsParentProcess());
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(!gQuotaManagerInitialized);

  if (!QuotaManager::IsRunningGTests()) {
    // These services have to be started on the main thread currently.
    const nsCOMPtr<mozIStorageService> ss =
        do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
    QM_WARNONLY_TRY(OkIf(ss));

    RefPtr<net::ExtensionProtocolHandler> extensionProtocolHandler =
        net::ExtensionProtocolHandler::GetSingleton();
    QM_WARNONLY_TRY(MOZ_TO_RESULT(extensionProtocolHandler));

    IndexedDatabaseManager* mgr = IndexedDatabaseManager::GetOrCreate();
    QM_WARNONLY_TRY(MOZ_TO_RESULT(mgr));
  }

  QM_WARNONLY_TRY(QM_TO_RESULT(QuotaManager::Initialize()));

#ifdef DEBUG
  gQuotaManagerInitialized = true;
#endif
}

void InitializeScopedLogExtraInfo() {
#ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
  ScopedLogExtraInfo::Initialize();
#endif
}

bool RecvShutdownQuotaManager() {
  AssertIsOnBackgroundThread();

  // If we are already in shutdown, don't call ShutdownInstance()
  // again and return true immediately. We shall see this incident
  // in Telemetry.
  // XXX todo: Make QM_TRY stacks thread-aware (Bug 1735124)
  // XXX todo: Active QM_TRY context for shutdown (Bug 1735170)
  QM_TRY(OkIf(!gShutdown), true);

  QuotaManager::ShutdownInstance();

  return true;
}

QuotaManager::Observer* QuotaManager::Observer::sInstance = nullptr;

// static
nsresult QuotaManager::Observer::Initialize() {
  MOZ_ASSERT(NS_IsMainThread());

  RefPtr<Observer> observer = new Observer();

  nsresult rv = observer->Init();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  sInstance = observer;

  return NS_OK;
}

// static
nsIObserver* QuotaManager::Observer::GetInstance() {
  MOZ_ASSERT(NS_IsMainThread());

  return sInstance;
}

// static
void QuotaManager::Observer::ShutdownCompleted() {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(sInstance);

  sInstance->mShutdownComplete = true;
}

nsresult QuotaManager::Observer::Init() {
  MOZ_ASSERT(NS_IsMainThread());

  /**
   * A RAII utility class to manage the registration and automatic
   * unregistration of observers with `nsIObserverService`. This class is
   * designed to simplify observer management, particularly when registering
   * for multiple topics, by ensuring that already registered topics are
   * unregistered if a failure occurs during subsequent registrations.
   */

  class MOZ_RAII Registrar {
   public:
    Registrar(nsIObserverService* aObserverService, nsIObserver* aObserver,
              const char* aTopic)
        : mObserverService(std::move(aObserverService)),
          mObserver(aObserver),
          mTopic(aTopic),
          mUnregisterOnDestruction(false) {
      MOZ_ASSERT(aObserverService);
      MOZ_ASSERT(aObserver);
      MOZ_ASSERT(aTopic);
    }

    ~Registrar() {
      if (mUnregisterOnDestruction) {
        mObserverService->RemoveObserver(mObserver, mTopic);
      }
    }

    nsresult Register() {
      MOZ_ASSERT(!mUnregisterOnDestruction);

      nsresult rv = mObserverService->AddObserver(mObserver, mTopic, false);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      mUnregisterOnDestruction = true;

      return NS_OK;
    }

    void Commit() { mUnregisterOnDestruction = false; }

   private:
    nsIObserverService* mObserverService;
    nsIObserver* mObserver;
    const char* mTopic;
    bool mUnregisterOnDestruction;
  };

  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  if (NS_WARN_IF(!obs)) {
    return NS_ERROR_FAILURE;
  }

  Registrar xpcomShutdownRegistrar(obs, this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
  QM_TRY(MOZ_TO_RESULT(xpcomShutdownRegistrar.Register()));

  Registrar profileDoChangeRegistrar(obs, this, kProfileDoChangeTopic);
  QM_TRY(MOZ_TO_RESULT(profileDoChangeRegistrar.Register()));

  Registrar contextualIdentityServiceLoadFinishedRegistrar(
      obs, this, kContextualIdentityServiceLoadFinishedTopic);
  QM_TRY(
      MOZ_TO_RESULT(contextualIdentityServiceLoadFinishedRegistrar.Register()));

  Registrar profileBeforeChangeQmRegistrar(
      obs, this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID);
  QM_TRY(MOZ_TO_RESULT(profileBeforeChangeQmRegistrar.Register()));

  Registrar wakeNotificationRegistrar(obs, this, NS_WIDGET_WAKE_OBSERVER_TOPIC);
  QM_TRY(MOZ_TO_RESULT(wakeNotificationRegistrar.Register()));

  Registrar lastPbContextExitedRegistrar(obs, this,
                                         kPrivateBrowsingObserverTopic);
  QM_TRY(MOZ_TO_RESULT(lastPbContextExitedRegistrar.Register()));

  xpcomShutdownRegistrar.Commit();
  profileDoChangeRegistrar.Commit();
  contextualIdentityServiceLoadFinishedRegistrar.Commit();
  profileBeforeChangeQmRegistrar.Commit();
  wakeNotificationRegistrar.Commit();
  lastPbContextExitedRegistrar.Commit();

  return NS_OK;
}

nsresult QuotaManager::Observer::Shutdown() {
  MOZ_ASSERT(NS_IsMainThread());

  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  if (NS_WARN_IF(!obs)) {
    return NS_ERROR_FAILURE;
  }

  MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, kPrivateBrowsingObserverTopic));
  MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC));
  MOZ_ALWAYS_SUCCEEDS(
      obs->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID));
  MOZ_ALWAYS_SUCCEEDS(
      obs->RemoveObserver(this, kContextualIdentityServiceLoadFinishedTopic));
  MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, kProfileDoChangeTopic));
  MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID));

  sInstance = nullptr;

  // In general, the instance will have died after the latter removal call, so
  // it's not safe to do anything after that point.
  // However, Shutdown is currently called from Observe which is called by the
  // Observer Service which holds a strong reference to the observer while the
  // Observe method is being called.

  return NS_OK;
}

NS_IMPL_ISUPPORTS(QuotaManager::Observer, nsIObserver)

NS_IMETHODIMP
QuotaManager::Observer::Observe(nsISupports* aSubject, const char* aTopic,
                                const char16_t* aData) {
  MOZ_ASSERT(NS_IsMainThread());

  nsresult rv;

  if (!strcmp(aTopic, kProfileDoChangeTopic)) {
    if (NS_WARN_IF(gBasePath)) {
      NS_WARNING(
          "profile-before-change-qm must precede repeated "
          "profile-do-change!");
      return NS_OK;
    }

    gBasePath = new nsString();

    nsCOMPtr<nsIFile> baseDir;
    rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR,
                                getter_AddRefs(baseDir));
    if (NS_FAILED(rv)) {
      rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                                  getter_AddRefs(baseDir));
    }
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    rv = baseDir->GetPath(*gBasePath);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

#ifdef XP_WIN
    // Annotate if our profile lives on a network resource.
    bool isNetworkPath = PathIsNetworkPathW(gBasePath->get());
    CrashReporter::RecordAnnotationBool(
        CrashReporter::Annotation::QuotaManagerStorageIsNetworkResource,
        isNetworkPath);
#endif

    QM_LOG(("Base path: %s", NS_ConvertUTF16toUTF8(*gBasePath).get()));

    gStorageName = new nsString();

    rv = Preferences::GetString("dom.quotaManager.storageName", *gStorageName);
    if (NS_FAILED(rv)) {
      *gStorageName = kStorageName;
    }

    gBuildId = new nsCString();

    nsCOMPtr<nsIPlatformInfo> platformInfo =
        do_GetService("@mozilla.org/xre/app-info;1");
    if (NS_WARN_IF(!platformInfo)) {
      return NS_ERROR_FAILURE;
    }

    rv = platformInfo->GetPlatformBuildID(*gBuildId);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    return NS_OK;
  }

  if (!strcmp(aTopic, kContextualIdentityServiceLoadFinishedTopic)) {
    if (NS_WARN_IF(!gBasePath)) {
      NS_WARNING(
          "profile-do-change must precede "
          "contextual-identity-service-load-finished!");
      return NS_OK;
    }

    nsCOMPtr<nsIQuotaManagerServiceInternal> quotaManagerService =
        QuotaManagerService::GetOrCreate();
    if (NS_WARN_IF(!quotaManagerService)) {
      return NS_ERROR_FAILURE;
    }

    nsCOMPtr<nsIQuotaUtilsService> quotaUtilsService =
        do_GetService("@mozilla.org/dom/quota-utils-service;1");
    if (NS_WARN_IF(!quotaUtilsService)) {
      return NS_ERROR_FAILURE;
    }

    uint32_t thumbnailPrivateIdentityId;
    nsresult rv = quotaUtilsService->GetPrivateIdentityId(
        u"userContextIdInternal.thumbnail"_ns, &thumbnailPrivateIdentityId);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    rv = quotaManagerService->SetThumbnailPrivateIdentityId(
        thumbnailPrivateIdentityId);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    return NS_OK;
  }

  if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)) {
    if (NS_WARN_IF(!gBasePath)) {
      NS_WARNING("profile-do-change must precede profile-before-change-qm!");
      return NS_OK;
    }

    // mPendingProfileChange is our re-entrancy guard (the nested event loop
    // below may cause re-entrancy).
    if (mPendingProfileChange) {
      return NS_OK;
    }

    AutoRestore<bool> pending(mPendingProfileChange);
    mPendingProfileChange = true;

    mShutdownComplete = false;

    PBackgroundChild* backgroundActor =
        BackgroundChild::GetOrCreateForCurrentThread();
    if (NS_WARN_IF(!backgroundActor)) {
      return NS_ERROR_FAILURE;
    }

    if (NS_WARN_IF(!backgroundActor->SendShutdownQuotaManager())) {
      return NS_ERROR_FAILURE;
    }

    MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
        "QuotaManager::Observer::Observe profile-before-change-qm"_ns,
        [&]() { return mShutdownComplete; }));

    gBasePath = nullptr;

    gStorageName = nullptr;

    gBuildId = nullptr;

    return NS_OK;
  }

  if (!strcmp(aTopic, kPrivateBrowsingObserverTopic)) {
    autoconst quotaManagerService = QuotaManagerService::GetOrCreate();
    if (NS_WARN_IF(!quotaManagerService)) {
      return NS_ERROR_FAILURE;
    }

    nsCOMPtr<nsIQuotaRequest> request;
    rv = quotaManagerService->ClearStoragesForPrivateBrowsing(
        nsGetterAddRefs(request));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    return NS_OK;
  }

  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
    rv = Shutdown();
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    return NS_OK;
  }

  if (!strcmp(aTopic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
    gLastOSWake = TimeStamp::Now();

    return NS_OK;
  }

  NS_WARNING("Unknown observer topic!");
  return NS_OK;
}

/*******************************************************************************
 * Quota manager
 ******************************************************************************/


QuotaManager::QuotaManager(const nsAString& aBasePath,
                           const nsAString& aStorageName)
    : mQuotaMutex("QuotaManager.mQuotaMutex"),
      mBasePath(aBasePath),
      mStorageName(aStorageName),
      mTemporaryStorageUsage(0),
      mNextDirectoryLockId(0),
      mStorageInitialized(false),
      mPersistentStorageInitialized(false),
      mPersistentStorageInitializedInternal(false),
      mTemporaryStorageInitialized(false),
      mTemporaryStorageInitializedInternal(false),
      mInitializingAllTemporaryOrigins(false),
      mAllTemporaryOriginsInitialized(false),
      mCacheUsable(false) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(!gInstance);
}

QuotaManager::~QuotaManager() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(!gInstance || gInstance == this);
}

// static
nsresult QuotaManager::Initialize() {
  MOZ_ASSERT(NS_IsMainThread());

  nsresult rv = Observer::Initialize();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  return NS_OK;
}

// static
Result<MovingNotNull<RefPtr<QuotaManager>>, nsresult>
QuotaManager::GetOrCreate() {
  AssertIsOnBackgroundThread();

  if (gInstance) {
    return WrapMovingNotNullUnchecked(RefPtr<QuotaManager>{gInstance});
  }

  QM_TRY(OkIf(gBasePath), Err(NS_ERROR_FAILURE), [](const auto&) {
    NS_WARNING(
        "Trying to create QuotaManager before profile-do-change! "
        "Forgot to call do_get_profile()?");
  });

  QM_TRY(OkIf(!IsShuttingDown()), Err(NS_ERROR_FAILURE), [](const auto&) {
    MOZ_ASSERT(false,
               "Trying to create QuotaManager after profile-before-change-qm!");
  });

  auto instance = MakeRefPtr<QuotaManager>(*gBasePath, *gStorageName);

  QM_TRY(MOZ_TO_RESULT(instance->Init()));

  gInstance = instance;

  // Do this before clients have a chance to acquire a directory lock for the
  // private repository.
  gInstance->ClearPrivateRepository();

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

Result<Ok, nsresult> QuotaManager::EnsureCreated() {
  AssertIsOnBackgroundThread();

  QM_TRY_RETURN(GetOrCreate().map([](const auto& res) { return Ok{}; }))
}

// static
QuotaManager* QuotaManager::Get() {
  // Does not return an owning reference.
  return gInstance;
}

// static
nsIObserver* QuotaManager::GetObserver() {
  MOZ_ASSERT(NS_IsMainThread());

  return Observer::GetInstance();
}

// static
bool QuotaManager::IsShuttingDown() { return gShutdown; }

// static
void QuotaManager::ShutdownInstance() {
  AssertIsOnBackgroundThread();

  if (gInstance) {
    auto recordTimeDeltaHelper =
        MakeRefPtr<RecordTimeDeltaHelper>(Telemetry::QM_SHUTDOWN_TIME_V0);

    recordTimeDeltaHelper->Start();

    gInstance->Shutdown();

    recordTimeDeltaHelper->End();

    gInstance = nullptr;
  } else {
    // If we were never initialized, just set the flag to avoid late creation.
    gShutdown = true;
  }

  RefPtr<Runnable> runnable =
      NS_NewRunnableFunction("dom::quota::QuotaManager::ShutdownCompleted",
                             []() { Observer::ShutdownCompleted(); });
  MOZ_ASSERT(runnable);

  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable.forget()));
}

// static
void QuotaManager::Reset() {
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(!gInstance);
  MOZ_ASSERT(gShutdown);

  gShutdown = false;
}

// static
bool QuotaManager::IsOSMetadata(const nsAString& aFileName) {
  return mozilla::dom::quota::IsOSMetadata(aFileName);
}

// static
bool QuotaManager::IsDotFile(const nsAString& aFileName) {
  return mozilla::dom::quota::IsDotFile(aFileName);
}

void QuotaManager::RegisterNormalOriginOp(
    NormalOriginOperationBase& aNormalOriginOp) {
  AssertIsOnBackgroundThread();

  if (!gNormalOriginOps) {
    gNormalOriginOps = new NormalOriginOpArray();
  }

  gNormalOriginOps->AppendElement(&aNormalOriginOp);
}

void QuotaManager::UnregisterNormalOriginOp(
    NormalOriginOperationBase& aNormalOriginOp) {
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(gNormalOriginOps);

  gNormalOriginOps->RemoveElement(&aNormalOriginOp);

  if (gNormalOriginOps->IsEmpty()) {
    gNormalOriginOps = nullptr;
  }
}

void QuotaManager::RegisterDirectoryLock(DirectoryLockImpl& aLock) {
  AssertIsOnOwningThread();

  mDirectoryLocks.AppendElement(WrapNotNullUnchecked(&aLock));

  if (aLock.ShouldUpdateLockIdTable()) {
    MutexAutoLock lock(mQuotaMutex);

    MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLockIdTable.Contains(aLock.Id()));
    mDirectoryLockIdTable.InsertOrUpdate(aLock.Id(),
                                         WrapNotNullUnchecked(&aLock));
  }

  if (aLock.ShouldUpdateLockTable()) {
    DirectoryLockTable& directoryLockTable =
        GetDirectoryLockTable(aLock.GetPersistenceType());

    // XXX It seems that the contents of the array are never actually used, we
    // just use that like an inefficient use counter. Can't we just change
    // DirectoryLockTable to a nsTHashMap<nsCStringHashKey, uint32_t>?
    directoryLockTable
        .LookupOrInsertWith(
            aLock.Origin(),
            [this, &aLock] {
              if (!IsShuttingDown()) {
                UpdateOriginAccessTime(aLock.GetPersistenceType(),
                                       aLock.OriginMetadata());
              }
              return MakeUnique<nsTArray<NotNull<DirectoryLockImpl*>>>();
            })
        ->AppendElement(WrapNotNullUnchecked(&aLock));
  }

  aLock.SetRegistered(true);
}

void QuotaManager::UnregisterDirectoryLock(DirectoryLockImpl& aLock) {
  AssertIsOnOwningThread();

  MOZ_ALWAYS_TRUE(mDirectoryLocks.RemoveElement(&aLock));

  if (aLock.ShouldUpdateLockIdTable()) {
    MutexAutoLock lock(mQuotaMutex);

    MOZ_DIAGNOSTIC_ASSERT(mDirectoryLockIdTable.Contains(aLock.Id()));
    mDirectoryLockIdTable.Remove(aLock.Id());
  }

  if (aLock.ShouldUpdateLockTable()) {
    DirectoryLockTable& directoryLockTable =
        GetDirectoryLockTable(aLock.GetPersistenceType());

    // ClearDirectoryLockTables may have been called, so the element or entire
    // array may not exist anymre.
    nsTArray<NotNull<DirectoryLockImpl*>>* array;
    if (directoryLockTable.Get(aLock.Origin(), &array) &&
        array->RemoveElement(&aLock) && array->IsEmpty()) {
      directoryLockTable.Remove(aLock.Origin());

      if (!IsShuttingDown()) {
        UpdateOriginAccessTime(aLock.GetPersistenceType(),
                               aLock.OriginMetadata());
      }
    }
  }

  aLock.SetRegistered(false);
}

void QuotaManager::AddPendingDirectoryLock(DirectoryLockImpl& aLock) {
  AssertIsOnOwningThread();

  mPendingDirectoryLocks.AppendElement(&aLock);
}

void QuotaManager::RemovePendingDirectoryLock(DirectoryLockImpl& aLock) {
  AssertIsOnOwningThread();

  MOZ_ALWAYS_TRUE(mPendingDirectoryLocks.RemoveElement(&aLock));
}

uint64_t QuotaManager::CollectOriginsForEviction(
    uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aLocks.IsEmpty());

  // XXX This looks as if this could/should also use CollectLRUOriginInfosUntil,
  // or maybe a generalization if that.

  struct MOZ_STACK_CLASS Helper final {
    static void GetInactiveOriginInfos(
        const nsTArray<NotNull<RefPtr<OriginInfo>>>& aOriginInfos,
        const nsTArray<NotNull<const DirectoryLockImpl*>>& aLocks,
        OriginInfosFlatTraversable& aInactiveOriginInfos) {
      for (const auto& originInfo : aOriginInfos) {
        MOZ_ASSERT(originInfo->mGroupInfo->mPersistenceType !=
                   PERSISTENCE_TYPE_PERSISTENT);

        if (originInfo->LockedPersisted()) {
          continue;
        }

        // Never evict PERSISTENCE_TYPE_DEFAULT data associated to a
        // moz-extension origin, unlike websites (which may more likely using
        // the local data as a cache but still able to retrieve the same data
        // from the server side) extensions do not have the same data stored
        // anywhere else and evicting the data would result into potential data
        // loss for the users.
        //
        // Also, unlike a website the extensions are explicitly installed and
        // uninstalled by the user and all data associated to the extension
        // principal will be completely removed once the addon is uninstalled.
        if (originInfo->mGroupInfo->mPersistenceType !=
                PERSISTENCE_TYPE_TEMPORARY &&
            originInfo->IsExtensionOrigin()) {
          continue;
        }

        const auto originScope =
            OriginScope::FromOrigin(originInfo->FlattenToOriginMetadata());

        const bool match =
            std::any_of(aLocks.begin(), aLocks.end(),
                        [&originScope](const DirectoryLockImpl* const lock) {
                          return originScope.Matches(lock->GetOriginScope());
                        });

        if (!match) {
          MOZ_ASSERT(!originInfo->mCanonicalQuotaObjects.Count(),
                     "Inactive origin shouldn't have open files!");
          aInactiveOriginInfos.InsertElementSorted(
              originInfo, OriginInfoAccessTimeComparator());
        }
      }
    }
  };

  // Split locks into separate arrays and filter out locks for persistent
  // storage, they can't block us.
  auto [temporaryStorageLocks, defaultStorageLocks,
        privateStorageLocks] = [this] {
    nsTArray<NotNull<const DirectoryLockImpl*>> temporaryStorageLocks;
    nsTArray<NotNull<const DirectoryLockImpl*>> defaultStorageLocks;
    nsTArray<NotNull<const DirectoryLockImpl*>> privateStorageLocks;

    for (NotNull<const DirectoryLockImpl*> const lock : mDirectoryLocks) {
      const PersistenceScope& persistenceScope = lock->PersistenceScopeRef();

      if (persistenceScope.Matches(
--> --------------------

--> maximum size reached

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

Messung V0.5
C=89 H=97 G=93

¤ Dauer der Verarbeitung: 0.20 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.