summaryrefslogtreecommitdiffstats
path: root/dom/quota/ActorsParent.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/quota/ActorsParent.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/quota/ActorsParent.cpp')
-rw-r--r--dom/quota/ActorsParent.cpp7874
1 files changed, 7874 insertions, 0 deletions
diff --git a/dom/quota/ActorsParent.cpp b/dom/quota/ActorsParent.cpp
new file mode 100644
index 0000000000..3f8a1a316b
--- /dev/null
+++ b/dom/quota/ActorsParent.cpp
@@ -0,0 +1,7874 @@
+/* -*- 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 "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 "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/Services.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/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/AssertionsImpl.h"
+#include "mozilla/dom/quota/CheckedUnsafePtr.h"
+#include "mozilla/dom/quota/Client.h"
+#include "mozilla/dom/quota/Config.h"
+#include "mozilla/dom/quota/Constants.h"
+#include "mozilla/dom/quota/DirectoryLock.h"
+#include "mozilla/dom/quota/FileUtils.h"
+#include "mozilla/dom/quota/PersistenceType.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/simpledb/ActorsParent.h"
+#include "mozilla/fallible.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/net/ExtensionProtocolHandler.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 "nsIQuotaRequests.h"
+#include "nsIPlatformInfo.h"
+#include "nsIPrincipal.h"
+#include "nsIRunnable.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsISupports.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIThread.h"
+#include "nsITimer.h"
+#include "nsIURI.h"
+#include "nsIWidget.h"
+#include "nsLiteralString.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.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 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 StoragePressureRunnable final : public Runnable {
+ const uint64_t mUsage;
+
+ public:
+ explicit StoragePressureRunnable(uint64_t aUsage)
+ : Runnable("dom::quota::QuotaObject::StoragePressureRunnable"),
+ mUsage(aUsage) {}
+
+ private:
+ ~StoragePressureRunnable() = default;
+
+ NS_DECL_NSIRUNNABLE
+};
+
+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
+
+BackgroundThreadObject::BackgroundThreadObject()
+ : mOwningThread(GetCurrentSerialEventTarget()) {
+ AssertIsOnOwningThread();
+}
+
+BackgroundThreadObject::BackgroundThreadObject(
+ nsISerialEventTarget* aOwningThread)
+ : mOwningThread(aOwningThread) {}
+
+#ifdef DEBUG
+
+void BackgroundThreadObject::AssertIsOnOwningThread() const {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mOwningThread);
+ bool current;
+ MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)));
+ MOZ_ASSERT(current);
+}
+
+#endif // DEBUG
+
+nsISerialEventTarget* BackgroundThreadObject::OwningThread() const {
+ MOZ_ASSERT(mOwningThread);
+ return mOwningThread;
+}
+
+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, &timestamp);
+ 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();
+}
+
+} // 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));
+ }
+
+ 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());
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // XXX: Improve the way that we remove observer in failure cases.
+ nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = obs->AddObserver(this, kProfileDoChangeTopic, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ return rv;
+ }
+
+ rv = obs->AddObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ obs->RemoveObserver(this, kProfileDoChangeTopic);
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ return rv;
+ }
+
+ rv = obs->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ obs->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID);
+ obs->RemoveObserver(this, kProfileDoChangeTopic);
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ return rv;
+ }
+
+ rv = obs->AddObserver(this, kPrivateBrowsingObserverTopic, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ obs->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC);
+ obs->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID);
+ obs->RemoveObserver(this, kProfileDoChangeTopic);
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ return rv;
+ }
+
+ 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, 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;
+ }
+
+ Telemetry::SetEventRecordingEnabled("dom.quota.try"_ns, true);
+
+ 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;
+ }
+
+ 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, 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;
+
+ Telemetry::SetEventRecordingEnabled("dom.quota.try"_ns, false);
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, kPrivateBrowsingObserverTopic)) {
+ auto* const 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),
+ mShutdownStorageOpCount(0),
+ mStorageInitialized(false),
+ mTemporaryStorageInitialized(false),
+ mTemporaryStorageInitializedInternal(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->mOrigin);
+
+ 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 Nullable<PersistenceType>& persistenceType =
+ lock->NullablePersistenceType();
+
+ if (persistenceType.IsNull()) {
+ temporaryStorageLocks.AppendElement(lock);
+ defaultStorageLocks.AppendElement(lock);
+ } else if (persistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) {
+ temporaryStorageLocks.AppendElement(lock);
+ } else if (persistenceType.Value() == PERSISTENCE_TYPE_DEFAULT) {
+ defaultStorageLocks.AppendElement(lock);
+ } else if (persistenceType.Value() == PERSISTENCE_TYPE_PRIVATE) {
+ privateStorageLocks.AppendElement(lock);
+ } else {
+ MOZ_ASSERT(persistenceType.Value() == PERSISTENCE_TYPE_PERSISTENT);
+
+ // Do nothing here, persistent origins don't need to be collected ever.
+ }
+ }
+
+ return std::make_tuple(std::move(temporaryStorageLocks),
+ std::move(defaultStorageLocks),
+ std::move(privateStorageLocks));
+ }();
+
+ // Enumerate and process inactive origins. This must be protected by the
+ // mutex.
+ MutexAutoLock lock(mQuotaMutex);
+
+ const auto [inactiveOrigins, sizeToBeFreed] =
+ [this, &temporaryStorageLocks = temporaryStorageLocks,
+ &defaultStorageLocks = defaultStorageLocks,
+ &privateStorageLocks = privateStorageLocks, aMinSizeToBeFreed] {
+ nsTArray<NotNull<RefPtr<const OriginInfo>>> inactiveOrigins;
+ for (const auto& entry : mGroupInfoPairs) {
+ const auto& pair = entry.GetData();
+
+ MOZ_ASSERT(!entry.GetKey().IsEmpty());
+ MOZ_ASSERT(pair);
+
+ RefPtr<GroupInfo> groupInfo =
+ pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
+ if (groupInfo) {
+ Helper::GetInactiveOriginInfos(groupInfo->mOriginInfos,
+ temporaryStorageLocks,
+ inactiveOrigins);
+ }
+
+ groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
+ if (groupInfo) {
+ Helper::GetInactiveOriginInfos(
+ groupInfo->mOriginInfos, defaultStorageLocks, inactiveOrigins);
+ }
+
+ groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE);
+ if (groupInfo) {
+ Helper::GetInactiveOriginInfos(
+ groupInfo->mOriginInfos, privateStorageLocks, inactiveOrigins);
+ }
+ }
+
+#ifdef DEBUG
+ // Make sure the array is sorted correctly.
+ const bool inactiveOriginsSorted =
+ std::is_sorted(inactiveOrigins.cbegin(), inactiveOrigins.cend(),
+ [](const auto& lhs, const auto& rhs) {
+ return lhs->mAccessTime < rhs->mAccessTime;
+ });
+ MOZ_ASSERT(inactiveOriginsSorted);
+#endif
+
+ // Create a list of inactive and the least recently used origins
+ // whose aggregate size is greater or equals the minimal size to be
+ // freed.
+ uint64_t sizeToBeFreed = 0;
+ for (uint32_t count = inactiveOrigins.Length(), index = 0;
+ index < count; index++) {
+ if (sizeToBeFreed >= aMinSizeToBeFreed) {
+ inactiveOrigins.TruncateLength(index);
+ break;
+ }
+
+ sizeToBeFreed += inactiveOrigins[index]->LockedUsage();
+ }
+
+ return std::pair(std::move(inactiveOrigins), sizeToBeFreed);
+ }();
+
+ if (sizeToBeFreed >= aMinSizeToBeFreed) {
+ // Success, add directory locks for these origins, so any other
+ // operations for them will be delayed (until origin eviction is finalized).
+
+ for (const auto& originInfo : inactiveOrigins) {
+ auto lock = DirectoryLockImpl::CreateForEviction(
+ WrapNotNullUnchecked(this), originInfo->mGroupInfo->mPersistenceType,
+ originInfo->FlattenToOriginMetadata());
+
+ lock->AcquireImmediately();
+
+ aLocks.AppendElement(lock.forget());
+ }
+
+ return sizeToBeFreed;
+ }
+
+ return 0;
+}
+
+nsresult QuotaManager::Init() {
+ AssertIsOnOwningThread();
+
+#ifdef XP_WIN
+ CacheUseDOSDevicePathSyntaxPrefValue();
+#endif
+
+ QM_TRY_INSPECT(const auto& baseDir, QM_NewLocalFile(mBasePath));
+
+ QM_TRY_UNWRAP(
+ do_Init(mIndexedDBPath),
+ GetPathForStorage(*baseDir, nsLiteralString(INDEXEDDB_DIRECTORY_NAME)));
+
+ QM_TRY(MOZ_TO_RESULT(baseDir->Append(mStorageName)));
+
+ QM_TRY_UNWRAP(do_Init(mStoragePath),
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, baseDir, GetPath));
+
+ QM_TRY_UNWRAP(
+ do_Init(mStorageArchivesPath),
+ GetPathForStorage(*baseDir, nsLiteralString(ARCHIVES_DIRECTORY_NAME)));
+
+ QM_TRY_UNWRAP(
+ do_Init(mPermanentStoragePath),
+ GetPathForStorage(*baseDir, nsLiteralString(PERMANENT_DIRECTORY_NAME)));
+
+ QM_TRY_UNWRAP(
+ do_Init(mTemporaryStoragePath),
+ GetPathForStorage(*baseDir, nsLiteralString(TEMPORARY_DIRECTORY_NAME)));
+
+ QM_TRY_UNWRAP(
+ do_Init(mDefaultStoragePath),
+ GetPathForStorage(*baseDir, nsLiteralString(DEFAULT_DIRECTORY_NAME)));
+
+ QM_TRY_UNWRAP(
+ do_Init(mPrivateStoragePath),
+ GetPathForStorage(*baseDir, nsLiteralString(PRIVATE_DIRECTORY_NAME)));
+
+ QM_TRY_UNWRAP(
+ do_Init(mToBeRemovedStoragePath),
+ GetPathForStorage(*baseDir, nsLiteralString(TOBEREMOVED_DIRECTORY_NAME)));
+
+ QM_TRY_UNWRAP(do_Init(mIOThread),
+ MOZ_TO_RESULT_INVOKE_TYPED(
+ nsCOMPtr<nsIThread>, MOZ_SELECT_OVERLOAD(NS_NewNamedThread),
+ "QuotaManager IO"));
+
+ static_assert(Client::IDB == 0 && Client::DOMCACHE == 1 && Client::SDB == 2 &&
+ Client::FILESYSTEM == 3 && Client::LS == 4 &&
+ Client::TYPE_MAX == 5,
+ "Fix the registration!");
+
+ // Register clients.
+ auto clients = decltype(mClients)::ValueType{};
+ clients.AppendElement(indexedDB::CreateQuotaClient());
+ clients.AppendElement(cache::CreateQuotaClient());
+ clients.AppendElement(simpledb::CreateQuotaClient());
+ clients.AppendElement(fs::CreateQuotaClient());
+ if (NextGenLocalStorageEnabled()) {
+ clients.AppendElement(localstorage::CreateQuotaClient());
+ } else {
+ clients.SetLength(Client::TypeMax());
+ }
+
+ mClients.init(std::move(clients));
+
+ MOZ_ASSERT(mClients->Capacity() == Client::TYPE_MAX,
+ "Should be using an auto array with correct capacity!");
+
+ mAllClientTypes.init(ClientTypesArray{
+ Client::Type::IDB, Client::Type::DOMCACHE, Client::Type::SDB,
+ Client::Type::FILESYSTEM, Client::Type::LS});
+ mAllClientTypesExceptLS.init(
+ ClientTypesArray{Client::Type::IDB, Client::Type::DOMCACHE,
+ Client::Type::SDB, Client::Type::FILESYSTEM});
+
+ return NS_OK;
+}
+
+// static
+void QuotaManager::MaybeRecordQuotaClientShutdownStep(
+ const Client::Type aClientType, const nsACString& aStepDescription) {
+ // Callable on any thread.
+
+ auto* const quotaManager = QuotaManager::Get();
+ MOZ_DIAGNOSTIC_ASSERT(quotaManager);
+
+ if (quotaManager->IsShuttingDown()) {
+ quotaManager->RecordShutdownStep(Some(aClientType), aStepDescription);
+ }
+}
+
+// static
+void QuotaManager::SafeMaybeRecordQuotaClientShutdownStep(
+ const Client::Type aClientType, const nsACString& aStepDescription) {
+ // Callable on any thread.
+
+ auto* const quotaManager = QuotaManager::Get();
+
+ if (quotaManager && quotaManager->IsShuttingDown()) {
+ quotaManager->RecordShutdownStep(Some(aClientType), aStepDescription);
+ }
+}
+
+void QuotaManager::RecordQuotaManagerShutdownStep(
+ const nsACString& aStepDescription) {
+ // Callable on any thread.
+ MOZ_ASSERT(IsShuttingDown());
+
+ RecordShutdownStep(Nothing{}, aStepDescription);
+}
+
+void QuotaManager::MaybeRecordQuotaManagerShutdownStep(
+ const nsACString& aStepDescription) {
+ // Callable on any thread.
+
+ if (IsShuttingDown()) {
+ RecordQuotaManagerShutdownStep(aStepDescription);
+ }
+}
+
+void QuotaManager::RecordShutdownStep(const Maybe<Client::Type> aClientType,
+ const nsACString& aStepDescription) {
+ MOZ_ASSERT(IsShuttingDown());
+
+ const TimeDuration elapsedSinceShutdownStart =
+ TimeStamp::NowLoRes() - *mShutdownStartedAt;
+
+ const auto stepString =
+ nsPrintfCString("%fs: %s", elapsedSinceShutdownStart.ToSeconds(),
+ nsPromiseFlatCString(aStepDescription).get());
+
+ if (aClientType) {
+ AssertIsOnBackgroundThread();
+
+ mShutdownSteps[*aClientType].Append(stepString + "\n"_ns);
+ } else {
+ // Callable on any thread.
+ MutexAutoLock lock(mQuotaMutex);
+
+ mQuotaManagerShutdownSteps.Append(stepString + "\n"_ns);
+ }
+
+#ifdef DEBUG
+ // XXX Probably this isn't the mechanism that should be used here.
+
+ NS_DebugBreak(
+ NS_DEBUG_WARNING,
+ nsAutoCString(aClientType ? Client::TypeToText(*aClientType)
+ : "quota manager"_ns + " shutdown step"_ns)
+ .get(),
+ stepString.get(), __FILE__, __LINE__);
+#endif
+}
+
+void QuotaManager::Shutdown() {
+ AssertIsOnOwningThread();
+ MOZ_DIAGNOSTIC_ASSERT(!gShutdown);
+
+ // Define some local helper functions
+
+ auto flagShutdownStarted = [this]() {
+ mShutdownStartedAt.init(TimeStamp::NowLoRes());
+
+ // Setting this flag prevents the service from being recreated and prevents
+ // further storages from being created.
+ gShutdown = true;
+ };
+
+ nsCOMPtr<nsITimer> crashBrowserTimer;
+
+ auto crashBrowserTimerCallback = [](nsITimer* aTimer, void* aClosure) {
+ auto* const quotaManager = static_cast<QuotaManager*>(aClosure);
+
+ nsCString annotation;
+
+ for (Client::Type type : quotaManager->AllClientTypes()) {
+ auto& quotaClient = *(*quotaManager->mClients)[type];
+
+ if (!quotaClient.IsShutdownCompleted()) {
+ annotation.AppendPrintf("%s: %s\nIntermediate steps:\n%s\n\n",
+ Client::TypeToText(type).get(),
+ quotaClient.GetShutdownStatus().get(),
+ quotaManager->mShutdownSteps[type].get());
+ }
+ }
+
+ if (gNormalOriginOps) {
+ annotation.AppendPrintf("QM: %zu normal origin ops pending\n",
+ gNormalOriginOps->Length());
+#ifdef QM_COLLECTING_OPERATION_TELEMETRY
+ for (const auto& op : *gNormalOriginOps) {
+ annotation.AppendPrintf("Op: %s pending\n", op->Name());
+ }
+#endif
+ }
+ {
+ MutexAutoLock lock(quotaManager->mQuotaMutex);
+
+ annotation.AppendPrintf("Intermediate steps:\n%s\n",
+ quotaManager->mQuotaManagerShutdownSteps.get());
+ }
+
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::QuotaManagerShutdownTimeout, annotation);
+
+ MOZ_CRASH("Quota manager shutdown timed out");
+ };
+
+ auto startCrashBrowserTimer = [&]() {
+ crashBrowserTimer = NS_NewTimer();
+ MOZ_ASSERT(crashBrowserTimer);
+ if (crashBrowserTimer) {
+ RecordQuotaManagerShutdownStep("startCrashBrowserTimer"_ns);
+ MOZ_ALWAYS_SUCCEEDS(crashBrowserTimer->InitWithNamedFuncCallback(
+ crashBrowserTimerCallback, this, SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS,
+ nsITimer::TYPE_ONE_SHOT,
+ "quota::QuotaManager::Shutdown::crashBrowserTimer"));
+ }
+ };
+
+ auto stopCrashBrowserTimer = [&]() {
+ if (crashBrowserTimer) {
+ RecordQuotaManagerShutdownStep("stopCrashBrowserTimer"_ns);
+ QM_WARNONLY_TRY(QM_TO_RESULT(crashBrowserTimer->Cancel()));
+ }
+ };
+
+ auto initiateShutdownWorkThreads = [this]() {
+ RecordQuotaManagerShutdownStep("initiateShutdownWorkThreads"_ns);
+ bool needsToWait = false;
+ for (Client::Type type : AllClientTypes()) {
+ // Clients are supposed to also AbortAllOperations from this point on
+ // to speed up shutdown, if possible. Thus pending operations
+ // might not be executed anymore.
+ needsToWait |= (*mClients)[type]->InitiateShutdownWorkThreads();
+ }
+
+ return needsToWait;
+ };
+
+ nsCOMPtr<nsITimer> killActorsTimer;
+
+ auto killActorsTimerCallback = [](nsITimer* aTimer, void* aClosure) {
+ auto* const quotaManager = static_cast<QuotaManager*>(aClosure);
+
+ quotaManager->RecordQuotaManagerShutdownStep("killActorsTimerCallback"_ns);
+
+ // XXX: This abort is a workaround to unblock shutdown, which
+ // ought to be removed by bug 1682326. We probably need more
+ // checks to immediately abort new operations during
+ // shutdown.
+ quotaManager->GetClient(Client::IDB)->AbortAllOperations();
+
+ for (Client::Type type : quotaManager->AllClientTypes()) {
+ quotaManager->GetClient(type)->ForceKillActors();
+ }
+ };
+
+ auto startKillActorsTimer = [&]() {
+ killActorsTimer = NS_NewTimer();
+ MOZ_ASSERT(killActorsTimer);
+ if (killActorsTimer) {
+ RecordQuotaManagerShutdownStep("startKillActorsTimer"_ns);
+ MOZ_ALWAYS_SUCCEEDS(killActorsTimer->InitWithNamedFuncCallback(
+ killActorsTimerCallback, this, SHUTDOWN_KILL_ACTORS_TIMEOUT_MS,
+ nsITimer::TYPE_ONE_SHOT,
+ "quota::QuotaManager::Shutdown::killActorsTimer"));
+ }
+ };
+
+ auto stopKillActorsTimer = [&]() {
+ if (killActorsTimer) {
+ RecordQuotaManagerShutdownStep("stopKillActorsTimer"_ns);
+ QM_WARNONLY_TRY(QM_TO_RESULT(killActorsTimer->Cancel()));
+ }
+ };
+
+ auto isAllClientsShutdownComplete = [this] {
+ return std::all_of(AllClientTypes().cbegin(), AllClientTypes().cend(),
+ [&self = *this](const auto type) {
+ return (*self.mClients)[type]->IsShutdownCompleted();
+ });
+ };
+
+ auto shutdownAndJoinWorkThreads = [this]() {
+ RecordQuotaManagerShutdownStep("shutdownAndJoinWorkThreads"_ns);
+ for (Client::Type type : AllClientTypes()) {
+ (*mClients)[type]->FinalizeShutdownWorkThreads();
+ }
+ };
+
+ auto shutdownAndJoinIOThread = [this]() {
+ RecordQuotaManagerShutdownStep("shutdownAndJoinIOThread"_ns);
+
+ // Make sure to join with our IO thread.
+ QM_WARNONLY_TRY(QM_TO_RESULT((*mIOThread)->Shutdown()));
+ };
+
+ auto invalidatePendingDirectoryLocks = [this]() {
+ RecordQuotaManagerShutdownStep("invalidatePendingDirectoryLocks"_ns);
+ for (RefPtr<DirectoryLockImpl>& lock : mPendingDirectoryLocks) {
+ lock->Invalidate();
+ }
+ };
+
+ // Body of the function
+
+ ScopedLogExtraInfo scope{ScopedLogExtraInfo::kTagContext,
+ "dom::quota::QuotaManager::Shutdown"_ns};
+
+ // This must be called before `flagShutdownStarted`, it would fail otherwise.
+ // `ShutdownStorageOp` needs to acquire an exclusive directory lock over
+ // entire <profile>/storage which will abort any existing operations and wait
+ // for all existing directory locks to be released. So the shutdown operation
+ // will effectively run after all existing operations.
+ // We don't need to use the returned promise here because `ShutdownStorage`
+ // registers `ShudownStorageOp` in `gNormalOriginOps`.
+ ShutdownStorage();
+
+ flagShutdownStarted();
+
+ startCrashBrowserTimer();
+
+ // XXX: StopIdleMaintenance now just notifies all clients to abort any
+ // maintenance work.
+ // This could be done as part of QuotaClient::AbortAllOperations.
+ StopIdleMaintenance();
+
+ // XXX In theory, we could simplify the code below (and also the `Client`
+ // interface) by removing the `initiateShutdownWorkThreads` and
+ // `isAllClientsShutdownComplete` calls because it should be sufficient
+ // to rely on `ShutdownStorage` to abort all existing operations and to
+ // wait for all existing directory locks to be released as well.
+
+ const bool needsToWait =
+ initiateShutdownWorkThreads() || static_cast<bool>(gNormalOriginOps);
+
+ // If any clients cannot shutdown immediately, spin the event loop while we
+ // wait on all the threads to close.
+ if (needsToWait) {
+ startKillActorsTimer();
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "QuotaManager::Shutdown"_ns, [isAllClientsShutdownComplete]() {
+ return !gNormalOriginOps && isAllClientsShutdownComplete();
+ }));
+
+ stopKillActorsTimer();
+ }
+
+ shutdownAndJoinWorkThreads();
+
+ shutdownAndJoinIOThread();
+
+ invalidatePendingDirectoryLocks();
+
+ stopCrashBrowserTimer();
+}
+
+void QuotaManager::InitQuotaForOrigin(
+ const FullOriginMetadata& aFullOriginMetadata,
+ const ClientUsageArray& aClientUsages, uint64_t aUsageBytes) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(IsBestEffortPersistenceType(aFullOriginMetadata.mPersistenceType));
+
+ MutexAutoLock lock(mQuotaMutex);
+
+ RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo(
+ aFullOriginMetadata.mPersistenceType, aFullOriginMetadata.mSuffix,
+ aFullOriginMetadata.mGroup);
+
+ groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>(
+ groupInfo, aFullOriginMetadata.mOrigin,
+ aFullOriginMetadata.mStorageOrigin, aFullOriginMetadata.mIsPrivate,
+ aClientUsages, aUsageBytes, aFullOriginMetadata.mLastAccessTime,
+ aFullOriginMetadata.mPersisted,
+ /* aDirectoryExists */ true));
+}
+
+void QuotaManager::EnsureQuotaForOrigin(const OriginMetadata& aOriginMetadata) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(IsBestEffortPersistenceType(aOriginMetadata.mPersistenceType));
+
+ MutexAutoLock lock(mQuotaMutex);
+
+ RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo(
+ aOriginMetadata.mPersistenceType, aOriginMetadata.mSuffix,
+ aOriginMetadata.mGroup);
+
+ RefPtr<OriginInfo> originInfo =
+ groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
+ if (!originInfo) {
+ groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>(
+ groupInfo, aOriginMetadata.mOrigin, aOriginMetadata.mStorageOrigin,
+ aOriginMetadata.mIsPrivate, ClientUsageArray(),
+ /* aUsageBytes */ 0,
+ /* aAccessTime */ PR_Now(), /* aPersisted */ false,
+ /* aDirectoryExists */ false));
+ }
+}
+
+int64_t QuotaManager::NoteOriginDirectoryCreated(
+ const OriginMetadata& aOriginMetadata, bool aPersisted) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(IsBestEffortPersistenceType(aOriginMetadata.mPersistenceType));
+
+ int64_t timestamp;
+
+ MutexAutoLock lock(mQuotaMutex);
+
+ RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo(
+ aOriginMetadata.mPersistenceType, aOriginMetadata.mSuffix,
+ aOriginMetadata.mGroup);
+
+ RefPtr<OriginInfo> originInfo =
+ groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
+ if (originInfo) {
+ timestamp = originInfo->LockedAccessTime();
+ originInfo->mPersisted = aPersisted;
+ originInfo->mDirectoryExists = true;
+ } else {
+ timestamp = PR_Now();
+ groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>(
+ groupInfo, aOriginMetadata.mOrigin, aOriginMetadata.mStorageOrigin,
+ aOriginMetadata.mIsPrivate, ClientUsageArray(),
+ /* aUsageBytes */ 0,
+ /* aAccessTime */ timestamp, aPersisted, /* aDirectoryExists */ true));
+ }
+
+ return timestamp;
+}
+
+void QuotaManager::DecreaseUsageForClient(const ClientMetadata& aClientMetadata,
+ int64_t aSize) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata.mPersistenceType));
+
+ MutexAutoLock lock(mQuotaMutex);
+
+ GroupInfoPair* pair;
+ if (!mGroupInfoPairs.Get(aClientMetadata.mGroup, &pair)) {
+ return;
+ }
+
+ RefPtr<GroupInfo> groupInfo =
+ pair->LockedGetGroupInfo(aClientMetadata.mPersistenceType);
+ if (!groupInfo) {
+ return;
+ }
+
+ RefPtr<OriginInfo> originInfo =
+ groupInfo->LockedGetOriginInfo(aClientMetadata.mOrigin);
+ if (originInfo) {
+ originInfo->LockedDecreaseUsage(aClientMetadata.mClientType, aSize);
+ }
+}
+
+void QuotaManager::ResetUsageForClient(const ClientMetadata& aClientMetadata) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata.mPersistenceType));
+
+ MutexAutoLock lock(mQuotaMutex);
+
+ GroupInfoPair* pair;
+ if (!mGroupInfoPairs.Get(aClientMetadata.mGroup, &pair)) {
+ return;
+ }
+
+ RefPtr<GroupInfo> groupInfo =
+ pair->LockedGetGroupInfo(aClientMetadata.mPersistenceType);
+ if (!groupInfo) {
+ return;
+ }
+
+ RefPtr<OriginInfo> originInfo =
+ groupInfo->LockedGetOriginInfo(aClientMetadata.mOrigin);
+ if (originInfo) {
+ originInfo->LockedResetUsageForClient(aClientMetadata.mClientType);
+ }
+}
+
+UsageInfo QuotaManager::GetUsageForClient(PersistenceType aPersistenceType,
+ const OriginMetadata& aOriginMetadata,
+ Client::Type aClientType) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+ MutexAutoLock lock(mQuotaMutex);
+
+ GroupInfoPair* pair;
+ if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
+ return UsageInfo{};
+ }
+
+ RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
+ if (!groupInfo) {
+ return UsageInfo{};
+ }
+
+ RefPtr<OriginInfo> originInfo =
+ groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
+ if (!originInfo) {
+ return UsageInfo{};
+ }
+
+ return originInfo->LockedGetUsageForClient(aClientType);
+}
+
+void QuotaManager::UpdateOriginAccessTime(
+ PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+ MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType);
+ MOZ_ASSERT(!IsShuttingDown());
+
+ MutexAutoLock lock(mQuotaMutex);
+
+ GroupInfoPair* pair;
+ if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
+ return;
+ }
+
+ RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
+ if (!groupInfo) {
+ return;
+ }
+
+ RefPtr<OriginInfo> originInfo =
+ groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
+ if (originInfo) {
+ int64_t timestamp = PR_Now();
+ originInfo->LockedUpdateAccessTime(timestamp);
+
+ MutexAutoUnlock autoUnlock(mQuotaMutex);
+
+ auto op = CreateSaveOriginAccessTimeOp(WrapMovingNotNullUnchecked(this),
+ aOriginMetadata, timestamp);
+
+ RegisterNormalOriginOp(*op);
+
+ op->RunImmediately();
+ }
+}
+
+void QuotaManager::RemoveQuota() {
+ AssertIsOnIOThread();
+
+ MutexAutoLock lock(mQuotaMutex);
+
+ for (const auto& entry : mGroupInfoPairs) {
+ const auto& pair = entry.GetData();
+
+ MOZ_ASSERT(!entry.GetKey().IsEmpty());
+ MOZ_ASSERT(pair);
+
+ RefPtr<GroupInfo> groupInfo =
+ pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
+ if (groupInfo) {
+ groupInfo->LockedRemoveOriginInfos();
+ }
+
+ groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
+ if (groupInfo) {
+ groupInfo->LockedRemoveOriginInfos();
+ }
+
+ groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE);
+ if (groupInfo) {
+ groupInfo->LockedRemoveOriginInfos();
+ }
+ }
+
+ mGroupInfoPairs.Clear();
+
+ MOZ_ASSERT(mTemporaryStorageUsage == 0, "Should be zero!");
+}
+
+nsresult QuotaManager::LoadQuota() {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(mStorageConnection);
+ MOZ_ASSERT(!mTemporaryStorageInitializedInternal);
+
+ // A list of all unaccessed default or temporary origins.
+ nsTArray<FullOriginMetadata> unaccessedOrigins;
+
+ auto MaybeCollectUnaccessedOrigin =
+ [loadQuotaInfoStartTime = PR_Now(),
+ &unaccessedOrigins](auto& fullOriginMetadata) {
+ if (IsOriginUnaccessed(fullOriginMetadata, loadQuotaInfoStartTime)) {
+ unaccessedOrigins.AppendElement(std::move(fullOriginMetadata));
+ }
+ };
+
+ auto recordTimeDeltaHelper =
+ MakeRefPtr<RecordTimeDeltaHelper>(Telemetry::QM_QUOTA_INFO_LOAD_TIME_V0);
+
+ const auto startTime = recordTimeDeltaHelper->Start();
+
+ auto LoadQuotaFromCache = [&]() -> nsresult {
+ QM_TRY_INSPECT(
+ const auto& stmt,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement,
+ "SELECT repository_id, suffix, group_, "
+ "origin, client_usages, usage, "
+ "last_access_time, accessed, persisted "
+ "FROM origin"_ns));
+
+ auto autoRemoveQuota = MakeScopeExit([&] {
+ RemoveQuota();
+ unaccessedOrigins.Clear();
+ });
+
+ QM_TRY(quota::CollectWhileHasResult(
+ *stmt,
+ [this,
+ &MaybeCollectUnaccessedOrigin](auto& stmt) -> Result<Ok, nsresult> {
+ QM_TRY_INSPECT(const int32_t& repositoryId,
+ MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
+
+ const auto maybePersistenceType =
+ PersistenceTypeFromInt32(repositoryId, fallible);
+ QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
+
+ FullOriginMetadata fullOriginMetadata;
+
+ fullOriginMetadata.mPersistenceType = maybePersistenceType.value();
+
+ QM_TRY_UNWRAP(fullOriginMetadata.mSuffix,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
+ GetUTF8String, 1));
+
+ QM_TRY_UNWRAP(fullOriginMetadata.mGroup,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
+ GetUTF8String, 2));
+
+ QM_TRY_UNWRAP(fullOriginMetadata.mOrigin,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
+ GetUTF8String, 3));
+
+ fullOriginMetadata.mStorageOrigin = fullOriginMetadata.mOrigin;
+
+ fullOriginMetadata.mIsPrivate = false;
+
+ QM_TRY_INSPECT(const auto& clientUsagesText,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
+ GetUTF8String, 4));
+
+ ClientUsageArray clientUsages;
+ QM_TRY(MOZ_TO_RESULT(clientUsages.Deserialize(clientUsagesText)));
+
+ QM_TRY_INSPECT(const int64_t& usage,
+ MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 5));
+ QM_TRY_UNWRAP(fullOriginMetadata.mLastAccessTime,
+ MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 6));
+ QM_TRY_INSPECT(const int64_t& accessed,
+ MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 7));
+ QM_TRY_UNWRAP(fullOriginMetadata.mPersisted,
+ MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 8));
+
+ QM_TRY_INSPECT(const bool& groupUpdated,
+ MaybeUpdateGroupForOrigin(fullOriginMetadata));
+
+ Unused << groupUpdated;
+
+ QM_TRY_INSPECT(
+ const bool& lastAccessTimeUpdated,
+ MaybeUpdateLastAccessTimeForOrigin(fullOriginMetadata));
+
+ Unused << lastAccessTimeUpdated;
+
+ // We don't need to update the .metadata-v2 file on disk here,
+ // EnsureTemporaryOriginIsInitialized is responsible for doing that.
+ // We just need to use correct group and last access time before
+ // initializing quota for the given origin. (Note that calling
+ // LoadFullOriginMetadataWithRestore below might update the group in
+ // the metadata file, but only as a side-effect. The actual place we
+ // ensure consistency is in EnsureTemporaryOriginIsInitialized.)
+
+ if (accessed) {
+ QM_TRY_INSPECT(const auto& directory,
+ GetOriginDirectory(fullOriginMetadata));
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
+
+ QM_TRY(OkIf(exists), Err(NS_ERROR_FILE_NOT_FOUND));
+
+ QM_TRY_INSPECT(const bool& isDirectory,
+ MOZ_TO_RESULT_INVOKE_MEMBER(directory, IsDirectory));
+
+ QM_TRY(OkIf(isDirectory), Err(NS_ERROR_FILE_DESTINATION_NOT_DIR));
+
+ // Calling LoadFullOriginMetadataWithRestore might update the group
+ // in the metadata file, but only as a side-effect. The actual place
+ // we ensure consistency is in EnsureTemporaryOriginIsInitialized.
+
+ QM_TRY_INSPECT(const auto& metadata,
+ LoadFullOriginMetadataWithRestore(directory));
+
+ QM_WARNONLY_TRY(OkIf(fullOriginMetadata.mLastAccessTime ==
+ metadata.mLastAccessTime));
+
+ QM_TRY(OkIf(fullOriginMetadata.mPersisted == metadata.mPersisted),
+ Err(NS_ERROR_FAILURE));
+
+ QM_TRY(OkIf(fullOriginMetadata.mPersistenceType ==
+ metadata.mPersistenceType),
+ Err(NS_ERROR_FAILURE));
+
+ QM_TRY(OkIf(fullOriginMetadata.mSuffix == metadata.mSuffix),
+ Err(NS_ERROR_FAILURE));
+
+ QM_TRY(OkIf(fullOriginMetadata.mGroup == metadata.mGroup),
+ Err(NS_ERROR_FAILURE));
+
+ QM_TRY(OkIf(fullOriginMetadata.mOrigin == metadata.mOrigin),
+ Err(NS_ERROR_FAILURE));
+
+ QM_TRY(OkIf(fullOriginMetadata.mStorageOrigin ==
+ metadata.mStorageOrigin),
+ Err(NS_ERROR_FAILURE));
+
+ QM_TRY(OkIf(fullOriginMetadata.mIsPrivate == metadata.mIsPrivate),
+ Err(NS_ERROR_FAILURE));
+
+ QM_TRY(MOZ_TO_RESULT(InitializeOrigin(
+ fullOriginMetadata.mPersistenceType, fullOriginMetadata,
+ fullOriginMetadata.mLastAccessTime,
+ fullOriginMetadata.mPersisted, directory)));
+ } else {
+ InitQuotaForOrigin(fullOriginMetadata, clientUsages, usage);
+ }
+
+ MaybeCollectUnaccessedOrigin(fullOriginMetadata);
+
+ return Ok{};
+ }));
+
+ autoRemoveQuota.release();
+
+ return NS_OK;
+ };
+
+ QM_TRY_INSPECT(
+ const bool& loadQuotaFromCache, ([this]() -> Result<bool, nsresult> {
+ if (mCacheUsable) {
+ QM_TRY_INSPECT(
+ const auto& stmt,
+ CreateAndExecuteSingleStepStatement<
+ SingleStepResult::ReturnNullIfNoResult>(
+ *mStorageConnection, "SELECT valid, build_id FROM cache"_ns));
+
+ QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
+
+ QM_TRY_INSPECT(const int32_t& valid,
+ MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
+
+ if (valid) {
+ if (!StaticPrefs::dom_quotaManager_caching_checkBuildId()) {
+ return true;
+ }
+
+ QM_TRY_INSPECT(const auto& buildId,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsAutoCString, stmt, GetUTF8String, 1));
+
+ return buildId == *gBuildId;
+ }
+ }
+
+ return false;
+ }()));
+
+ auto autoRemoveQuota = MakeScopeExit([&] { RemoveQuota(); });
+
+ if (!loadQuotaFromCache ||
+ !StaticPrefs::dom_quotaManager_loadQuotaFromCache() ||
+ ![&LoadQuotaFromCache] {
+ QM_WARNONLY_TRY_UNWRAP(auto res, MOZ_TO_RESULT(LoadQuotaFromCache()));
+ return static_cast<bool>(res);
+ }()) {
+ // A keeper to defer the return only in Nightly, so that the telemetry data
+ // for whole profile can be collected.
+#ifdef NIGHTLY_BUILD
+ nsresult statusKeeper = NS_OK;
+#endif
+
+ const auto statusKeeperFunc = [&](const nsresult rv) {
+ RECORD_IN_NIGHTLY(statusKeeper, rv);
+ };
+
+ for (const PersistenceType type :
+ kInitializableBestEffortPersistenceTypes) {
+ if (NS_WARN_IF(IsShuttingDown())) {
+ RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
+ }
+
+ QM_TRY(([&]() -> Result<Ok, nsresult> {
+ QM_TRY(MOZ_TO_RESULT(([this, type, &MaybeCollectUnaccessedOrigin] {
+ const auto innerFunc = [&](const auto&) -> nsresult {
+ return InitializeRepository(type,
+ MaybeCollectUnaccessedOrigin);
+ };
+
+ return ExecuteInitialization(
+ type == PERSISTENCE_TYPE_DEFAULT
+ ? Initialization::DefaultRepository
+ : Initialization::TemporaryRepository,
+ innerFunc);
+ }())),
+ OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
+
+ return Ok{};
+ }()));
+ }
+
+#ifdef NIGHTLY_BUILD
+ if (NS_FAILED(statusKeeper)) {
+ return statusKeeper;
+ }
+#endif
+ }
+
+ autoRemoveQuota.release();
+
+ const auto endTime = recordTimeDeltaHelper->End();
+
+ if (StaticPrefs::dom_quotaManager_checkQuotaInfoLoadTime() &&
+ static_cast<uint32_t>((endTime - startTime).ToMilliseconds()) >=
+ StaticPrefs::dom_quotaManager_longQuotaInfoLoadTimeThresholdMs() &&
+ !unaccessedOrigins.IsEmpty()) {
+ QM_WARNONLY_TRY(ArchiveOrigins(unaccessedOrigins));
+ }
+
+ return NS_OK;
+}
+
+void QuotaManager::UnloadQuota() {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(mStorageConnection);
+ MOZ_ASSERT(mTemporaryStorageInitializedInternal);
+ MOZ_ASSERT(mCacheUsable);
+
+ auto autoRemoveQuota = MakeScopeExit([&] { RemoveQuota(); });
+
+ mozStorageTransaction transaction(
+ mStorageConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+ QM_TRY(MOZ_TO_RESULT(transaction.Start()), QM_VOID);
+
+ QM_TRY(MOZ_TO_RESULT(
+ mStorageConnection->ExecuteSimpleSQL("DELETE FROM origin;"_ns)),
+ QM_VOID);
+
+ nsCOMPtr<mozIStorageStatement> insertStmt;
+
+ {
+ MutexAutoLock lock(mQuotaMutex);
+
+ for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
+ MOZ_ASSERT(!iter.Key().IsEmpty());
+
+ GroupInfoPair* const pair = iter.UserData();
+ MOZ_ASSERT(pair);
+
+ for (const PersistenceType type : kBestEffortPersistenceTypes) {
+ RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
+ if (!groupInfo) {
+ continue;
+ }
+
+ for (const auto& originInfo : groupInfo->mOriginInfos) {
+ MOZ_ASSERT(!originInfo->mCanonicalQuotaObjects.Count());
+
+ if (!originInfo->mDirectoryExists) {
+ continue;
+ }
+
+ if (originInfo->mIsPrivate) {
+ continue;
+ }
+
+ if (insertStmt) {
+ MOZ_ALWAYS_SUCCEEDS(insertStmt->Reset());
+ } else {
+ QM_TRY_UNWRAP(
+ insertStmt,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCOMPtr<mozIStorageStatement>, mStorageConnection,
+ CreateStatement,
+ "INSERT INTO origin (repository_id, suffix, group_, "
+ "origin, client_usages, usage, last_access_time, "
+ "accessed, persisted) "
+ "VALUES (:repository_id, :suffix, :group_, :origin, "
+ ":client_usages, :usage, :last_access_time, :accessed, "
+ ":persisted)"_ns),
+ QM_VOID);
+ }
+
+ QM_TRY(MOZ_TO_RESULT(originInfo->LockedBindToStatement(insertStmt)),
+ QM_VOID);
+
+ QM_TRY(MOZ_TO_RESULT(insertStmt->Execute()), QM_VOID);
+ }
+
+ groupInfo->LockedRemoveOriginInfos();
+ }
+
+ iter.Remove();
+ }
+ }
+
+ QM_TRY_INSPECT(
+ const auto& stmt,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement,
+ "UPDATE cache SET valid = :valid, build_id = :buildId;"_ns),
+ QM_VOID);
+
+ QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("valid"_ns, 1)), QM_VOID);
+ QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByName("buildId"_ns, *gBuildId)),
+ QM_VOID);
+ QM_TRY(MOZ_TO_RESULT(stmt->Execute()), QM_VOID);
+ QM_TRY(MOZ_TO_RESULT(transaction.Commit()), QM_VOID);
+}
+
+already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
+ PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
+ Client::Type aClientType, nsIFile* aFile, int64_t aFileSize,
+ int64_t* aFileSizeOut /* = nullptr */) {
+ NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+ MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType);
+
+ if (aFileSizeOut) {
+ *aFileSizeOut = 0;
+ }
+
+ if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
+ return nullptr;
+ }
+
+ QM_TRY_INSPECT(const auto& path,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, aFile, GetPath),
+ nullptr);
+
+#ifdef DEBUG
+ {
+ QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aOriginMetadata),
+ nullptr);
+
+ nsAutoString clientType;
+ QM_TRY(OkIf(Client::TypeToText(aClientType, clientType, fallible)),
+ nullptr);
+
+ QM_TRY(MOZ_TO_RESULT(directory->Append(clientType)), nullptr);
+
+ QM_TRY_INSPECT(
+ const auto& directoryPath,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, directory, GetPath),
+ nullptr);
+
+ MOZ_ASSERT(StringBeginsWith(path, directoryPath));
+ }
+#endif
+
+ QM_TRY_INSPECT(
+ const int64_t fileSize,
+ ([&aFile, aFileSize]() -> Result<int64_t, nsresult> {
+ if (aFileSize == -1) {
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aFile, Exists));
+
+ if (exists) {
+ QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(aFile, GetFileSize));
+ }
+
+ return 0;
+ }
+
+ return aFileSize;
+ }()),
+ nullptr);
+
+ RefPtr<QuotaObject> result;
+ {
+ MutexAutoLock lock(mQuotaMutex);
+
+ GroupInfoPair* pair;
+ if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
+ return nullptr;
+ }
+
+ RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
+
+ if (!groupInfo) {
+ return nullptr;
+ }
+
+ RefPtr<OriginInfo> originInfo =
+ groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
+
+ if (!originInfo) {
+ return nullptr;
+ }
+
+ // We need this extra raw pointer because we can't assign to the smart
+ // pointer directly since QuotaObject::AddRef would try to acquire the same
+ // mutex.
+ const NotNull<CanonicalQuotaObject*> canonicalQuotaObject =
+ originInfo->mCanonicalQuotaObjects.LookupOrInsertWith(path, [&] {
+ // Create a new QuotaObject. The hashtable is not responsible to
+ // delete the QuotaObject.
+ return WrapNotNullUnchecked(new CanonicalQuotaObject(
+ originInfo, aClientType, path, fileSize));
+ });
+
+ // Addref the QuotaObject and move the ownership to the result. This must
+ // happen before we unlock!
+ result = canonicalQuotaObject->LockedAddRef();
+ }
+
+ if (aFileSizeOut) {
+ *aFileSizeOut = fileSize;
+ }
+
+ // The caller becomes the owner of the QuotaObject, that is, the caller is
+ // is responsible to delete it when the last reference is removed.
+ return result.forget();
+}
+
+already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
+ PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
+ Client::Type aClientType, const nsAString& aPath, int64_t aFileSize,
+ int64_t* aFileSizeOut /* = nullptr */) {
+ NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+ if (aFileSizeOut) {
+ *aFileSizeOut = 0;
+ }
+
+ QM_TRY_INSPECT(const auto& file, QM_NewLocalFile(aPath), nullptr);
+
+ return GetQuotaObject(aPersistenceType, aOriginMetadata, aClientType, file,
+ aFileSize, aFileSizeOut);
+}
+
+already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
+ const int64_t aDirectoryLockId, const nsAString& aPath) {
+ NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+ Maybe<MutexAutoLock> lock;
+
+ // See the comment for mDirectoryLockIdTable in QuotaManager.h
+ if (!IsOnBackgroundThread()) {
+ lock.emplace(mQuotaMutex);
+ }
+
+ if (auto maybeDirectoryLock =
+ mDirectoryLockIdTable.MaybeGet(aDirectoryLockId)) {
+ const auto& directoryLock = *maybeDirectoryLock;
+ MOZ_DIAGNOSTIC_ASSERT(directoryLock->ShouldUpdateLockIdTable());
+
+ const PersistenceType persistenceType = directoryLock->GetPersistenceType();
+ const OriginMetadata& originMetadata = directoryLock->OriginMetadata();
+ const Client::Type clientType = directoryLock->ClientType();
+
+ lock.reset();
+
+ return GetQuotaObject(persistenceType, originMetadata, clientType, aPath);
+ }
+
+ MOZ_ASSERT(aDirectoryLockId == -1);
+ return nullptr;
+}
+
+Nullable<bool> QuotaManager::OriginPersisted(
+ const OriginMetadata& aOriginMetadata) {
+ AssertIsOnIOThread();
+
+ MutexAutoLock lock(mQuotaMutex);
+
+ RefPtr<OriginInfo> originInfo =
+ LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aOriginMetadata);
+ if (originInfo) {
+ return Nullable<bool>(originInfo->LockedPersisted());
+ }
+
+ return Nullable<bool>();
+}
+
+void QuotaManager::PersistOrigin(const OriginMetadata& aOriginMetadata) {
+ AssertIsOnIOThread();
+
+ MutexAutoLock lock(mQuotaMutex);
+
+ RefPtr<OriginInfo> originInfo =
+ LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aOriginMetadata);
+ if (originInfo && !originInfo->LockedPersisted()) {
+ originInfo->LockedPersist();
+ }
+}
+
+void QuotaManager::AbortOperationsForLocks(
+ const DirectoryLockIdTableArray& aLockIds) {
+ for (Client::Type type : AllClientTypes()) {
+ if (aLockIds[type].Filled()) {
+ (*mClients)[type]->AbortOperationsForLocks(aLockIds[type]);
+ }
+ }
+}
+
+void QuotaManager::AbortOperationsForProcess(ContentParentId aContentParentId) {
+ AssertIsOnOwningThread();
+
+ for (const RefPtr<Client>& client : *mClients) {
+ client->AbortOperationsForProcess(aContentParentId);
+ }
+}
+
+Result<nsCOMPtr<nsIFile>, nsresult> QuotaManager::GetOriginDirectory(
+ const OriginMetadata& aOriginMetadata) const {
+ QM_TRY_UNWRAP(
+ auto directory,
+ QM_NewLocalFile(GetStoragePath(aOriginMetadata.mPersistenceType)));
+
+ QM_TRY(MOZ_TO_RESULT(directory->Append(
+ MakeSanitizedOriginString(aOriginMetadata.mStorageOrigin))));
+
+ return directory;
+}
+
+// static
+nsresult QuotaManager::CreateDirectoryMetadata(
+ nsIFile& aDirectory, int64_t aTimestamp,
+ const OriginMetadata& aOriginMetadata) {
+ AssertIsOnIOThread();
+
+ OriginAttributes groupAttributes;
+
+ nsCString groupNoSuffix;
+ QM_TRY(OkIf(groupAttributes.PopulateFromOrigin(aOriginMetadata.mGroup,
+ groupNoSuffix)),
+ NS_ERROR_FAILURE);
+
+ nsCString groupPrefix;
+ GetJarPrefix(groupAttributes.mInIsolatedMozBrowser, groupPrefix);
+
+ nsCString group = groupPrefix + groupNoSuffix;
+
+ OriginAttributes originAttributes;
+
+ nsCString originNoSuffix;
+ QM_TRY(OkIf(originAttributes.PopulateFromOrigin(aOriginMetadata.mOrigin,
+ originNoSuffix)),
+ NS_ERROR_FAILURE);
+
+ nsCString originPrefix;
+ GetJarPrefix(originAttributes.mInIsolatedMozBrowser, originPrefix);
+
+ nsCString origin = originPrefix + originNoSuffix;
+
+ MOZ_ASSERT(groupPrefix == originPrefix);
+
+ QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCOMPtr<nsIFile>, aDirectory, Clone));
+
+ QM_TRY(MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_TMP_FILE_NAME))));
+
+ QM_TRY_INSPECT(const auto& stream,
+ GetBinaryOutputStream(*file, FileFlag::Truncate));
+ MOZ_ASSERT(stream);
+
+ QM_TRY(MOZ_TO_RESULT(stream->Write64(aTimestamp)));
+
+ QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(group.get())));
+
+ QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(origin.get())));
+
+ // Currently unused (used to be isApp).
+ QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(false)));
+
+ QM_TRY(MOZ_TO_RESULT(stream->Flush()));
+
+ QM_TRY(MOZ_TO_RESULT(stream->Close()));
+
+ QM_TRY(MOZ_TO_RESULT(
+ file->RenameTo(nullptr, nsLiteralString(METADATA_FILE_NAME))));
+
+ return NS_OK;
+}
+
+// static
+nsresult QuotaManager::CreateDirectoryMetadata2(
+ nsIFile& aDirectory, int64_t aTimestamp, bool aPersisted,
+ const OriginMetadata& aOriginMetadata) {
+ AssertIsOnIOThread();
+
+ QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCOMPtr<nsIFile>, aDirectory, Clone));
+
+ QM_TRY(
+ MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_V2_TMP_FILE_NAME))));
+
+ QM_TRY_INSPECT(const auto& stream,
+ GetBinaryOutputStream(*file, FileFlag::Truncate));
+ MOZ_ASSERT(stream);
+
+ QM_TRY(MOZ_TO_RESULT(stream->Write64(aTimestamp)));
+
+ QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(aPersisted)));
+
+ // Reserved data 1
+ QM_TRY(MOZ_TO_RESULT(stream->Write32(0)));
+
+ // Reserved data 2
+ QM_TRY(MOZ_TO_RESULT(stream->Write32(0)));
+
+ // Currently unused (used to be suffix).
+ QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ("")));
+
+ // Currently unused (used to be group).
+ QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ("")));
+
+ QM_TRY(MOZ_TO_RESULT(
+ stream->WriteStringZ(aOriginMetadata.mStorageOrigin.get())));
+
+ // Currently used for isPrivate (used to be used for isApp).
+ QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(aOriginMetadata.mIsPrivate)));
+
+ QM_TRY(MOZ_TO_RESULT(stream->Flush()));
+
+ QM_TRY(MOZ_TO_RESULT(stream->Close()));
+
+ QM_TRY(MOZ_TO_RESULT(
+ file->RenameTo(nullptr, nsLiteralString(METADATA_V2_FILE_NAME))));
+
+ return NS_OK;
+}
+
+nsresult QuotaManager::RestoreDirectoryMetadata2(nsIFile* aDirectory) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aDirectory);
+ MOZ_ASSERT(mStorageConnection);
+
+ RefPtr<RestoreDirectoryMetadata2Helper> helper =
+ new RestoreDirectoryMetadata2Helper(aDirectory);
+
+ QM_TRY(MOZ_TO_RESULT(helper->Init()));
+
+ QM_TRY(MOZ_TO_RESULT(helper->RestoreMetadata2File()));
+
+ return NS_OK;
+}
+
+Result<FullOriginMetadata, nsresult> QuotaManager::LoadFullOriginMetadata(
+ nsIFile* aDirectory, PersistenceType aPersistenceType) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aDirectory);
+ MOZ_ASSERT(mStorageConnection);
+
+ QM_TRY_INSPECT(const auto& binaryStream,
+ GetBinaryInputStream(*aDirectory,
+ nsLiteralString(METADATA_V2_FILE_NAME)));
+
+ FullOriginMetadata fullOriginMetadata;
+
+ QM_TRY_UNWRAP(fullOriginMetadata.mLastAccessTime,
+ MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
+
+ QM_TRY_UNWRAP(fullOriginMetadata.mPersisted,
+ MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
+
+ QM_TRY_INSPECT(const bool& reservedData1,
+ MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
+ Unused << reservedData1;
+
+ // XXX Use for the persistence type.
+ QM_TRY_INSPECT(const bool& reservedData2,
+ MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
+ Unused << reservedData2;
+
+ fullOriginMetadata.mPersistenceType = aPersistenceType;
+
+ QM_TRY_INSPECT(const auto& suffix, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCString, binaryStream, ReadCString));
+ Unused << suffix;
+
+ QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCString, binaryStream, ReadCString));
+ Unused << group;
+
+ QM_TRY_UNWRAP(
+ fullOriginMetadata.mStorageOrigin,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, binaryStream, ReadCString));
+
+ // Currently used for isPrivate (used to be used for isApp).
+ QM_TRY_UNWRAP(fullOriginMetadata.mIsPrivate,
+ MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
+
+ QM_TRY(MOZ_TO_RESULT(binaryStream->Close()));
+
+ auto principal =
+ [&storageOrigin =
+ fullOriginMetadata.mStorageOrigin]() -> nsCOMPtr<nsIPrincipal> {
+ if (storageOrigin.EqualsLiteral(kChromeOrigin)) {
+ return SystemPrincipal::Get();
+ }
+ return BasePrincipal::CreateContentPrincipal(storageOrigin);
+ }();
+ QM_TRY(MOZ_TO_RESULT(principal));
+
+ PrincipalInfo principalInfo;
+ QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
+
+ QM_TRY(MOZ_TO_RESULT(IsPrincipalInfoValid(principalInfo)),
+ Err(NS_ERROR_MALFORMED_URI));
+
+ QM_TRY_UNWRAP(auto principalMetadata,
+ GetInfoFromValidatedPrincipalInfo(principalInfo));
+
+ fullOriginMetadata.mSuffix = std::move(principalMetadata.mSuffix);
+ fullOriginMetadata.mGroup = std::move(principalMetadata.mGroup);
+ fullOriginMetadata.mOrigin = std::move(principalMetadata.mOrigin);
+
+ QM_TRY_INSPECT(const bool& groupUpdated,
+ MaybeUpdateGroupForOrigin(fullOriginMetadata));
+
+ // A workaround for a bug in GetLastModifiedTime implementation which should
+ // have returned the current time instead of INT64_MIN when there were no
+ // suitable files for getting last modified time.
+ QM_TRY_INSPECT(const bool& lastAccessTimeUpdated,
+ MaybeUpdateLastAccessTimeForOrigin(fullOriginMetadata));
+
+ if (groupUpdated || lastAccessTimeUpdated) {
+ // Only overwriting .metadata-v2 (used to overwrite .metadata too) to reduce
+ // I/O.
+ QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(
+ *aDirectory, fullOriginMetadata.mLastAccessTime,
+ fullOriginMetadata.mPersisted, fullOriginMetadata)));
+ }
+
+ return fullOriginMetadata;
+}
+
+Result<FullOriginMetadata, nsresult>
+QuotaManager::LoadFullOriginMetadataWithRestore(nsIFile* aDirectory) {
+ // XXX Once the persistence type is stored in the metadata file, this block
+ // for getting the persistence type from the parent directory name can be
+ // removed.
+ nsCOMPtr<nsIFile> parentDir;
+ QM_TRY(MOZ_TO_RESULT(aDirectory->GetParent(getter_AddRefs(parentDir))));
+
+ const auto maybePersistenceType =
+ PersistenceTypeFromFile(*parentDir, fallible);
+ QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
+
+ const auto& persistenceType = maybePersistenceType.value();
+
+ QM_TRY_RETURN(QM_OR_ELSE_WARN(
+ // Expression.
+ LoadFullOriginMetadata(aDirectory, persistenceType),
+ // Fallback.
+ ([&aDirectory, &persistenceType,
+ this](const nsresult rv) -> Result<FullOriginMetadata, nsresult> {
+ QM_TRY(MOZ_TO_RESULT(RestoreDirectoryMetadata2(aDirectory)));
+
+ QM_TRY_RETURN(LoadFullOriginMetadata(aDirectory, persistenceType));
+ })));
+}
+
+Result<OriginMetadata, nsresult> QuotaManager::GetOriginMetadata(
+ nsIFile* aDirectory) {
+ MOZ_ASSERT(aDirectory);
+ MOZ_ASSERT(mStorageConnection);
+
+ QM_TRY_INSPECT(
+ const auto& leafName,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, aDirectory, GetLeafName));
+
+ // XXX Consider using QuotaManager::ParseOrigin here.
+ nsCString spec;
+ OriginAttributes attrs;
+ nsCString originalSuffix;
+ OriginParser::ResultType result = OriginParser::ParseOrigin(
+ NS_ConvertUTF16toUTF8(leafName), spec, &attrs, originalSuffix);
+ QM_TRY(MOZ_TO_RESULT(result == OriginParser::ValidOrigin));
+
+ QM_TRY_INSPECT(
+ const auto& principal,
+ ([&spec, &attrs]() -> Result<nsCOMPtr<nsIPrincipal>, nsresult> {
+ if (spec.EqualsLiteral(kChromeOrigin)) {
+ return nsCOMPtr<nsIPrincipal>(SystemPrincipal::Get());
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), spec)));
+
+ return nsCOMPtr<nsIPrincipal>(
+ BasePrincipal::CreateContentPrincipal(uri, attrs));
+ }()));
+ QM_TRY(MOZ_TO_RESULT(principal));
+
+ PrincipalInfo principalInfo;
+ QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
+
+ QM_TRY(MOZ_TO_RESULT(IsPrincipalInfoValid(principalInfo)),
+ Err(NS_ERROR_MALFORMED_URI));
+
+ QM_TRY_UNWRAP(auto principalMetadata,
+ GetInfoFromValidatedPrincipalInfo(principalInfo));
+
+ QM_TRY_INSPECT(const auto& parentDirectory,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr<nsIFile>,
+ aDirectory, GetParent));
+
+ const auto maybePersistenceType =
+ PersistenceTypeFromFile(*parentDirectory, fallible);
+ QM_TRY(MOZ_TO_RESULT(maybePersistenceType.isSome()));
+
+ return OriginMetadata{std::move(principalMetadata),
+ maybePersistenceType.value()};
+}
+
+Result<Ok, nsresult> QuotaManager::RemoveOriginDirectory(nsIFile& aDirectory) {
+ AssertIsOnIOThread();
+
+ if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownTeardown)) {
+ QM_TRY_RETURN(MOZ_TO_RESULT(aDirectory.Remove(true)));
+ }
+
+ QM_TRY_INSPECT(const auto& toBeRemovedStorageDir,
+ QM_NewLocalFile(*mToBeRemovedStoragePath));
+
+ QM_TRY_INSPECT(const bool& created, EnsureDirectory(*toBeRemovedStorageDir));
+
+ (void)created;
+
+ QM_TRY_RETURN(MOZ_TO_RESULT(aDirectory.MoveTo(
+ toBeRemovedStorageDir, NSID_TrimBracketsUTF16(nsID::GenerateUUID()))));
+}
+
+template <typename OriginFunc>
+nsresult QuotaManager::InitializeRepository(PersistenceType aPersistenceType,
+ OriginFunc&& aOriginFunc) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY ||
+ aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
+
+ QM_TRY_INSPECT(const auto& directory,
+ QM_NewLocalFile(GetStoragePath(aPersistenceType)));
+
+ QM_TRY_INSPECT(const bool& created, EnsureDirectory(*directory));
+
+ Unused << created;
+
+ // A keeper to defer the return only in Nightly, so that the telemetry data
+ // for whole profile can be collected
+#ifdef NIGHTLY_BUILD
+ nsresult statusKeeper = NS_OK;
+#endif
+
+ const auto statusKeeperFunc = [&](const nsresult rv) {
+ RECORD_IN_NIGHTLY(statusKeeper, rv);
+ };
+
+ struct RenameAndInitInfo {
+ nsCOMPtr<nsIFile> mOriginDirectory;
+ FullOriginMetadata mFullOriginMetadata;
+ int64_t mTimestamp;
+ bool mPersisted;
+ };
+ nsTArray<RenameAndInitInfo> renameAndInitInfos;
+
+ QM_TRY(([&]() -> Result<Ok, nsresult> {
+ QM_TRY(
+ CollectEachFile(
+ *directory,
+ [&](nsCOMPtr<nsIFile>&& childDirectory) -> Result<Ok, nsresult> {
+ if (NS_WARN_IF(IsShuttingDown())) {
+ RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
+ }
+
+ QM_TRY(
+ ([this, &childDirectory, &renameAndInitInfos,
+ aPersistenceType, &aOriginFunc]() -> Result<Ok, nsresult> {
+ QM_TRY_INSPECT(
+ const auto& leafName,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsAutoString, childDirectory, GetLeafName));
+
+ QM_TRY_INSPECT(const auto& dirEntryKind,
+ GetDirEntryKind(*childDirectory));
+
+ switch (dirEntryKind) {
+ case nsIFileKind::ExistsAsDirectory: {
+ QM_TRY_UNWRAP(
+ auto maybeMetadata,
+ QM_OR_ELSE_WARN_IF(
+ // Expression
+ LoadFullOriginMetadataWithRestore(
+ childDirectory)
+ .map([](auto metadata)
+ -> Maybe<FullOriginMetadata> {
+ return Some(std::move(metadata));
+ }),
+ // Predicate.
+ IsSpecificError<NS_ERROR_MALFORMED_URI>,
+ // Fallback.
+ ErrToDefaultOk<Maybe<FullOriginMetadata>>));
+
+ if (!maybeMetadata) {
+ // Unknown directories during initialization are
+ // allowed. Just warn if we find them.
+ UNKNOWN_FILE_WARNING(leafName);
+ break;
+ }
+
+ auto metadata = maybeMetadata.extract();
+
+ MOZ_ASSERT(metadata.mPersistenceType ==
+ aPersistenceType);
+
+ // FIXME(tt): The check for origin name consistency can
+ // be removed once we have an upgrade to traverse origin
+ // directories and check through the directory metadata
+ // files.
+ const auto originSanitized =
+ MakeSanitizedOriginCString(metadata.mOrigin);
+
+ NS_ConvertUTF16toUTF8 utf8LeafName(leafName);
+ if (!originSanitized.Equals(utf8LeafName)) {
+ QM_WARNING(
+ "The name of the origin directory (%s) doesn't "
+ "match the sanitized origin string (%s) in the "
+ "metadata file!",
+ utf8LeafName.get(), originSanitized.get());
+
+ // If it's the known case, we try to restore the
+ // origin directory name if it's possible.
+ if (originSanitized.Equals(utf8LeafName + "."_ns)) {
+ const int64_t lastAccessTime =
+ metadata.mLastAccessTime;
+ const bool persisted = metadata.mPersisted;
+ renameAndInitInfos.AppendElement(RenameAndInitInfo{
+ std::move(childDirectory), std::move(metadata),
+ lastAccessTime, persisted});
+ break;
+ }
+
+ // XXXtt: Try to restore the unknown cases base on the
+ // content for their metadata files. Note that if the
+ // restore fails, QM should maintain a list and ensure
+ // they won't be accessed after initialization.
+ }
+
+ QM_TRY(QM_OR_ELSE_WARN_IF(
+ // Expression.
+ MOZ_TO_RESULT(InitializeOrigin(
+ aPersistenceType, metadata,
+ metadata.mLastAccessTime, metadata.mPersisted,
+ childDirectory)),
+ // Predicate.
+ IsDatabaseCorruptionError,
+ // Fallback.
+ ([&childDirectory](
+ const nsresult rv) -> Result<Ok, nsresult> {
+ // If the origin can't be initialized due to
+ // corruption, this is a permanent
+ // condition, and we need to remove all data
+ // for the origin on disk.
+
+ QM_TRY(
+ MOZ_TO_RESULT(childDirectory->Remove(true)));
+
+ return Ok{};
+ })));
+
+ std::forward<OriginFunc>(aOriginFunc)(metadata);
+
+ break;
+ }
+
+ case nsIFileKind::ExistsAsFile:
+ if (IsOSMetadata(leafName) || IsDotFile(leafName)) {
+ break;
+ }
+
+ // Unknown files during initialization are now allowed.
+ // Just warn if we find them.
+ UNKNOWN_FILE_WARNING(leafName);
+ break;
+
+ case nsIFileKind::DoesNotExist:
+ // Ignore files that got removed externally while
+ // iterating.
+ break;
+ }
+
+ return Ok{};
+ }()),
+ OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
+
+ return Ok{};
+ }),
+ OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
+
+ return Ok{};
+ }()));
+
+ for (auto& info : renameAndInitInfos) {
+ QM_TRY(([&]() -> Result<Ok, nsresult> {
+ QM_TRY(([&directory, &info, this, aPersistenceType,
+ &aOriginFunc]() -> Result<Ok, nsresult> {
+ const auto originDirName =
+ MakeSanitizedOriginString(info.mFullOriginMetadata.mOrigin);
+
+ // Check if targetDirectory exist.
+ QM_TRY_INSPECT(const auto& targetDirectory,
+ CloneFileAndAppend(*directory, originDirName));
+
+ QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE_MEMBER(
+ targetDirectory, Exists));
+
+ if (exists) {
+ QM_TRY(MOZ_TO_RESULT(info.mOriginDirectory->Remove(true)));
+
+ return Ok{};
+ }
+
+ QM_TRY(MOZ_TO_RESULT(
+ info.mOriginDirectory->RenameTo(nullptr, originDirName)));
+
+ // XXX We don't check corruption here ?
+ QM_TRY(MOZ_TO_RESULT(InitializeOrigin(
+ aPersistenceType, info.mFullOriginMetadata, info.mTimestamp,
+ info.mPersisted, targetDirectory)));
+
+ std::forward<OriginFunc>(aOriginFunc)(info.mFullOriginMetadata);
+
+ return Ok{};
+ }()),
+ OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
+
+ return Ok{};
+ }()));
+ }
+
+#ifdef NIGHTLY_BUILD
+ if (NS_FAILED(statusKeeper)) {
+ return statusKeeper;
+ }
+#endif
+
+ return NS_OK;
+}
+
+nsresult QuotaManager::InitializeOrigin(PersistenceType aPersistenceType,
+ const OriginMetadata& aOriginMetadata,
+ int64_t aAccessTime, bool aPersisted,
+ nsIFile* aDirectory) {
+ AssertIsOnIOThread();
+
+ const bool trackQuota = aPersistenceType != PERSISTENCE_TYPE_PERSISTENT;
+
+ // We need to initialize directories of all clients if they exists and also
+ // get the total usage to initialize the quota.
+
+ ClientUsageArray clientUsages;
+
+ // A keeper to defer the return only in Nightly, so that the telemetry data
+ // for whole profile can be collected
+#ifdef NIGHTLY_BUILD
+ nsresult statusKeeper = NS_OK;
+#endif
+
+ QM_TRY(([&, statusKeeperFunc = [&](const nsresult rv) {
+ RECORD_IN_NIGHTLY(statusKeeper, rv);
+ }]() -> Result<Ok, nsresult> {
+ QM_TRY(
+ CollectEachFile(
+ *aDirectory,
+ [&](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
+ if (NS_WARN_IF(IsShuttingDown())) {
+ RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
+ }
+
+ QM_TRY(
+ ([this, &file, trackQuota, aPersistenceType, &aOriginMetadata,
+ &clientUsages]() -> Result<Ok, nsresult> {
+ QM_TRY_INSPECT(const auto& leafName,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsAutoString, file, GetLeafName));
+
+ QM_TRY_INSPECT(const auto& dirEntryKind,
+ GetDirEntryKind(*file));
+
+ switch (dirEntryKind) {
+ case nsIFileKind::ExistsAsDirectory: {
+ Client::Type clientType;
+ const bool ok = Client::TypeFromText(
+ leafName, clientType, fallible);
+ if (!ok) {
+ // Unknown directories during initialization are now
+ // allowed. Just warn if we find them.
+ UNKNOWN_FILE_WARNING(leafName);
+ break;
+ }
+
+ if (trackQuota) {
+ QM_TRY_INSPECT(
+ const auto& usageInfo,
+ (*mClients)[clientType]->InitOrigin(
+ aPersistenceType, aOriginMetadata,
+ /* aCanceled */ Atomic<bool>(false)));
+
+ MOZ_ASSERT(!clientUsages[clientType]);
+
+ if (usageInfo.TotalUsage()) {
+ // XXX(Bug 1683863) Until we identify the root cause
+ // of seemingly converted-from-negative usage
+ // values, we will just treat them as unset here,
+ // but log a warning to the browser console.
+ if (static_cast<int64_t>(*usageInfo.TotalUsage()) >=
+ 0) {
+ clientUsages[clientType] = usageInfo.TotalUsage();
+ } else {
+#if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
+ const nsCOMPtr<nsIConsoleService> console =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (console) {
+ console->LogStringMessage(
+ nsString(
+ u"QuotaManager warning: client "_ns +
+ leafName +
+ u" reported negative usage for group "_ns +
+ NS_ConvertUTF8toUTF16(
+ aOriginMetadata.mGroup) +
+ u", origin "_ns +
+ NS_ConvertUTF8toUTF16(
+ aOriginMetadata.mOrigin))
+ .get());
+ }
+#endif
+ }
+ }
+ } else {
+ QM_TRY(MOZ_TO_RESULT(
+ (*mClients)[clientType]
+ ->InitOriginWithoutTracking(
+ aPersistenceType, aOriginMetadata,
+ /* aCanceled */ Atomic<bool>(false))));
+ }
+
+ break;
+ }
+
+ case nsIFileKind::ExistsAsFile:
+ if (IsOriginMetadata(leafName)) {
+ break;
+ }
+
+ if (IsTempMetadata(leafName)) {
+ QM_TRY(MOZ_TO_RESULT(
+ file->Remove(/* recursive */ false)));
+
+ break;
+ }
+
+ if (IsOSMetadata(leafName) || IsDotFile(leafName)) {
+ break;
+ }
+
+ // Unknown files during initialization are now allowed.
+ // Just warn if we find them.
+ UNKNOWN_FILE_WARNING(leafName);
+ // Bug 1595448 will handle the case for unknown files
+ // like idb, cache, or ls.
+ break;
+
+ case nsIFileKind::DoesNotExist:
+ // Ignore files that got removed externally while
+ // iterating.
+ break;
+ }
+
+ return Ok{};
+ }()),
+ OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
+
+ return Ok{};
+ }),
+ OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
+
+ return Ok{};
+ }()));
+
+#ifdef NIGHTLY_BUILD
+ if (NS_FAILED(statusKeeper)) {
+ return statusKeeper;
+ }
+#endif
+
+ if (trackQuota) {
+ const auto usage = std::accumulate(
+ clientUsages.cbegin(), clientUsages.cend(), CheckedUint64(0),
+ [](CheckedUint64 value, const Maybe<uint64_t>& clientUsage) {
+ return value + clientUsage.valueOr(0);
+ });
+
+ // XXX Should we log more information, i.e. the whole clientUsages array, in
+ // case usage is not valid?
+
+ QM_TRY(OkIf(usage.isValid()), NS_ERROR_FAILURE);
+
+ InitQuotaForOrigin(
+ FullOriginMetadata{aOriginMetadata, aPersisted, aAccessTime},
+ clientUsages, usage.value());
+ }
+
+ return NS_OK;
+}
+
+nsresult
+QuotaManager::UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
+ nsIFile* aIndexedDBDir) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aIndexedDBDir);
+
+ const auto innerFunc = [this, &aIndexedDBDir](const auto&) -> nsresult {
+ bool isDirectory;
+ QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->IsDirectory(&isDirectory)));
+
+ if (!isDirectory) {
+ NS_WARNING("indexedDB entry is not a directory!");
+ return NS_OK;
+ }
+
+ auto persistentStorageDirOrErr = QM_NewLocalFile(*mStoragePath);
+ if (NS_WARN_IF(persistentStorageDirOrErr.isErr())) {
+ return persistentStorageDirOrErr.unwrapErr();
+ }
+
+ nsCOMPtr<nsIFile> persistentStorageDir = persistentStorageDirOrErr.unwrap();
+
+ QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Append(
+ nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
+
+ bool exists;
+ QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Exists(&exists)));
+
+ if (exists) {
+ QM_WARNING("Deleting old <profile>/indexedDB directory!");
+
+ QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->Remove(/* aRecursive */ true)));
+
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> storageDir;
+ QM_TRY(MOZ_TO_RESULT(
+ persistentStorageDir->GetParent(getter_AddRefs(storageDir))));
+
+ // MoveTo() is atomic if the move happens on the same volume which should
+ // be our case, so even if we crash in the middle of the operation nothing
+ // breaks next time we try to initialize.
+ // However there's a theoretical possibility that the indexedDB directory
+ // is on different volume, but it should be rare enough that we don't have
+ // to worry about it.
+ QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->MoveTo(
+ storageDir, nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
+
+ return NS_OK;
+ };
+
+ return ExecuteInitialization(Initialization::UpgradeFromIndexedDBDirectory,
+ innerFunc);
+}
+
+nsresult
+QuotaManager::UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
+ nsIFile* aPersistentStorageDir) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aPersistentStorageDir);
+
+ const auto innerFunc = [this,
+ &aPersistentStorageDir](const auto&) -> nsresult {
+ QM_TRY_INSPECT(
+ const bool& isDirectory,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aPersistentStorageDir, IsDirectory));
+
+ if (!isDirectory) {
+ NS_WARNING("persistent entry is not a directory!");
+ return NS_OK;
+ }
+
+ {
+ QM_TRY_INSPECT(const auto& defaultStorageDir,
+ QM_NewLocalFile(*mDefaultStoragePath));
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(defaultStorageDir, Exists));
+
+ if (exists) {
+ QM_WARNING("Deleting old <profile>/storage/persistent directory!");
+
+ QM_TRY(MOZ_TO_RESULT(
+ aPersistentStorageDir->Remove(/* aRecursive */ true)));
+
+ return NS_OK;
+ }
+ }
+
+ {
+ // Create real metadata files for origin directories in persistent
+ // storage.
+ auto helper = MakeRefPtr<CreateOrUpgradeDirectoryMetadataHelper>(
+ aPersistentStorageDir);
+
+ QM_TRY(MOZ_TO_RESULT(helper->Init()));
+
+ QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
+
+ // Upgrade metadata files for origin directories in temporary storage.
+ QM_TRY_INSPECT(const auto& temporaryStorageDir,
+ QM_NewLocalFile(*mTemporaryStoragePath));
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(temporaryStorageDir, Exists));
+
+ if (exists) {
+ QM_TRY_INSPECT(
+ const bool& isDirectory,
+ MOZ_TO_RESULT_INVOKE_MEMBER(temporaryStorageDir, IsDirectory));
+
+ if (!isDirectory) {
+ NS_WARNING("temporary entry is not a directory!");
+ return NS_OK;
+ }
+
+ helper = MakeRefPtr<CreateOrUpgradeDirectoryMetadataHelper>(
+ temporaryStorageDir);
+
+ QM_TRY(MOZ_TO_RESULT(helper->Init()));
+
+ QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
+ }
+ }
+
+ // And finally rename persistent to default.
+ QM_TRY(MOZ_TO_RESULT(aPersistentStorageDir->RenameTo(
+ nullptr, nsLiteralString(DEFAULT_DIRECTORY_NAME))));
+
+ return NS_OK;
+ };
+
+ return ExecuteInitialization(
+ Initialization::UpgradeFromPersistentStorageDirectory, innerFunc);
+}
+
+template <typename Helper>
+nsresult QuotaManager::UpgradeStorage(const int32_t aOldVersion,
+ const int32_t aNewVersion,
+ mozIStorageConnection* aConnection) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aNewVersion > aOldVersion);
+ MOZ_ASSERT(aNewVersion <= kStorageVersion);
+ MOZ_ASSERT(aConnection);
+
+ for (const PersistenceType persistenceType : kAllPersistenceTypes) {
+ QM_TRY_UNWRAP(auto directory,
+ QM_NewLocalFile(GetStoragePath(persistenceType)));
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
+
+ if (!exists) {
+ continue;
+ }
+
+ RefPtr<UpgradeStorageHelperBase> helper = new Helper(directory);
+
+ QM_TRY(MOZ_TO_RESULT(helper->Init()));
+
+ QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
+ }
+
+#ifdef DEBUG
+ {
+ QM_TRY_INSPECT(const int32_t& storageVersion,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
+
+ MOZ_ASSERT(storageVersion == aOldVersion);
+ }
+#endif
+
+ QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(aNewVersion)));
+
+ return NS_OK;
+}
+
+nsresult QuotaManager::UpgradeStorageFrom0_0To1_0(
+ mozIStorageConnection* aConnection) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom0_0To1_0Helper>(
+ 0, MakeStorageVersion(1, 0), aConnection)));
+
+ return NS_OK;
+ };
+
+ return ExecuteInitialization(Initialization::UpgradeStorageFrom0_0To1_0,
+ innerFunc);
+}
+
+nsresult QuotaManager::UpgradeStorageFrom1_0To2_0(
+ mozIStorageConnection* aConnection) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ // The upgrade consists of a number of logically distinct bugs that
+ // intentionally got fixed at the same time to trigger just one major
+ // version bump.
+ //
+ //
+ // Morgue directory cleanup
+ // [Feature/Bug]:
+ // The original bug that added "on demand" morgue cleanup is 1165119.
+ //
+ // [Mutations]:
+ // Morgue directories are removed from all origin directories during the
+ // upgrade process. Origin initialization and usage calculation doesn't try
+ // to remove morgue directories anymore.
+ //
+ // [Downgrade-incompatible changes]:
+ // Morgue directories can reappear if user runs an already upgraded profile
+ // in an older version of Firefox. Morgue directories then prevent current
+ // Firefox from initializing and using the storage.
+ //
+ //
+ // App data removal
+ // [Feature/Bug]:
+ // The bug that removes isApp flags is 1311057.
+ //
+ // [Mutations]:
+ // Origin directories with appIds are removed during the upgrade process.
+ //
+ // [Downgrade-incompatible changes]:
+ // Origin directories with appIds can reappear if user runs an already
+ // upgraded profile in an older version of Firefox. Origin directories with
+ // appIds don't prevent current Firefox from initializing and using the
+ // storage, but they wouldn't ever be removed again, potentially causing
+ // problems once appId is removed from origin attributes.
+ //
+ //
+ // Strip obsolete origin attributes
+ // [Feature/Bug]:
+ // The bug that strips obsolete origin attributes is 1314361.
+ //
+ // [Mutations]:
+ // Origin directories with obsolete origin attributes are renamed and their
+ // metadata files are updated during the upgrade process.
+ //
+ // [Downgrade-incompatible changes]:
+ // Origin directories with obsolete origin attributes can reappear if user
+ // runs an already upgraded profile in an older version of Firefox. Origin
+ // directories with obsolete origin attributes don't prevent current Firefox
+ // from initializing and using the storage, but they wouldn't ever be upgraded
+ // again, potentially causing problems in future.
+ //
+ //
+ // File manager directory renaming (client specific)
+ // [Feature/Bug]:
+ // The original bug that added "on demand" file manager directory renaming is
+ // 1056939.
+ //
+ // [Mutations]:
+ // All file manager directories are renamed to contain the ".files" suffix.
+ //
+ // [Downgrade-incompatible changes]:
+ // File manager directories with the ".files" suffix prevent older versions of
+ // Firefox from initializing and using the storage.
+ // File manager directories without the ".files" suffix can appear if user
+ // runs an already upgraded profile in an older version of Firefox. File
+ // manager directories without the ".files" suffix then prevent current
+ // Firefox from initializing and using the storage.
+
+ const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom1_0To2_0Helper>(
+ MakeStorageVersion(1, 0), MakeStorageVersion(2, 0), aConnection)));
+
+ return NS_OK;
+ };
+
+ return ExecuteInitialization(Initialization::UpgradeStorageFrom1_0To2_0,
+ innerFunc);
+}
+
+nsresult QuotaManager::UpgradeStorageFrom2_0To2_1(
+ mozIStorageConnection* aConnection) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ // The upgrade is mainly to create a directory padding file in DOM Cache
+ // directory to record the overall padding size of an origin.
+
+ const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom2_0To2_1Helper>(
+ MakeStorageVersion(2, 0), MakeStorageVersion(2, 1), aConnection)));
+
+ return NS_OK;
+ };
+
+ return ExecuteInitialization(Initialization::UpgradeStorageFrom2_0To2_1,
+ innerFunc);
+}
+
+nsresult QuotaManager::UpgradeStorageFrom2_1To2_2(
+ mozIStorageConnection* aConnection) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ // The upgrade is mainly to clean obsolete origins in the repositoies, remove
+ // asmjs client, and ".tmp" file in the idb folers.
+
+ const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom2_1To2_2Helper>(
+ MakeStorageVersion(2, 1), MakeStorageVersion(2, 2), aConnection)));
+
+ return NS_OK;
+ };
+
+ return ExecuteInitialization(Initialization::UpgradeStorageFrom2_1To2_2,
+ innerFunc);
+}
+
+nsresult QuotaManager::UpgradeStorageFrom2_2To2_3(
+ mozIStorageConnection* aConnection) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ const auto innerFunc = [&aConnection](const auto&) -> nsresult {
+ // Table `database`
+ QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
+ nsLiteralCString("CREATE TABLE database"
+ "( cache_version INTEGER NOT NULL DEFAULT 0"
+ ");"))));
+
+ QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
+ nsLiteralCString("INSERT INTO database (cache_version) "
+ "VALUES (0)"))));
+
+#ifdef DEBUG
+ {
+ QM_TRY_INSPECT(
+ const int32_t& storageVersion,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
+
+ MOZ_ASSERT(storageVersion == MakeStorageVersion(2, 2));
+ }
+#endif
+
+ QM_TRY(
+ MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeStorageVersion(2, 3))));
+
+ return NS_OK;
+ };
+
+ return ExecuteInitialization(Initialization::UpgradeStorageFrom2_2To2_3,
+ innerFunc);
+}
+
+nsresult QuotaManager::MaybeRemoveLocalStorageDataAndArchive(
+ nsIFile& aLsArchiveFile) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(!CachedNextGenLocalStorageEnabled());
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
+
+ if (!exists) {
+ // If the ls archive doesn't exist then ls directories can't exist either.
+ return NS_OK;
+ }
+
+ QM_TRY(MOZ_TO_RESULT(MaybeRemoveLocalStorageDirectories()));
+
+ InvalidateQuotaCache();
+
+ // Finally remove the ls archive, so we don't have to check all origin
+ // directories next time this method is called.
+ QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(false)));
+
+ return NS_OK;
+}
+
+nsresult QuotaManager::MaybeRemoveLocalStorageDirectories() {
+ AssertIsOnIOThread();
+
+ QM_TRY_INSPECT(const auto& defaultStorageDir,
+ QM_NewLocalFile(*mDefaultStoragePath));
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(defaultStorageDir, Exists));
+
+ if (!exists) {
+ return NS_OK;
+ }
+
+ QM_TRY(CollectEachFile(
+ *defaultStorageDir,
+ [](const nsCOMPtr<nsIFile>& originDir) -> Result<Ok, nsresult> {
+#ifdef DEBUG
+ {
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(originDir, Exists));
+ MOZ_ASSERT(exists);
+ }
+#endif
+
+ QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*originDir));
+
+ switch (dirEntryKind) {
+ case nsIFileKind::ExistsAsDirectory: {
+ QM_TRY_INSPECT(
+ const auto& lsDir,
+ CloneFileAndAppend(*originDir, NS_LITERAL_STRING_FROM_CSTRING(
+ LS_DIRECTORY_NAME)));
+
+ {
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(lsDir, Exists));
+
+ if (!exists) {
+ return Ok{};
+ }
+ }
+
+ {
+ QM_TRY_INSPECT(const bool& isDirectory,
+ MOZ_TO_RESULT_INVOKE_MEMBER(lsDir, IsDirectory));
+
+ if (!isDirectory) {
+ QM_WARNING("ls entry is not a directory!");
+
+ return Ok{};
+ }
+ }
+
+ nsString path;
+ QM_TRY(MOZ_TO_RESULT(lsDir->GetPath(path)));
+
+ QM_WARNING("Deleting %s directory!",
+ NS_ConvertUTF16toUTF8(path).get());
+
+ QM_TRY(MOZ_TO_RESULT(lsDir->Remove(/* aRecursive */ true)));
+
+ break;
+ }
+
+ case nsIFileKind::ExistsAsFile: {
+ QM_TRY_INSPECT(const auto& leafName,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsAutoString, originDir, GetLeafName));
+
+ // Unknown files during upgrade are allowed. Just warn if we find
+ // them.
+ if (!IsOSMetadata(leafName)) {
+ UNKNOWN_FILE_WARNING(leafName);
+ }
+
+ break;
+ }
+
+ case nsIFileKind::DoesNotExist:
+ // Ignore files that got removed externally while iterating.
+ break;
+ }
+ return Ok{};
+ }));
+
+ return NS_OK;
+}
+
+Result<Ok, nsresult> QuotaManager::CopyLocalStorageArchiveFromWebAppsStore(
+ nsIFile& aLsArchiveFile) const {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
+
+#ifdef DEBUG
+ {
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
+ MOZ_ASSERT(!exists);
+ }
+#endif
+
+ // Get the storage service first, we will need it at multiple places.
+ QM_TRY_INSPECT(const auto& ss,
+ MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
+ MOZ_SELECT_OVERLOAD(do_GetService),
+ MOZ_STORAGE_SERVICE_CONTRACTID));
+
+ // Get the web apps store file.
+ QM_TRY_INSPECT(const auto& webAppsStoreFile, QM_NewLocalFile(mBasePath));
+
+ QM_TRY(MOZ_TO_RESULT(
+ webAppsStoreFile->Append(nsLiteralString(WEB_APPS_STORE_FILE_NAME))));
+
+ // Now check if the web apps store is useable.
+ QM_TRY_INSPECT(const auto& connection,
+ CreateWebAppsStoreConnection(*webAppsStoreFile, *ss));
+
+ if (connection) {
+ // Find out the journal mode.
+ QM_TRY_INSPECT(const auto& stmt,
+ CreateAndExecuteSingleStepStatement(
+ *connection, "PRAGMA journal_mode;"_ns));
+
+ QM_TRY_INSPECT(const auto& journalMode,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, *stmt,
+ GetUTF8String, 0));
+
+ QM_TRY(MOZ_TO_RESULT(stmt->Finalize()));
+
+ if (journalMode.EqualsLiteral("wal")) {
+ // We don't copy the WAL file, so make sure the old database is fully
+ // checkpointed.
+ QM_TRY(MOZ_TO_RESULT(
+ connection->ExecuteSimpleSQL("PRAGMA wal_checkpoint(TRUNCATE);"_ns)));
+ }
+
+ // Explicitely close the connection before the old database is copied.
+ QM_TRY(MOZ_TO_RESULT(connection->Close()));
+
+ // Copy the old database. The database is copied from
+ // <profile>/webappsstore.sqlite to
+ // <profile>/storage/ls-archive-tmp.sqlite
+ // We use a "-tmp" postfix since we are not done yet.
+ QM_TRY_INSPECT(const auto& storageDir, QM_NewLocalFile(*mStoragePath));
+
+ QM_TRY(MOZ_TO_RESULT(webAppsStoreFile->CopyTo(
+ storageDir, nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME))));
+
+ QM_TRY_INSPECT(const auto& lsArchiveTmpFile,
+ GetLocalStorageArchiveTmpFile(*mStoragePath));
+
+ if (journalMode.EqualsLiteral("wal")) {
+ QM_TRY_INSPECT(
+ const auto& lsArchiveTmpConnection,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
+ lsArchiveTmpFile, mozIStorageService::CONNECTION_DEFAULT));
+
+ // The archive will only be used for lazy data migration. There won't be
+ // any concurrent readers and writers that could benefit from Write-Ahead
+ // Logging. So switch to a standard rollback journal. The standard
+ // rollback journal also provides atomicity across multiple attached
+ // databases which is import for the lazy data migration to work safely.
+ QM_TRY(MOZ_TO_RESULT(lsArchiveTmpConnection->ExecuteSimpleSQL(
+ "PRAGMA journal_mode = DELETE;"_ns)));
+
+ // Close the connection explicitly. We are going to rename the file below.
+ QM_TRY(MOZ_TO_RESULT(lsArchiveTmpConnection->Close()));
+ }
+
+ // Finally, rename ls-archive-tmp.sqlite to ls-archive.sqlite
+ QM_TRY(MOZ_TO_RESULT(lsArchiveTmpFile->MoveTo(
+ nullptr, nsLiteralString(LS_ARCHIVE_FILE_NAME))));
+
+ return Ok{};
+ }
+
+ // If webappsstore database is not useable, just create an empty archive.
+ // XXX The code below should be removed and the caller should call us only
+ // when webappstore.sqlite exists. CreateWebAppsStoreConnection should be
+ // reworked to propagate database corruption instead of returning null
+ // connection.
+ // So, if there's no webappsstore.sqlite
+ // MaybeCreateOrUpgradeLocalStorageArchive will call
+ // CreateEmptyLocalStorageArchive instead of
+ // CopyLocalStorageArchiveFromWebAppsStore.
+ // If there's any corruption detected during
+ // MaybeCreateOrUpgradeLocalStorageArchive (including nested calls like
+ // CopyLocalStorageArchiveFromWebAppsStore and CreateWebAppsStoreConnection)
+ // EnsureStorageIsInitializedInternal will fallback to
+ // CreateEmptyLocalStorageArchive.
+
+ // Ensure the storage directory actually exists.
+ QM_TRY_INSPECT(const auto& storageDirectory, QM_NewLocalFile(*mStoragePath));
+
+ QM_TRY_INSPECT(const bool& created, EnsureDirectory(*storageDirectory));
+
+ Unused << created;
+
+ QM_TRY_UNWRAP(auto lsArchiveConnection,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
+ &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
+
+ QM_TRY(MOZ_TO_RESULT(
+ StorageDBUpdater::CreateCurrentSchema(lsArchiveConnection)));
+
+ return Ok{};
+}
+
+Result<nsCOMPtr<mozIStorageConnection>, nsresult>
+QuotaManager::CreateLocalStorageArchiveConnection(
+ nsIFile& aLsArchiveFile) const {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
+
+#ifdef DEBUG
+ {
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
+ MOZ_ASSERT(exists);
+ }
+#endif
+
+ QM_TRY_INSPECT(const bool& isDirectory,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, IsDirectory));
+
+ // A directory with the name of the archive file is treated as corruption
+ // (similarly as wrong content of the file).
+ QM_TRY(OkIf(!isDirectory), Err(NS_ERROR_FILE_CORRUPTED));
+
+ QM_TRY_INSPECT(const auto& ss,
+ MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
+ MOZ_SELECT_OVERLOAD(do_GetService),
+ MOZ_STORAGE_SERVICE_CONTRACTID));
+
+ // This may return NS_ERROR_FILE_CORRUPTED too.
+ QM_TRY_UNWRAP(auto connection,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
+ &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
+
+ // The legacy LS implementation removes the database and creates an empty one
+ // when the schema can't be updated. The same effect can be achieved here by
+ // mapping all errors to NS_ERROR_FILE_CORRUPTED. One such case is tested by
+ // sub test case 3 of dom/localstorage/test/unit/test_archive.js
+ QM_TRY(
+ MOZ_TO_RESULT(StorageDBUpdater::Update(connection))
+ .mapErr([](const nsresult rv) { return NS_ERROR_FILE_CORRUPTED; }));
+
+ return connection;
+}
+
+Result<nsCOMPtr<mozIStorageConnection>, nsresult>
+QuotaManager::RecopyLocalStorageArchiveFromWebAppsStore(
+ nsIFile& aLsArchiveFile) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
+
+ QM_TRY(MOZ_TO_RESULT(MaybeRemoveLocalStorageDirectories()));
+
+#ifdef DEBUG
+ {
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
+
+ MOZ_ASSERT(exists);
+ }
+#endif
+
+ QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(false)));
+
+ QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
+
+ QM_TRY_UNWRAP(auto connection,
+ CreateLocalStorageArchiveConnection(aLsArchiveFile));
+
+ QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
+
+ return connection;
+}
+
+Result<nsCOMPtr<mozIStorageConnection>, nsresult>
+QuotaManager::DowngradeLocalStorageArchive(nsIFile& aLsArchiveFile) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
+
+ QM_TRY_UNWRAP(auto connection,
+ RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
+
+ QM_TRY(MOZ_TO_RESULT(
+ SaveLocalStorageArchiveVersion(connection, kLocalStorageArchiveVersion)));
+
+ return connection;
+}
+
+Result<nsCOMPtr<mozIStorageConnection>, nsresult>
+QuotaManager::UpgradeLocalStorageArchiveFromLessThan4To4(
+ nsIFile& aLsArchiveFile) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
+
+ QM_TRY_UNWRAP(auto connection,
+ RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
+
+ QM_TRY(MOZ_TO_RESULT(SaveLocalStorageArchiveVersion(connection, 4)));
+
+ return connection;
+}
+
+/*
+nsresult QuotaManager::UpgradeLocalStorageArchiveFrom4To5(
+ nsCOMPtr<mozIStorageConnection>& aConnection) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
+
+ nsresult rv = SaveLocalStorageArchiveVersion(aConnection, 5);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+*/
+
+#ifdef DEBUG
+
+void QuotaManager::AssertStorageIsInitializedInternal() const {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(IsStorageInitializedInternal());
+}
+
+#endif // DEBUG
+
+nsresult QuotaManager::MaybeUpgradeToDefaultStorageDirectory(
+ nsIFile& aStorageFile) {
+ AssertIsOnIOThread();
+
+ QM_TRY_INSPECT(const auto& storageFileExists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aStorageFile, Exists));
+
+ if (!storageFileExists) {
+ QM_TRY_INSPECT(const auto& indexedDBDir, QM_NewLocalFile(*mIndexedDBPath));
+
+ QM_TRY_INSPECT(const auto& indexedDBDirExists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(indexedDBDir, Exists));
+
+ if (indexedDBDirExists) {
+ QM_TRY(MOZ_TO_RESULT(
+ UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
+ indexedDBDir)));
+ }
+
+ QM_TRY_INSPECT(const auto& persistentStorageDir,
+ QM_NewLocalFile(*mStoragePath));
+
+ QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Append(
+ nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
+
+ QM_TRY_INSPECT(const auto& persistentStorageDirExists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(persistentStorageDir, Exists));
+
+ if (persistentStorageDirExists) {
+ QM_TRY(MOZ_TO_RESULT(
+ UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
+ persistentStorageDir)));
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult QuotaManager::MaybeCreateOrUpgradeStorage(
+ mozIStorageConnection& aConnection) {
+ AssertIsOnIOThread();
+
+ QM_TRY_UNWRAP(auto storageVersion,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
+
+ // Hacky downgrade logic!
+ // If we see major.minor of 3.0, downgrade it to be 2.1.
+ if (storageVersion == kHackyPreDowngradeStorageVersion) {
+ storageVersion = kHackyPostDowngradeStorageVersion;
+ QM_TRY(MOZ_TO_RESULT(aConnection.SetSchemaVersion(storageVersion)),
+ QM_PROPAGATE,
+ [](const auto&) { MOZ_ASSERT(false, "Downgrade didn't take."); });
+ }
+
+ QM_TRY(OkIf(GetMajorStorageVersion(storageVersion) <= kMajorStorageVersion),
+ NS_ERROR_FAILURE, [](const auto&) {
+ NS_WARNING("Unable to initialize storage, version is too high!");
+ });
+
+ if (storageVersion < kStorageVersion) {
+ const bool newDatabase = !storageVersion;
+
+ QM_TRY_INSPECT(const auto& storageDir, QM_NewLocalFile(*mStoragePath));
+
+ QM_TRY_INSPECT(const auto& storageDirExists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(storageDir, Exists));
+
+ const bool newDirectory = !storageDirExists;
+
+ if (newDatabase) {
+ // Set the page size first.
+ if (kSQLitePageSizeOverride) {
+ QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(nsPrintfCString(
+ "PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride))));
+ }
+ }
+
+ mozStorageTransaction transaction(
+ &aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+ QM_TRY(MOZ_TO_RESULT(transaction.Start()));
+
+ // An upgrade method can upgrade the database, the storage or both.
+ // The upgrade loop below can only be avoided when there's no database and
+ // no storage yet (e.g. new profile).
+ if (newDatabase && newDirectory) {
+ QM_TRY(MOZ_TO_RESULT(CreateTables(&aConnection)));
+
+#ifdef DEBUG
+ {
+ QM_TRY_INSPECT(
+ const int32_t& storageVersion,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion),
+ QM_ASSERT_UNREACHABLE);
+ MOZ_ASSERT(storageVersion == kStorageVersion);
+ }
+#endif
+
+ QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
+ nsLiteralCString("INSERT INTO database (cache_version) "
+ "VALUES (0)"))));
+ } else {
+ // This logic needs to change next time we change the storage!
+ static_assert(kStorageVersion == int32_t((2 << 16) + 3),
+ "Upgrade function needed due to storage version increase.");
+
+ while (storageVersion != kStorageVersion) {
+ if (storageVersion == 0) {
+ QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom0_0To1_0(&aConnection)));
+ } else if (storageVersion == MakeStorageVersion(1, 0)) {
+ QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom1_0To2_0(&aConnection)));
+ } else if (storageVersion == MakeStorageVersion(2, 0)) {
+ QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_0To2_1(&aConnection)));
+ } else if (storageVersion == MakeStorageVersion(2, 1)) {
+ QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_1To2_2(&aConnection)));
+ } else if (storageVersion == MakeStorageVersion(2, 2)) {
+ QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_2To2_3(&aConnection)));
+ } else {
+ QM_FAIL(NS_ERROR_FAILURE, []() {
+ NS_WARNING(
+ "Unable to initialize storage, no upgrade path is "
+ "available!");
+ });
+ }
+
+ QM_TRY_UNWRAP(storageVersion, MOZ_TO_RESULT_INVOKE_MEMBER(
+ aConnection, GetSchemaVersion));
+ }
+
+ MOZ_ASSERT(storageVersion == kStorageVersion);
+ }
+
+ QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
+ }
+
+ return NS_OK;
+}
+
+OkOrErr QuotaManager::MaybeRemoveLocalStorageArchiveTmpFile() {
+ AssertIsOnIOThread();
+
+ QM_TRY_INSPECT(
+ const auto& lsArchiveTmpFile,
+ QM_TO_RESULT_TRANSFORM(GetLocalStorageArchiveTmpFile(*mStoragePath)));
+
+ QM_TRY_INSPECT(const bool& exists,
+ QM_TO_RESULT_INVOKE_MEMBER(lsArchiveTmpFile, Exists));
+
+ if (exists) {
+ QM_TRY(QM_TO_RESULT(lsArchiveTmpFile->Remove(false)));
+ }
+
+ return Ok{};
+}
+
+Result<Ok, nsresult> QuotaManager::MaybeCreateOrUpgradeLocalStorageArchive(
+ nsIFile& aLsArchiveFile) {
+ AssertIsOnIOThread();
+
+ QM_TRY_INSPECT(
+ const bool& lsArchiveFileExisted,
+ ([this, &aLsArchiveFile]() -> Result<bool, nsresult> {
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
+
+ if (!exists) {
+ QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
+ }
+
+ return exists;
+ }()));
+
+ QM_TRY_UNWRAP(auto connection,
+ CreateLocalStorageArchiveConnection(aLsArchiveFile));
+
+ QM_TRY_INSPECT(const auto& initialized,
+ IsLocalStorageArchiveInitialized(*connection));
+
+ if (!initialized) {
+ QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
+ }
+
+ QM_TRY_UNWRAP(int32_t version, LoadLocalStorageArchiveVersion(*connection));
+
+ if (version > kLocalStorageArchiveVersion) {
+ // Close local storage archive connection. We are going to remove underlying
+ // file.
+ QM_TRY(MOZ_TO_RESULT(connection->Close()));
+
+ // This will wipe the archive and any migrated data and recopy the archive
+ // from webappsstore.sqlite.
+ QM_TRY_UNWRAP(connection, DowngradeLocalStorageArchive(aLsArchiveFile));
+
+ QM_TRY_UNWRAP(version, LoadLocalStorageArchiveVersion(*connection));
+
+ MOZ_ASSERT(version == kLocalStorageArchiveVersion);
+ } else if (version != kLocalStorageArchiveVersion) {
+ // The version can be zero either when the archive didn't exist or it did
+ // exist, but the archive was created without any version information.
+ // We don't need to do any upgrades only if it didn't exist because existing
+ // archives without version information must be recopied to really fix bug
+ // 1542104. See also bug 1546305 which introduced archive versions.
+ if (!lsArchiveFileExisted) {
+ MOZ_ASSERT(version == 0);
+
+ QM_TRY(MOZ_TO_RESULT(SaveLocalStorageArchiveVersion(
+ connection, kLocalStorageArchiveVersion)));
+ } else {
+ static_assert(kLocalStorageArchiveVersion == 4,
+ "Upgrade function needed due to LocalStorage archive "
+ "version increase.");
+
+ while (version != kLocalStorageArchiveVersion) {
+ if (version < 4) {
+ // Close local storage archive connection. We are going to remove
+ // underlying file.
+ QM_TRY(MOZ_TO_RESULT(connection->Close()));
+
+ // This won't do an "upgrade" in a normal sense. It will wipe the
+ // archive and any migrated data and recopy the archive from
+ // webappsstore.sqlite
+ QM_TRY_UNWRAP(connection, UpgradeLocalStorageArchiveFromLessThan4To4(
+ aLsArchiveFile));
+ } /* else if (version == 4) {
+ QM_TRY(MOZ_TO_RESULT(UpgradeLocalStorageArchiveFrom4To5(connection)));
+ } */
+ else {
+ QM_FAIL(Err(NS_ERROR_FAILURE), []() {
+ QM_WARNING(
+ "Unable to initialize LocalStorage archive, no upgrade path "
+ "is available!");
+ });
+ }
+
+ QM_TRY_UNWRAP(version, LoadLocalStorageArchiveVersion(*connection));
+ }
+
+ MOZ_ASSERT(version == kLocalStorageArchiveVersion);
+ }
+ }
+
+ // At this point, we have finished initializing the local storage archive, and
+ // can continue storage initialization. We don't know though if the actual
+ // data in the archive file is readable. We can't do a PRAGMA integrity_check
+ // here though, because that would be too heavyweight.
+
+ return Ok{};
+}
+
+Result<Ok, nsresult> QuotaManager::CreateEmptyLocalStorageArchive(
+ nsIFile& aLsArchiveFile) const {
+ AssertIsOnIOThread();
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
+
+ // If it exists, remove it. It might be a directory, so remove it recursively.
+ if (exists) {
+ QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(true)));
+
+ // XXX If we crash right here, the next session will copy the archive from
+ // webappsstore.sqlite again!
+ // XXX Create a marker file before removing the archive which can be
+ // used in MaybeCreateOrUpgradeLocalStorageArchive to create an empty
+ // archive instead of recopying it from webapppstore.sqlite (in other
+ // words, finishing what was started here).
+ }
+
+ 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(const auto connection,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
+ &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
+
+ QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::CreateCurrentSchema(connection)));
+
+ QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
+
+ QM_TRY(MOZ_TO_RESULT(
+ SaveLocalStorageArchiveVersion(connection, kLocalStorageArchiveVersion)));
+
+ return Ok{};
+}
+
+RefPtr<BoolPromise> QuotaManager::InitializeStorage() {
+ AssertIsOnOwningThread();
+
+ // If storage is initialized but there's a clear storage or shutdown storage
+ // operation already scheduled, we can't immediately resolve the promise and
+ // return from the function because the clear or shutdown storage operation
+ // uninitializes storage.
+ if (mStorageInitialized && !mShutdownStorageOpCount) {
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }
+
+ RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
+ Nullable<PersistenceType>(), OriginScope::FromNull(),
+ Nullable<Client::Type>(),
+ /* aExclusive */ false);
+
+ return directoryLock->Acquire()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr(this),
+ directoryLock](const BoolPromise::ResolveOrRejectValue& aValue) mutable {
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
+ }
+
+ return self->InitializeStorage(std::move(directoryLock));
+ });
+}
+
+RefPtr<BoolPromise> QuotaManager::InitializeStorage(
+ RefPtr<UniversalDirectoryLock> aDirectoryLock) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aDirectoryLock);
+
+ if (mStorageInitialized && !mShutdownStorageOpCount) {
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }
+
+ auto initializeStorageOp =
+ CreateInitOp(WrapMovingNotNullUnchecked(this), std::move(aDirectoryLock));
+
+ RegisterNormalOriginOp(*initializeStorageOp);
+
+ initializeStorageOp->RunImmediately();
+
+ return initializeStorageOp->OnResults()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
+ }
+
+ self->mStorageInitialized = true;
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ });
+}
+
+RefPtr<BoolPromise> QuotaManager::StorageInitialized() {
+ AssertIsOnOwningThread();
+
+ auto storageInitializedOp =
+ CreateStorageInitializedOp(WrapMovingNotNullUnchecked(this));
+
+ RegisterNormalOriginOp(*storageInitializedOp);
+
+ storageInitializedOp->RunImmediately();
+
+ return storageInitializedOp->OnResults();
+}
+
+nsresult QuotaManager::EnsureStorageIsInitializedInternal() {
+ DiagnosticAssertIsOnIOThread();
+
+ const auto innerFunc =
+ [&](const auto& firstInitializationAttempt) -> nsresult {
+ if (mStorageConnection) {
+ MOZ_ASSERT(firstInitializationAttempt.Recorded());
+ return NS_OK;
+ }
+
+ QM_TRY_INSPECT(const auto& storageFile, QM_NewLocalFile(mBasePath));
+ QM_TRY(MOZ_TO_RESULT(storageFile->Append(mStorageName + kSQLiteSuffix)));
+
+ QM_TRY(MOZ_TO_RESULT(MaybeUpgradeToDefaultStorageDirectory(*storageFile)));
+
+ 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,
+ storageFile, mozIStorageService::CONNECTION_DEFAULT),
+ // Predicate.
+ IsDatabaseCorruptionError,
+ // Fallback.
+ ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));
+
+ if (!connection) {
+ // Nuke the database file.
+ QM_TRY(MOZ_TO_RESULT(storageFile->Remove(false)));
+
+ QM_TRY_UNWRAP(connection, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCOMPtr<mozIStorageConnection>, ss,
+ OpenUnsharedDatabase, storageFile,
+ mozIStorageService::CONNECTION_DEFAULT));
+ }
+
+ // We want extra durability for this important file.
+ QM_TRY(MOZ_TO_RESULT(
+ connection->ExecuteSimpleSQL("PRAGMA synchronous = EXTRA;"_ns)));
+
+ // Check to make sure that the storage version is correct.
+ QM_TRY(MOZ_TO_RESULT(MaybeCreateOrUpgradeStorage(*connection)));
+
+ QM_TRY(MaybeRemoveLocalStorageArchiveTmpFile());
+
+ QM_TRY_INSPECT(const auto& lsArchiveFile,
+ GetLocalStorageArchiveFile(*mStoragePath));
+
+ if (CachedNextGenLocalStorageEnabled()) {
+ QM_TRY(QM_OR_ELSE_WARN_IF(
+ // Expression.
+ MaybeCreateOrUpgradeLocalStorageArchive(*lsArchiveFile),
+ // Predicate.
+ IsDatabaseCorruptionError,
+ // Fallback.
+ ([&](const nsresult rv) -> Result<Ok, nsresult> {
+ QM_TRY_RETURN(CreateEmptyLocalStorageArchive(*lsArchiveFile));
+ })));
+ } else {
+ QM_TRY(
+ MOZ_TO_RESULT(MaybeRemoveLocalStorageDataAndArchive(*lsArchiveFile)));
+ }
+
+ QM_TRY_UNWRAP(mCacheUsable, MaybeCreateOrUpgradeCache(*connection));
+
+ if (mCacheUsable && gInvalidateQuotaCache) {
+ QM_TRY(InvalidateCache(*connection));
+
+ gInvalidateQuotaCache = false;
+ }
+
+ mStorageConnection = std::move(connection);
+
+ return NS_OK;
+ };
+
+ return ExecuteInitialization(
+ Initialization::Storage,
+ "dom::quota::FirstInitializationAttempt::Storage"_ns, innerFunc);
+}
+
+RefPtr<BoolPromise> QuotaManager::TemporaryStorageInitialized() {
+ AssertIsOnOwningThread();
+
+ auto temporaryStorageInitializedOp =
+ CreateTemporaryStorageInitializedOp(WrapMovingNotNullUnchecked(this));
+
+ RegisterNormalOriginOp(*temporaryStorageInitializedOp);
+
+ temporaryStorageInitializedOp->RunImmediately();
+
+ return temporaryStorageInitializedOp->OnResults();
+}
+
+RefPtr<UniversalDirectoryLockPromise> QuotaManager::OpenStorageDirectory(
+ const Nullable<PersistenceType>& aPersistenceType,
+ const OriginScope& aOriginScope, const Nullable<Client::Type>& aClientType,
+ bool aExclusive, DirectoryLockCategory aCategory,
+ Maybe<RefPtr<UniversalDirectoryLock>&> aPendingDirectoryLockOut) {
+ AssertIsOnOwningThread();
+
+ RefPtr<UniversalDirectoryLock> storageDirectoryLock;
+
+ RefPtr<BoolPromise> storageDirectoryLockPromise;
+
+ if (mStorageInitialized && !mShutdownStorageOpCount) {
+ storageDirectoryLockPromise = BoolPromise::CreateAndResolve(true, __func__);
+ } else {
+ storageDirectoryLock = CreateDirectoryLockInternal(
+ Nullable<PersistenceType>(), OriginScope::FromNull(),
+ Nullable<Client::Type>(),
+ /* aExclusive */ false);
+
+ storageDirectoryLockPromise = storageDirectoryLock->Acquire();
+ }
+
+ RefPtr<UniversalDirectoryLock> universalDirectoryLock =
+ CreateDirectoryLockInternal(aPersistenceType, aOriginScope, aClientType,
+ aExclusive, aCategory);
+
+ RefPtr<BoolPromise> universalDirectoryLockPromise =
+ universalDirectoryLock->Acquire();
+
+ if (aPendingDirectoryLockOut.isSome()) {
+ aPendingDirectoryLockOut.ref() = universalDirectoryLock;
+ }
+
+ return storageDirectoryLockPromise
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr(this),
+ storageDirectoryLock = std::move(storageDirectoryLock)](
+ const BoolPromise::ResolveOrRejectValue& aValue) mutable {
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(),
+ __func__);
+ }
+
+ if (!storageDirectoryLock) {
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }
+
+ return self->InitializeStorage(std::move(storageDirectoryLock));
+ })
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [universalDirectoryLockPromise =
+ std::move(universalDirectoryLockPromise)](
+ const BoolPromise::ResolveOrRejectValue& aValue) mutable {
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(),
+ __func__);
+ }
+
+ return std::move(universalDirectoryLockPromise);
+ })
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [universalDirectoryLock = std::move(universalDirectoryLock)](
+ const BoolPromise::ResolveOrRejectValue& aValue) mutable {
+ if (aValue.IsReject()) {
+ return UniversalDirectoryLockPromise::CreateAndReject(
+ aValue.RejectValue(), __func__);
+ }
+
+ return UniversalDirectoryLockPromise::CreateAndResolve(
+ std::move(universalDirectoryLock), __func__);
+ });
+}
+
+RefPtr<ClientDirectoryLockPromise> QuotaManager::OpenClientDirectory(
+ const ClientMetadata& aClientMetadata,
+ Maybe<RefPtr<ClientDirectoryLock>&> aPendingDirectoryLockOut) {
+ AssertIsOnOwningThread();
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ RefPtr<UniversalDirectoryLock> storageDirectoryLock;
+
+ if (!mStorageInitialized || mShutdownStorageOpCount) {
+ storageDirectoryLock = CreateDirectoryLockInternal(
+ Nullable<PersistenceType>(), OriginScope::FromNull(),
+ Nullable<Client::Type>(),
+ /* aExclusive */ false);
+ promises.AppendElement(storageDirectoryLock->Acquire());
+ }
+
+ RefPtr<ClientDirectoryLock> clientDirectoryLock =
+ CreateDirectoryLock(aClientMetadata, /* aExclusive */ false);
+
+ promises.AppendElement(clientDirectoryLock->Acquire());
+
+ if (aPendingDirectoryLockOut.isSome()) {
+ aPendingDirectoryLockOut.ref() = clientDirectoryLock;
+ }
+
+ return BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr(this),
+ storageDirectoryLock = std::move(storageDirectoryLock)](
+ const CopyableTArray<bool>& aResolveValues) mutable {
+ if (!storageDirectoryLock) {
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }
+
+ return self->InitializeStorage(std::move(storageDirectoryLock));
+ },
+ [](nsresult aRejectValue) {
+ return BoolPromise::CreateAndReject(aRejectValue, __func__);
+ })
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [clientDirectoryLock = std::move(clientDirectoryLock)](
+ const BoolPromise::ResolveOrRejectValue& aValue) mutable {
+ if (aValue.IsReject()) {
+ return ClientDirectoryLockPromise::CreateAndReject(
+ aValue.RejectValue(), __func__);
+ }
+ return ClientDirectoryLockPromise::CreateAndResolve(
+ std::move(clientDirectoryLock), __func__);
+ });
+}
+
+RefPtr<ClientDirectoryLock> QuotaManager::CreateDirectoryLock(
+ const ClientMetadata& aClientMetadata, bool aExclusive) {
+ AssertIsOnOwningThread();
+
+ return DirectoryLockImpl::Create(
+ WrapNotNullUnchecked(this), aClientMetadata.mPersistenceType,
+ aClientMetadata, aClientMetadata.mClientType, aExclusive);
+}
+
+RefPtr<UniversalDirectoryLock> QuotaManager::CreateDirectoryLockInternal(
+ const Nullable<PersistenceType>& aPersistenceType,
+ const OriginScope& aOriginScope, const Nullable<Client::Type>& aClientType,
+ bool aExclusive, DirectoryLockCategory aCategory) {
+ AssertIsOnOwningThread();
+
+ return DirectoryLockImpl::CreateInternal(WrapNotNullUnchecked(this),
+ aPersistenceType, aOriginScope,
+ aClientType, aExclusive, aCategory);
+}
+
+Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
+QuotaManager::EnsurePersistentOriginIsInitialized(
+ const OriginMetadata& aOriginMetadata) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
+ MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
+
+ const auto innerFunc = [&aOriginMetadata,
+ this](const auto& firstInitializationAttempt)
+ -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
+ QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aOriginMetadata));
+
+ if (mInitializedOrigins.Contains(aOriginMetadata.mOrigin)) {
+ MOZ_ASSERT(firstInitializationAttempt.Recorded());
+ return std::pair(std::move(directory), false);
+ }
+
+ QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory));
+
+ QM_TRY_INSPECT(
+ const int64_t& timestamp,
+ ([this, created, &directory,
+ &aOriginMetadata]() -> Result<int64_t, nsresult> {
+ if (created) {
+ const int64_t timestamp = PR_Now();
+
+ // Only creating .metadata-v2 to reduce IO.
+ QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(*directory, timestamp,
+ /* aPersisted */ true,
+ aOriginMetadata)));
+
+ return timestamp;
+ }
+
+ // Get the metadata. We only use the timestamp.
+ QM_TRY_INSPECT(const auto& metadata,
+ LoadFullOriginMetadataWithRestore(directory));
+
+ MOZ_ASSERT(metadata.mLastAccessTime <= PR_Now());
+
+ return metadata.mLastAccessTime;
+ }()));
+
+ QM_TRY(MOZ_TO_RESULT(InitializeOrigin(PERSISTENCE_TYPE_PERSISTENT,
+ aOriginMetadata, timestamp,
+ /* aPersisted */ true, directory)));
+
+ mInitializedOrigins.AppendElement(aOriginMetadata.mOrigin);
+
+ return std::pair(std::move(directory), created);
+ };
+
+ return ExecuteOriginInitialization(
+ aOriginMetadata.mOrigin, OriginInitialization::PersistentOrigin,
+ "dom::quota::FirstOriginInitializationAttempt::PersistentOrigin"_ns,
+ innerFunc);
+}
+
+bool QuotaManager::IsTemporaryOriginInitialized(
+ const OriginMetadata& aOriginMetadata) const {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+ MutexAutoLock lock(mQuotaMutex);
+
+ RefPtr<OriginInfo> originInfo =
+ LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata);
+
+ return static_cast<bool>(originInfo);
+}
+
+Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
+QuotaManager::EnsureTemporaryOriginIsInitialized(
+ PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+ MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType);
+ MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
+ MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal);
+
+ const auto innerFunc = [&aOriginMetadata, this](const auto&)
+ -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
+ // Get directory for this origin and persistence type.
+ QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aOriginMetadata));
+
+ QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory));
+
+ if (created) {
+ const int64_t timestamp =
+ NoteOriginDirectoryCreated(aOriginMetadata, /* aPersisted */ false);
+
+ // Only creating .metadata-v2 to reduce IO.
+ QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(*directory, timestamp,
+ /* aPersisted */ false,
+ aOriginMetadata)));
+ }
+
+ // TODO: If the metadata file exists and we didn't call
+ // LoadFullOriginMetadataWithRestore for it (because the quota info
+ // was loaded from the cache), then the group in the metadata file
+ // may be wrong, so it should be checked and eventually updated.
+ // It's not a big deal that we are not doing it here, because the
+ // origin will be marked as "accessed", so
+ // LoadFullOriginMetadataWithRestore will be called for the metadata
+ // file in next session in LoadQuotaFromCache.
+
+ return std::pair(std::move(directory), created);
+ };
+
+ return ExecuteOriginInitialization(
+ aOriginMetadata.mOrigin, OriginInitialization::TemporaryOrigin,
+ "dom::quota::FirstOriginInitializationAttempt::TemporaryOrigin"_ns,
+ innerFunc);
+}
+
+RefPtr<BoolPromise> QuotaManager::InitializePersistentClient(
+ const PrincipalInfo& aPrincipalInfo, Client::Type aClientType) {
+ AssertIsOnOwningThread();
+
+ auto initializePersistentClientOp = CreateInitializePersistentClientOp(
+ WrapMovingNotNullUnchecked(this), aPrincipalInfo, aClientType);
+
+ RegisterNormalOriginOp(*initializePersistentClientOp);
+
+ initializePersistentClientOp->RunImmediately();
+
+ return initializePersistentClientOp->OnResults();
+}
+
+Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
+QuotaManager::EnsurePersistentClientIsInitialized(
+ const ClientMetadata& aClientMetadata) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aClientMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
+ MOZ_ASSERT(Client::IsValidType(aClientMetadata.mClientType));
+ MOZ_DIAGNOSTIC_ASSERT(IsStorageInitializedInternal());
+ MOZ_DIAGNOSTIC_ASSERT(IsOriginInitialized(aClientMetadata.mOrigin));
+
+ QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aClientMetadata));
+
+ QM_TRY(MOZ_TO_RESULT(
+ directory->Append(Client::TypeToString(aClientMetadata.mClientType))));
+
+ QM_TRY_UNWRAP(bool created, EnsureDirectory(*directory));
+
+ return std::pair(std::move(directory), created);
+}
+
+RefPtr<BoolPromise> QuotaManager::InitializeTemporaryClient(
+ PersistenceType aPersistenceType, const PrincipalInfo& aPrincipalInfo,
+ Client::Type aClientType) {
+ AssertIsOnOwningThread();
+
+ auto initializeTemporaryClientOp = CreateInitializeTemporaryClientOp(
+ WrapMovingNotNullUnchecked(this), aPersistenceType, aPrincipalInfo,
+ aClientType);
+
+ RegisterNormalOriginOp(*initializeTemporaryClientOp);
+
+ initializeTemporaryClientOp->RunImmediately();
+
+ return initializeTemporaryClientOp->OnResults();
+}
+
+Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
+QuotaManager::EnsureTemporaryClientIsInitialized(
+ const ClientMetadata& aClientMetadata) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aClientMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+ MOZ_ASSERT(Client::IsValidType(aClientMetadata.mClientType));
+ MOZ_DIAGNOSTIC_ASSERT(IsStorageInitializedInternal());
+ MOZ_DIAGNOSTIC_ASSERT(IsTemporaryStorageInitializedInternal());
+ MOZ_DIAGNOSTIC_ASSERT(IsTemporaryOriginInitialized(aClientMetadata));
+
+ QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aClientMetadata));
+
+ QM_TRY(MOZ_TO_RESULT(
+ directory->Append(Client::TypeToString(aClientMetadata.mClientType))));
+
+ QM_TRY_UNWRAP(bool created, EnsureDirectory(*directory));
+
+ return std::pair(std::move(directory), created);
+}
+
+RefPtr<BoolPromise> QuotaManager::InitializeTemporaryStorage() {
+ AssertIsOnOwningThread();
+
+ // If temporary storage is initialized but there's a clear storage or
+ // shutdown storage operation already scheduled, we can't immediately resolve
+ // the promise and return from the function because the clear or shutdown
+ // storage operation uninitializes storage.
+ if (mTemporaryStorageInitialized && !mShutdownStorageOpCount) {
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }
+
+ RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
+ Nullable<PersistenceType>(), OriginScope::FromNull(),
+ Nullable<Client::Type>(),
+ /* aExclusive */ false);
+
+ return directoryLock->Acquire()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr(this),
+ directoryLock](const BoolPromise::ResolveOrRejectValue& aValue) mutable {
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
+ }
+
+ return self->InitializeTemporaryStorage(std::move(directoryLock));
+ });
+}
+
+RefPtr<BoolPromise> QuotaManager::InitializeTemporaryStorage(
+ RefPtr<UniversalDirectoryLock> aDirectoryLock) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aDirectoryLock);
+
+ if (mTemporaryStorageInitialized && !mShutdownStorageOpCount) {
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }
+
+ auto initializeTemporaryStorageOp = CreateInitTemporaryStorageOp(
+ WrapMovingNotNullUnchecked(this), std::move(aDirectoryLock));
+
+ RegisterNormalOriginOp(*initializeTemporaryStorageOp);
+
+ initializeTemporaryStorageOp->RunImmediately();
+
+ return initializeTemporaryStorageOp->OnResults()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
+ }
+
+ self->mTemporaryStorageInitialized = true;
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ });
+}
+
+nsresult QuotaManager::EnsureTemporaryStorageIsInitializedInternal() {
+ AssertIsOnIOThread();
+ MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
+
+ const auto innerFunc =
+ [&](const auto& firstInitializationAttempt) -> nsresult {
+ if (mTemporaryStorageInitializedInternal) {
+ MOZ_ASSERT(firstInitializationAttempt.Recorded());
+ return NS_OK;
+ }
+
+ QM_TRY_INSPECT(
+ const auto& storageDir,
+ MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIFile>,
+ MOZ_SELECT_OVERLOAD(do_CreateInstance),
+ NS_LOCAL_FILE_CONTRACTID));
+
+ QM_TRY(MOZ_TO_RESULT(storageDir->InitWithPath(GetStoragePath())));
+
+ // The storage directory must exist before calling GetTemporaryStorageLimit.
+ QM_TRY_INSPECT(const bool& created, EnsureDirectory(*storageDir));
+
+ Unused << created;
+
+ QM_TRY_UNWRAP(mTemporaryStorageLimit,
+ GetTemporaryStorageLimit(*storageDir));
+
+ QM_TRY(MOZ_TO_RESULT(LoadQuota()));
+
+ mTemporaryStorageInitializedInternal = true;
+
+ CleanupTemporaryStorage();
+
+ if (mCacheUsable) {
+ QM_TRY(InvalidateCache(*mStorageConnection));
+ }
+
+ return NS_OK;
+ };
+
+ return ExecuteInitialization(
+ Initialization::TemporaryStorage,
+ "dom::quota::FirstInitializationAttempt::TemporaryStorage"_ns, innerFunc);
+}
+
+RefPtr<BoolPromise> QuotaManager::ClearStoragesForOrigin(
+ const Maybe<PersistenceType>& aPersistenceType,
+ const PrincipalInfo& aPrincipalInfo,
+ const Maybe<Client::Type>& aClientType) {
+ AssertIsOnOwningThread();
+
+ auto clearOriginOp =
+ CreateClearOriginOp(WrapMovingNotNullUnchecked(this), aPersistenceType,
+ aPrincipalInfo, aClientType);
+
+ RegisterNormalOriginOp(*clearOriginOp);
+
+ clearOriginOp->RunImmediately();
+
+ return clearOriginOp->OnResults();
+}
+
+RefPtr<BoolPromise> QuotaManager::ClearStoragesForOriginPrefix(
+ const Maybe<PersistenceType>& aPersistenceType,
+ const PrincipalInfo& aPrincipalInfo) {
+ AssertIsOnOwningThread();
+
+ auto clearStoragesForOriginPrefixOp = CreateClearStoragesForOriginPrefixOp(
+ WrapMovingNotNullUnchecked(this), aPersistenceType, aPrincipalInfo);
+
+ RegisterNormalOriginOp(*clearStoragesForOriginPrefixOp);
+
+ clearStoragesForOriginPrefixOp->RunImmediately();
+
+ return clearStoragesForOriginPrefixOp->OnResults();
+}
+
+RefPtr<BoolPromise> QuotaManager::ClearStoragesForOriginAttributesPattern(
+ const OriginAttributesPattern& aPattern) {
+ AssertIsOnOwningThread();
+
+ auto clearDataOp =
+ CreateClearDataOp(WrapMovingNotNullUnchecked(this), aPattern);
+
+ RegisterNormalOriginOp(*clearDataOp);
+
+ clearDataOp->RunImmediately();
+
+ return clearDataOp->OnResults();
+}
+
+RefPtr<BoolPromise> QuotaManager::ClearPrivateRepository() {
+ AssertIsOnOwningThread();
+
+ auto clearPrivateRepositoryOp =
+ CreateClearPrivateRepositoryOp(WrapMovingNotNullUnchecked(this));
+
+ RegisterNormalOriginOp(*clearPrivateRepositoryOp);
+
+ clearPrivateRepositoryOp->RunImmediately();
+
+ return clearPrivateRepositoryOp->OnResults();
+}
+
+RefPtr<BoolPromise> QuotaManager::ClearStorage() {
+ AssertIsOnOwningThread();
+
+ auto clearStorageOp = CreateClearStorageOp(WrapMovingNotNullUnchecked(this));
+
+ RegisterNormalOriginOp(*clearStorageOp);
+
+ clearStorageOp->RunImmediately();
+
+ // Storage clearing also shuts it down, so we need to increses the counter
+ // here as well.
+ mShutdownStorageOpCount++;
+
+ return clearStorageOp->OnResults()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
+ self->mShutdownStorageOpCount--;
+
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
+ }
+
+ self->mTemporaryStorageInitialized = false;
+ self->mStorageInitialized = false;
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ });
+}
+
+RefPtr<BoolPromise> QuotaManager::ShutdownStorage() {
+ AssertIsOnOwningThread();
+
+ auto shutdownStorageOp =
+ CreateShutdownStorageOp(WrapMovingNotNullUnchecked(this));
+
+ RegisterNormalOriginOp(*shutdownStorageOp);
+
+ shutdownStorageOp->RunImmediately();
+
+ mShutdownStorageOpCount++;
+
+ return shutdownStorageOp->OnResults()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
+ self->mShutdownStorageOpCount--;
+
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
+ }
+
+ self->mTemporaryStorageInitialized = false;
+ self->mStorageInitialized = false;
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ });
+}
+
+void QuotaManager::ShutdownStorageInternal() {
+ AssertIsOnIOThread();
+
+ if (mStorageConnection) {
+ mInitializationInfo.ResetOriginInitializationInfos();
+ mInitializedOrigins.Clear();
+
+ if (mTemporaryStorageInitializedInternal) {
+ if (mCacheUsable) {
+ UnloadQuota();
+ } else {
+ RemoveQuota();
+ }
+
+ mTemporaryStorageInitializedInternal = false;
+ }
+
+ ReleaseIOThreadObjects();
+
+ mStorageConnection = nullptr;
+ mCacheUsable = false;
+ }
+
+ mInitializationInfo.ResetFirstInitializationAttempts();
+}
+
+Result<bool, nsresult> QuotaManager::EnsureOriginDirectory(
+ nsIFile& aDirectory) {
+ AssertIsOnIOThread();
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Exists));
+
+ if (!exists) {
+ QM_TRY_INSPECT(
+ const auto& leafName,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, aDirectory, GetLeafName)
+ .map([](const auto& leafName) {
+ return NS_ConvertUTF16toUTF8(leafName);
+ }));
+
+ QM_TRY(OkIf(IsSanitizedOriginValid(leafName)), Err(NS_ERROR_FAILURE),
+ [](const auto&) {
+ QM_WARNING(
+ "Preventing creation of a new origin directory which is not "
+ "supported by our origin parser or is obsolete!");
+ });
+ }
+
+ QM_TRY_RETURN(EnsureDirectory(aDirectory));
+}
+
+nsresult QuotaManager::AboutToClearOrigins(
+ const Nullable<PersistenceType>& aPersistenceType,
+ const OriginScope& aOriginScope,
+ const Nullable<Client::Type>& aClientType) {
+ AssertIsOnIOThread();
+
+ if (aClientType.IsNull()) {
+ for (Client::Type type : AllClientTypes()) {
+ QM_TRY(MOZ_TO_RESULT((*mClients)[type]->AboutToClearOrigins(
+ aPersistenceType, aOriginScope)));
+ }
+ } else {
+ QM_TRY(MOZ_TO_RESULT((*mClients)[aClientType.Value()]->AboutToClearOrigins(
+ aPersistenceType, aOriginScope)));
+ }
+
+ return NS_OK;
+}
+
+void QuotaManager::OriginClearCompleted(
+ PersistenceType aPersistenceType, const nsACString& aOrigin,
+ const Nullable<Client::Type>& aClientType) {
+ AssertIsOnIOThread();
+
+ if (aClientType.IsNull()) {
+ if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
+ mInitializedOrigins.RemoveElement(aOrigin);
+ }
+
+ for (Client::Type type : AllClientTypes()) {
+ (*mClients)[type]->OnOriginClearCompleted(aPersistenceType, aOrigin);
+ }
+ } else {
+ (*mClients)[aClientType.Value()]->OnOriginClearCompleted(aPersistenceType,
+ aOrigin);
+ }
+}
+
+void QuotaManager::RepositoryClearCompleted(PersistenceType aPersistenceType) {
+ AssertIsOnIOThread();
+
+ if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
+ mInitializedOrigins.Clear();
+ }
+
+ for (Client::Type type : AllClientTypes()) {
+ (*mClients)[type]->OnRepositoryClearCompleted(aPersistenceType);
+ }
+}
+
+Client* QuotaManager::GetClient(Client::Type aClientType) {
+ MOZ_ASSERT(aClientType >= Client::IDB);
+ MOZ_ASSERT(aClientType < Client::TypeMax());
+
+ return (*mClients)[aClientType];
+}
+
+const AutoTArray<Client::Type, Client::TYPE_MAX>&
+QuotaManager::AllClientTypes() {
+ if (CachedNextGenLocalStorageEnabled()) {
+ return *mAllClientTypes;
+ }
+ return *mAllClientTypesExceptLS;
+}
+
+uint64_t QuotaManager::GetGroupLimit() const {
+ // To avoid one group evicting all the rest, limit the amount any one group
+ // can use to 20% resp. a fifth. To prevent individual sites from using
+ // exorbitant amounts of storage where there is a lot of free space, cap the
+ // group limit to 10GB.
+ const auto x = std::min<uint64_t>(mTemporaryStorageLimit / 5, 10 GB);
+
+ // In low-storage situations, make an exception (while not exceeding the total
+ // storage limit).
+ return std::min<uint64_t>(mTemporaryStorageLimit,
+ std::max<uint64_t>(x, 10 MB));
+}
+
+std::pair<uint64_t, uint64_t> QuotaManager::GetUsageAndLimitForEstimate(
+ const OriginMetadata& aOriginMetadata) {
+ AssertIsOnIOThread();
+
+ uint64_t totalGroupUsage = 0;
+
+ {
+ MutexAutoLock lock(mQuotaMutex);
+
+ GroupInfoPair* pair;
+ if (mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
+ for (const PersistenceType type : kBestEffortPersistenceTypes) {
+ RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
+ if (groupInfo) {
+ if (type == PERSISTENCE_TYPE_DEFAULT) {
+ RefPtr<OriginInfo> originInfo =
+ groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
+
+ if (originInfo && originInfo->LockedPersisted()) {
+ return std::pair(mTemporaryStorageUsage, mTemporaryStorageLimit);
+ }
+ }
+
+ AssertNoOverflow(totalGroupUsage, groupInfo->mUsage);
+ totalGroupUsage += groupInfo->mUsage;
+ }
+ }
+ }
+ }
+
+ return std::pair(totalGroupUsage, GetGroupLimit());
+}
+
+uint64_t QuotaManager::GetOriginUsage(
+ const PrincipalMetadata& aPrincipalMetadata) {
+ AssertIsOnIOThread();
+
+ uint64_t usage = 0;
+
+ {
+ MutexAutoLock lock(mQuotaMutex);
+
+ GroupInfoPair* pair;
+ if (mGroupInfoPairs.Get(aPrincipalMetadata.mGroup, &pair)) {
+ for (const PersistenceType type : kBestEffortPersistenceTypes) {
+ RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
+ if (groupInfo) {
+ RefPtr<OriginInfo> originInfo =
+ groupInfo->LockedGetOriginInfo(aPrincipalMetadata.mOrigin);
+ if (originInfo) {
+ AssertNoOverflow(usage, originInfo->LockedUsage());
+ usage += originInfo->LockedUsage();
+ }
+ }
+ }
+ }
+ }
+
+ return usage;
+}
+
+Maybe<FullOriginMetadata> QuotaManager::GetFullOriginMetadata(
+ const OriginMetadata& aOriginMetadata) {
+ AssertIsOnIOThread();
+ MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
+ MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal);
+
+ MutexAutoLock lock(mQuotaMutex);
+
+ RefPtr<OriginInfo> originInfo =
+ LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata);
+ if (originInfo) {
+ return Some(originInfo->LockedFlattenToFullOriginMetadata());
+ }
+
+ return Nothing();
+}
+
+void QuotaManager::NotifyStoragePressure(uint64_t aUsage) {
+ mQuotaMutex.AssertNotCurrentThreadOwns();
+
+ RefPtr<StoragePressureRunnable> storagePressureRunnable =
+ new StoragePressureRunnable(aUsage);
+
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(storagePressureRunnable));
+}
+
+// static
+void QuotaManager::GetStorageId(PersistenceType aPersistenceType,
+ const nsACString& aOrigin,
+ Client::Type aClientType,
+ nsACString& aDatabaseId) {
+ nsAutoCString str;
+ str.AppendInt(aPersistenceType);
+ str.Append('*');
+ str.Append(aOrigin);
+ str.Append('*');
+ str.AppendInt(aClientType);
+
+ aDatabaseId = str;
+}
+
+// static
+bool QuotaManager::IsPrincipalInfoValid(const PrincipalInfo& aPrincipalInfo) {
+ switch (aPrincipalInfo.type()) {
+ // A system principal is acceptable.
+ case PrincipalInfo::TSystemPrincipalInfo: {
+ return true;
+ }
+
+ // Validate content principals to ensure that the spec, originNoSuffix and
+ // baseDomain are sane.
+ case PrincipalInfo::TContentPrincipalInfo: {
+ const ContentPrincipalInfo& info =
+ aPrincipalInfo.get_ContentPrincipalInfo();
+
+ // Verify the principal spec parses.
+ nsCOMPtr<nsIURI> uri;
+ QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), info.spec())), false);
+
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(uri, info.attrs());
+ QM_TRY(MOZ_TO_RESULT(principal), false);
+
+ // Verify the principal originNoSuffix matches spec.
+ QM_TRY_INSPECT(const auto& originNoSuffix,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, principal,
+ GetOriginNoSuffix),
+ false);
+
+ if (NS_WARN_IF(originNoSuffix != info.originNoSuffix())) {
+ QM_WARNING("originNoSuffix (%s) doesn't match passed one (%s)!",
+ originNoSuffix.get(), info.originNoSuffix().get());
+ return false;
+ }
+
+ if (NS_WARN_IF(info.originNoSuffix().EqualsLiteral(kChromeOrigin))) {
+ return false;
+ }
+
+ if (NS_WARN_IF(info.originNoSuffix().FindChar('^', 0) != -1)) {
+ QM_WARNING("originNoSuffix (%s) contains the '^' character!",
+ info.originNoSuffix().get());
+ return false;
+ }
+
+ // Verify the principal baseDomain exists.
+ if (NS_WARN_IF(info.baseDomain().IsVoid())) {
+ return false;
+ }
+
+ // Verify the principal baseDomain matches spec.
+ QM_TRY_INSPECT(const auto& baseDomain,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, principal,
+ GetBaseDomain),
+ false);
+
+ if (NS_WARN_IF(baseDomain != info.baseDomain())) {
+ QM_WARNING("baseDomain (%s) doesn't match passed one (%s)!",
+ baseDomain.get(), info.baseDomain().get());
+ return false;
+ }
+
+ return true;
+ }
+
+ default: {
+ break;
+ }
+ }
+
+ // Null and expanded principals are not acceptable.
+ return false;
+}
+
+Result<PrincipalMetadata, nsresult>
+QuotaManager::GetInfoFromValidatedPrincipalInfo(
+ const PrincipalInfo& aPrincipalInfo) {
+ MOZ_ASSERT(IsPrincipalInfoValid(aPrincipalInfo));
+
+ switch (aPrincipalInfo.type()) {
+ case PrincipalInfo::TSystemPrincipalInfo: {
+ return GetInfoForChrome();
+ }
+
+ case PrincipalInfo::TContentPrincipalInfo: {
+ const ContentPrincipalInfo& info =
+ aPrincipalInfo.get_ContentPrincipalInfo();
+
+ nsCString suffix;
+ info.attrs().CreateSuffix(suffix);
+
+ nsCString origin = info.originNoSuffix() + suffix;
+
+ if (IsUUIDOrigin(origin)) {
+ QM_TRY_INSPECT(const auto& originalOrigin,
+ GetOriginFromStorageOrigin(origin));
+
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(originalOrigin);
+ QM_TRY(MOZ_TO_RESULT(principal));
+
+ PrincipalInfo principalInfo;
+ QM_TRY(
+ MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
+
+ return GetInfoFromValidatedPrincipalInfo(principalInfo);
+ }
+
+ PrincipalMetadata principalMetadata;
+
+ principalMetadata.mSuffix = suffix;
+
+ principalMetadata.mGroup = info.baseDomain() + suffix;
+
+ principalMetadata.mOrigin = origin;
+
+ if (info.attrs().mPrivateBrowsingId != 0) {
+ QM_TRY_UNWRAP(principalMetadata.mStorageOrigin,
+ EnsureStorageOriginFromOrigin(origin));
+ } else {
+ principalMetadata.mStorageOrigin = origin;
+ }
+
+ principalMetadata.mIsPrivate = info.attrs().mPrivateBrowsingId != 0;
+
+ return principalMetadata;
+ }
+
+ default: {
+ MOZ_ASSERT_UNREACHABLE("Should never get here!");
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+ }
+}
+
+// static
+nsAutoCString QuotaManager::GetOriginFromValidatedPrincipalInfo(
+ const PrincipalInfo& aPrincipalInfo) {
+ MOZ_ASSERT(IsPrincipalInfoValid(aPrincipalInfo));
+
+ switch (aPrincipalInfo.type()) {
+ case PrincipalInfo::TSystemPrincipalInfo: {
+ return nsAutoCString{GetOriginForChrome()};
+ }
+
+ case PrincipalInfo::TContentPrincipalInfo: {
+ const ContentPrincipalInfo& info =
+ aPrincipalInfo.get_ContentPrincipalInfo();
+
+ nsAutoCString suffix;
+
+ info.attrs().CreateSuffix(suffix);
+
+ return info.originNoSuffix() + suffix;
+ }
+
+ default: {
+ MOZ_CRASH("Should never get here!");
+ }
+ }
+}
+
+// static
+Result<PrincipalMetadata, nsresult> QuotaManager::GetInfoFromPrincipal(
+ nsIPrincipal* aPrincipal) {
+ MOZ_ASSERT(aPrincipal);
+
+ if (aPrincipal->IsSystemPrincipal()) {
+ return GetInfoForChrome();
+ }
+
+ if (aPrincipal->GetIsNullPrincipal()) {
+ NS_WARNING("IndexedDB not supported from this principal!");
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ PrincipalMetadata principalMetadata;
+
+ QM_TRY(MOZ_TO_RESULT(aPrincipal->GetOrigin(principalMetadata.mOrigin)));
+
+ if (principalMetadata.mOrigin.EqualsLiteral(kChromeOrigin)) {
+ NS_WARNING("Non-chrome principal can't use chrome origin!");
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ aPrincipal->OriginAttributesRef().CreateSuffix(principalMetadata.mSuffix);
+
+ nsAutoCString baseDomain;
+ QM_TRY(MOZ_TO_RESULT(aPrincipal->GetBaseDomain(baseDomain)));
+
+ MOZ_ASSERT(!baseDomain.IsEmpty());
+
+ principalMetadata.mGroup = baseDomain + principalMetadata.mSuffix;
+
+ principalMetadata.mStorageOrigin = principalMetadata.mOrigin;
+
+ principalMetadata.mIsPrivate = aPrincipal->GetPrivateBrowsingId() != 0;
+
+ return principalMetadata;
+}
+
+Result<PrincipalMetadata, nsresult> QuotaManager::GetInfoFromWindow(
+ nsPIDOMWindowOuter* aWindow) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aWindow);
+
+ nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
+ QM_TRY(OkIf(sop), Err(NS_ERROR_FAILURE));
+
+ nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
+ QM_TRY(OkIf(principal), Err(NS_ERROR_FAILURE));
+
+ return GetInfoFromPrincipal(principal);
+}
+
+// static
+Result<nsAutoCString, nsresult> QuotaManager::GetOriginFromPrincipal(
+ nsIPrincipal* aPrincipal) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+
+ if (aPrincipal->IsSystemPrincipal()) {
+ return nsAutoCString{GetOriginForChrome()};
+ }
+
+ if (aPrincipal->GetIsNullPrincipal()) {
+ NS_WARNING("IndexedDB not supported from this principal!");
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ QM_TRY_UNWRAP(const auto origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsAutoCString, aPrincipal, GetOrigin));
+
+ if (origin.EqualsLiteral(kChromeOrigin)) {
+ NS_WARNING("Non-chrome principal can't use chrome origin!");
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ return origin;
+}
+
+// static
+Result<nsAutoCString, nsresult> QuotaManager::GetOriginFromWindow(
+ nsPIDOMWindowOuter* aWindow) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aWindow);
+
+ nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
+ QM_TRY(OkIf(sop), Err(NS_ERROR_FAILURE));
+
+ nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
+ QM_TRY(OkIf(principal), Err(NS_ERROR_FAILURE));
+
+ QM_TRY_RETURN(GetOriginFromPrincipal(principal));
+}
+
+// static
+PrincipalMetadata QuotaManager::GetInfoForChrome() {
+ return {{},
+ GetOriginForChrome(),
+ GetOriginForChrome(),
+ GetOriginForChrome(),
+ false};
+}
+
+// static
+nsLiteralCString QuotaManager::GetOriginForChrome() {
+ return nsLiteralCString{kChromeOrigin};
+}
+
+// static
+bool QuotaManager::IsOriginInternal(const nsACString& aOrigin) {
+ MOZ_ASSERT(!aOrigin.IsEmpty());
+
+ // The first prompt is not required for these origins.
+ if (aOrigin.EqualsLiteral(kChromeOrigin) ||
+ StringBeginsWith(aOrigin, nsDependentCString(kAboutHomeOriginPrefix)) ||
+ StringBeginsWith(aOrigin, nsDependentCString(kIndexedDBOriginPrefix)) ||
+ StringBeginsWith(aOrigin, nsDependentCString(kResourceOriginPrefix))) {
+ return true;
+ }
+
+ return false;
+}
+
+// static
+bool QuotaManager::AreOriginsEqualOnDisk(const nsACString& aOrigin1,
+ const nsACString& aOrigin2) {
+ return MakeSanitizedOriginCString(aOrigin1) ==
+ MakeSanitizedOriginCString(aOrigin2);
+}
+
+// static
+Result<PrincipalInfo, nsresult> QuotaManager::ParseOrigin(
+ const nsACString& aOrigin) {
+ // An origin string either corresponds to a SystemPrincipalInfo or a
+ // ContentPrincipalInfo, see
+ // QuotaManager::GetOriginFromValidatedPrincipalInfo.
+
+ nsCString spec;
+ OriginAttributes attrs;
+ nsCString originalSuffix;
+ const OriginParser::ResultType result = OriginParser::ParseOrigin(
+ MakeSanitizedOriginCString(aOrigin), spec, &attrs, originalSuffix);
+ QM_TRY(MOZ_TO_RESULT(result == OriginParser::ValidOrigin));
+
+ QM_TRY_INSPECT(
+ const auto& principal,
+ ([&spec, &attrs]() -> Result<nsCOMPtr<nsIPrincipal>, nsresult> {
+ if (spec.EqualsLiteral(kChromeOrigin)) {
+ return nsCOMPtr<nsIPrincipal>(SystemPrincipal::Get());
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), spec)));
+
+ return nsCOMPtr<nsIPrincipal>(
+ BasePrincipal::CreateContentPrincipal(uri, attrs));
+ }()));
+ QM_TRY(MOZ_TO_RESULT(principal));
+
+ PrincipalInfo principalInfo;
+ QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
+
+ return std::move(principalInfo);
+}
+
+// static
+void QuotaManager::InvalidateQuotaCache() { gInvalidateQuotaCache = true; }
+
+uint64_t QuotaManager::LockedCollectOriginsForEviction(
+ uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
+ mQuotaMutex.AssertCurrentThreadOwns();
+
+ RefPtr<CollectOriginsHelper> helper =
+ new CollectOriginsHelper(mQuotaMutex, aMinSizeToBeFreed);
+
+ // Unlock while calling out to XPCOM (code behind the dispatch method needs
+ // to acquire its own lock which can potentially lead to a deadlock and it
+ // also calls an observer that can do various stuff like IO, so it's better
+ // to not hold our mutex while that happens).
+ {
+ MutexAutoUnlock autoUnlock(mQuotaMutex);
+
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(helper, NS_DISPATCH_NORMAL));
+ }
+
+ return helper->BlockAndReturnOriginsForEviction(aLocks);
+}
+
+void QuotaManager::LockedRemoveQuotaForRepository(
+ PersistenceType aPersistenceType) {
+ mQuotaMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+ for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
+ auto& pair = iter.Data();
+
+ if (RefPtr<GroupInfo> groupInfo =
+ pair->LockedGetGroupInfo(aPersistenceType)) {
+ groupInfo->LockedRemoveOriginInfos();
+
+ pair->LockedClearGroupInfo(aPersistenceType);
+
+ if (!pair->LockedHasGroupInfos()) {
+ iter.Remove();
+ }
+ }
+ }
+}
+
+void QuotaManager::LockedRemoveQuotaForOrigin(
+ const OriginMetadata& aOriginMetadata) {
+ mQuotaMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+ GroupInfoPair* pair;
+ if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
+ return;
+ }
+
+ MOZ_ASSERT(pair);
+
+ if (RefPtr<GroupInfo> groupInfo =
+ pair->LockedGetGroupInfo(aOriginMetadata.mPersistenceType)) {
+ groupInfo->LockedRemoveOriginInfo(aOriginMetadata.mOrigin);
+
+ if (!groupInfo->LockedHasOriginInfos()) {
+ pair->LockedClearGroupInfo(aOriginMetadata.mPersistenceType);
+
+ if (!pair->LockedHasGroupInfos()) {
+ mGroupInfoPairs.Remove(aOriginMetadata.mGroup);
+ }
+ }
+ }
+}
+
+already_AddRefed<GroupInfo> QuotaManager::LockedGetOrCreateGroupInfo(
+ PersistenceType aPersistenceType, const nsACString& aSuffix,
+ const nsACString& aGroup) {
+ mQuotaMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+ GroupInfoPair* const pair =
+ mGroupInfoPairs.GetOrInsertNew(aGroup, aSuffix, aGroup);
+
+ RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
+ if (!groupInfo) {
+ groupInfo = new GroupInfo(pair, aPersistenceType);
+ pair->LockedSetGroupInfo(aPersistenceType, groupInfo);
+ }
+
+ return groupInfo.forget();
+}
+
+already_AddRefed<OriginInfo> QuotaManager::LockedGetOriginInfo(
+ PersistenceType aPersistenceType,
+ const OriginMetadata& aOriginMetadata) const {
+ mQuotaMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+ GroupInfoPair* pair;
+ if (mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
+ RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
+ if (groupInfo) {
+ return groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
+ }
+ }
+
+ return nullptr;
+}
+
+template <typename Iterator>
+void QuotaManager::MaybeInsertNonPersistedOriginInfos(
+ Iterator aDest, const RefPtr<GroupInfo>& aTemporaryGroupInfo,
+ const RefPtr<GroupInfo>& aDefaultGroupInfo,
+ const RefPtr<GroupInfo>& aPrivateGroupInfo) {
+ const auto copy = [&aDest](const GroupInfo& groupInfo) {
+ std::copy_if(
+ groupInfo.mOriginInfos.cbegin(), groupInfo.mOriginInfos.cend(), aDest,
+ [](const auto& originInfo) { return !originInfo->LockedPersisted(); });
+ };
+
+ if (aTemporaryGroupInfo) {
+ MOZ_ASSERT(PERSISTENCE_TYPE_TEMPORARY ==
+ aTemporaryGroupInfo->GetPersistenceType());
+
+ copy(*aTemporaryGroupInfo);
+ }
+ if (aDefaultGroupInfo) {
+ MOZ_ASSERT(PERSISTENCE_TYPE_DEFAULT ==
+ aDefaultGroupInfo->GetPersistenceType());
+
+ copy(*aDefaultGroupInfo);
+ }
+ if (aPrivateGroupInfo) {
+ MOZ_ASSERT(PERSISTENCE_TYPE_PRIVATE ==
+ aPrivateGroupInfo->GetPersistenceType());
+ copy(*aPrivateGroupInfo);
+ }
+}
+
+template <typename Collect, typename Pred>
+QuotaManager::OriginInfosFlatTraversable
+QuotaManager::CollectLRUOriginInfosUntil(Collect&& aCollect, Pred&& aPred) {
+ OriginInfosFlatTraversable originInfos;
+
+ std::forward<Collect>(aCollect)(MakeBackInserter(originInfos));
+
+ originInfos.Sort(OriginInfoAccessTimeComparator());
+
+ const auto foundIt = std::find_if(originInfos.cbegin(), originInfos.cend(),
+ std::forward<Pred>(aPred));
+
+ originInfos.TruncateLength(foundIt - originInfos.cbegin());
+
+ return originInfos;
+}
+
+QuotaManager::OriginInfosNestedTraversable
+QuotaManager::GetOriginInfosExceedingGroupLimit() const {
+ MutexAutoLock lock(mQuotaMutex);
+
+ OriginInfosNestedTraversable originInfos;
+
+ for (const auto& entry : mGroupInfoPairs) {
+ const auto& pair = entry.GetData();
+
+ MOZ_ASSERT(!entry.GetKey().IsEmpty());
+ MOZ_ASSERT(pair);
+
+ uint64_t groupUsage = 0;
+
+ const RefPtr<GroupInfo> temporaryGroupInfo =
+ pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
+ if (temporaryGroupInfo) {
+ groupUsage += temporaryGroupInfo->mUsage;
+ }
+
+ const RefPtr<GroupInfo> defaultGroupInfo =
+ pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
+ if (defaultGroupInfo) {
+ groupUsage += defaultGroupInfo->mUsage;
+ }
+
+ const RefPtr<GroupInfo> privateGroupInfo =
+ pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE);
+ if (privateGroupInfo) {
+ groupUsage += privateGroupInfo->mUsage;
+ }
+
+ if (groupUsage > 0) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager, "Shouldn't be null!");
+
+ if (groupUsage > quotaManager->GetGroupLimit()) {
+ originInfos.AppendElement(CollectLRUOriginInfosUntil(
+ [&temporaryGroupInfo, &defaultGroupInfo,
+ &privateGroupInfo](auto inserter) {
+ MaybeInsertNonPersistedOriginInfos(
+ std::move(inserter), temporaryGroupInfo, defaultGroupInfo,
+ privateGroupInfo);
+ },
+ [&groupUsage, quotaManager](const auto& originInfo) {
+ groupUsage -= originInfo->LockedUsage();
+
+ return groupUsage <= quotaManager->GetGroupLimit();
+ }));
+ }
+ }
+ }
+
+ return originInfos;
+}
+
+QuotaManager::OriginInfosNestedTraversable
+QuotaManager::GetOriginInfosExceedingGlobalLimit() const {
+ MutexAutoLock lock(mQuotaMutex);
+
+ QuotaManager::OriginInfosNestedTraversable res;
+ res.AppendElement(CollectLRUOriginInfosUntil(
+ // XXX The lambda only needs to capture this, but due to Bug 1421435 it
+ // can't.
+ [&](auto inserter) {
+ for (const auto& entry : mGroupInfoPairs) {
+ const auto& pair = entry.GetData();
+
+ MOZ_ASSERT(!entry.GetKey().IsEmpty());
+ MOZ_ASSERT(pair);
+
+ MaybeInsertNonPersistedOriginInfos(
+ inserter, pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY),
+ pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT),
+ pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE));
+ }
+ },
+ [temporaryStorageUsage = mTemporaryStorageUsage,
+ temporaryStorageLimit = mTemporaryStorageLimit,
+ doomedUsage = uint64_t{0}](const auto& originInfo) mutable {
+ if (temporaryStorageUsage - doomedUsage <= temporaryStorageLimit) {
+ return true;
+ }
+
+ doomedUsage += originInfo->LockedUsage();
+ return false;
+ }));
+
+ return res;
+}
+
+void QuotaManager::ClearOrigins(
+ const OriginInfosNestedTraversable& aDoomedOriginInfos) {
+ AssertIsOnIOThread();
+
+ // If we are in shutdown, we could break off early from clearing origins.
+ // In such cases, we would like to track the ones that were already cleared
+ // up, such that other essential cleanup could be performed on clearedOrigins.
+ // clearedOrigins is used in calls to LockedRemoveQuotaForOrigin and
+ // OriginClearCompleted below. We could have used a collection of OriginInfos
+ // rather than flattening them to OriginMetadata but groupInfo in OriginInfo
+ // is just a raw ptr and LockedRemoveQuotaForOrigin might delete groupInfo and
+ // as a result, we would not be able to get origin persistence type required
+ // in OriginClearCompleted call after lockedRemoveQuotaForOrigin call.
+ nsTArray<OriginMetadata> clearedOrigins;
+
+ // XXX Does this need to be done a) in order and/or b) sequentially?
+ for (const auto& doomedOriginInfo :
+ Flatten<OriginInfosFlatTraversable::value_type>(aDoomedOriginInfos)) {
+#ifdef DEBUG
+ {
+ MutexAutoLock lock(mQuotaMutex);
+ MOZ_ASSERT(!doomedOriginInfo->LockedPersisted());
+ }
+#endif
+
+ // TODO: We are currently only checking for this flag here which
+ // means that we cannot break off once we start cleaning an origin. It
+ // could be better if we could check for shutdown flag while cleaning an
+ // origin such that we could break off early from the cleaning process if
+ // we are stuck cleaning on one huge origin. Bug1797098 has been filed to
+ // track this.
+ if (QuotaManager::IsShuttingDown()) {
+ break;
+ }
+
+ auto originMetadata = doomedOriginInfo->FlattenToOriginMetadata();
+
+ DeleteOriginDirectory(originMetadata);
+
+ clearedOrigins.AppendElement(std::move(originMetadata));
+ }
+
+ {
+ MutexAutoLock lock(mQuotaMutex);
+
+ for (const auto& clearedOrigin : clearedOrigins) {
+ LockedRemoveQuotaForOrigin(clearedOrigin);
+ }
+ }
+
+ for (const auto& clearedOrigin : clearedOrigins) {
+ OriginClearCompleted(clearedOrigin.mPersistenceType, clearedOrigin.mOrigin,
+ Nullable<Client::Type>());
+ }
+}
+
+void QuotaManager::CleanupTemporaryStorage() {
+ AssertIsOnIOThread();
+
+ // Evicting origins that exceed their group limit also affects the global
+ // temporary storage usage, so these steps have to be taken sequentially.
+ // Combining them doesn't seem worth the added complexity.
+ ClearOrigins(GetOriginInfosExceedingGroupLimit());
+ ClearOrigins(GetOriginInfosExceedingGlobalLimit());
+
+ if (mTemporaryStorageUsage > mTemporaryStorageLimit) {
+ // If disk space is still low after origin clear, notify storage pressure.
+ NotifyStoragePressure(mTemporaryStorageUsage);
+ }
+}
+
+void QuotaManager::DeleteOriginDirectory(
+ const OriginMetadata& aOriginMetadata) {
+ QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aOriginMetadata),
+ QM_VOID);
+
+ nsresult rv = directory->Remove(true);
+ if (rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
+ // This should never fail if we've closed all storage connections
+ // correctly...
+ NS_ERROR("Failed to remove directory!");
+ }
+}
+
+void QuotaManager::FinalizeOriginEviction(
+ nsTArray<RefPtr<OriginDirectoryLock>>&& aLocks) {
+ NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+ auto finalizeOriginEviction = [locks = std::move(aLocks)]() mutable {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ RefPtr<OriginOperationBase> op = CreateFinalizeOriginEvictionOp(
+ WrapMovingNotNull(quotaManager), std::move(locks));
+
+ op->RunImmediately();
+ };
+
+ if (IsOnBackgroundThread()) {
+ finalizeOriginEviction();
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(
+ NS_NewRunnableFunction(
+ "dom::quota::QuotaManager::FinalizeOriginEviction",
+ std::move(finalizeOriginEviction)),
+ NS_DISPATCH_NORMAL));
+ }
+}
+
+Result<Ok, nsresult> QuotaManager::ArchiveOrigins(
+ const nsTArray<FullOriginMetadata>& aFullOriginMetadatas) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(!aFullOriginMetadatas.IsEmpty());
+
+ QM_TRY_INSPECT(const auto& storageArchivesDir,
+ QM_NewLocalFile(*mStorageArchivesPath));
+
+ // Create another subdir, so once we decide to remove all temporary archives,
+ // we can remove only the subdir and the parent directory can still be used
+ // for something else or similar in future. Otherwise, we would have to
+ // figure out a new name for it.
+ QM_TRY(MOZ_TO_RESULT(storageArchivesDir->Append(u"0"_ns)));
+
+ PRExplodedTime now;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
+
+ const auto dateStr =
+ nsPrintfCString("%04hd-%02" PRId32 "-%02" PRId32, now.tm_year,
+ now.tm_month + 1, now.tm_mday);
+
+ QM_TRY_INSPECT(
+ const auto& storageArchiveDir,
+ CloneFileAndAppend(*storageArchivesDir, NS_ConvertASCIItoUTF16(dateStr)));
+
+ QM_TRY(MOZ_TO_RESULT(
+ storageArchiveDir->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700)));
+
+ QM_TRY_INSPECT(const auto& defaultStorageArchiveDir,
+ CloneFileAndAppend(*storageArchiveDir,
+ nsLiteralString(DEFAULT_DIRECTORY_NAME)));
+
+ QM_TRY_INSPECT(const auto& temporaryStorageArchiveDir,
+ CloneFileAndAppend(*storageArchiveDir,
+ nsLiteralString(TEMPORARY_DIRECTORY_NAME)));
+
+ for (const auto& fullOriginMetadata : aFullOriginMetadatas) {
+ MOZ_ASSERT(
+ IsBestEffortPersistenceType(fullOriginMetadata.mPersistenceType));
+
+ QM_TRY_INSPECT(const auto& directory,
+ GetOriginDirectory(fullOriginMetadata));
+
+ // The origin could have been removed, for example due to corruption.
+ QM_TRY_INSPECT(
+ const auto& moved,
+ QM_OR_ELSE_WARN_IF(
+ // Expression.
+ MOZ_TO_RESULT(
+ directory->MoveTo(fullOriginMetadata.mPersistenceType ==
+ PERSISTENCE_TYPE_DEFAULT
+ ? defaultStorageArchiveDir
+ : temporaryStorageArchiveDir,
+ u""_ns))
+ .map([](Ok) { return true; }),
+ // Predicate.
+ ([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }),
+ // Fallback.
+ ErrToOk<false>));
+
+ if (moved) {
+ RemoveQuotaForOrigin(fullOriginMetadata.mPersistenceType,
+ fullOriginMetadata);
+ }
+ }
+
+ return Ok{};
+}
+
+auto QuotaManager::GetDirectoryLockTable(PersistenceType aPersistenceType)
+ -> DirectoryLockTable& {
+ switch (aPersistenceType) {
+ case PERSISTENCE_TYPE_TEMPORARY:
+ return mTemporaryDirectoryLockTable;
+ case PERSISTENCE_TYPE_DEFAULT:
+ return mDefaultDirectoryLockTable;
+ case PERSISTENCE_TYPE_PRIVATE:
+ return mPrivateDirectoryLockTable;
+
+ case PERSISTENCE_TYPE_PERSISTENT:
+ case PERSISTENCE_TYPE_INVALID:
+ default:
+ MOZ_CRASH("Bad persistence type value!");
+ }
+}
+
+void QuotaManager::ClearDirectoryLockTables() {
+ AssertIsOnOwningThread();
+
+ for (const PersistenceType type : kBestEffortPersistenceTypes) {
+ DirectoryLockTable& directoryLockTable = GetDirectoryLockTable(type);
+
+ if (!IsShuttingDown()) {
+ for (const auto& entry : directoryLockTable) {
+ const auto& array = entry.GetData();
+
+ // It doesn't matter which lock is used, they all have the same
+ // persistence type and origin metadata.
+ MOZ_ASSERT(!array->IsEmpty());
+ const auto& lock = array->ElementAt(0);
+
+ UpdateOriginAccessTime(lock->GetPersistenceType(),
+ lock->OriginMetadata());
+ }
+ }
+
+ directoryLockTable.Clear();
+ }
+}
+
+bool QuotaManager::IsSanitizedOriginValid(const nsACString& aSanitizedOrigin) {
+ AssertIsOnIOThread();
+
+ // Do not parse this sanitized origin string, if we already parsed it.
+ return mValidOrigins.LookupOrInsertWith(
+ aSanitizedOrigin, [&aSanitizedOrigin] {
+ nsCString spec;
+ OriginAttributes attrs;
+ nsCString originalSuffix;
+ const auto result = OriginParser::ParseOrigin(aSanitizedOrigin, spec,
+ &attrs, originalSuffix);
+
+ return result == OriginParser::ValidOrigin;
+ });
+}
+
+Result<nsCString, nsresult> QuotaManager::EnsureStorageOriginFromOrigin(
+ const nsACString& aOrigin) {
+ MutexAutoLock lock(mQuotaMutex);
+
+ QM_TRY_UNWRAP(
+ auto storageOrigin,
+ mOriginToStorageOriginMap.TryLookupOrInsertWith(
+ aOrigin, [this, &aOrigin]() -> Result<nsCString, nsresult> {
+ OriginAttributes originAttributes;
+
+ nsCString originNoSuffix;
+ QM_TRY(MOZ_TO_RESULT(
+ originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix)));
+
+ nsCOMPtr<nsIURI> uri;
+ QM_TRY(MOZ_TO_RESULT(
+ NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .SetSpec(originNoSuffix)
+ .SetScheme(kUUIDOriginScheme)
+ .SetHost(NSID_TrimBracketsASCII(nsID::GenerateUUID()))
+ .SetPort(-1)
+ .Finalize(uri)));
+
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(uri, OriginAttributes{});
+ QM_TRY(MOZ_TO_RESULT(principal));
+
+ QM_TRY_UNWRAP(auto origin,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsAutoCString, principal, GetOrigin));
+
+ mStorageOriginToOriginMap.WithEntryHandle(
+ origin,
+ [&aOrigin](auto entryHandle) { entryHandle.Insert(aOrigin); });
+
+ return nsCString(std::move(origin));
+ }));
+
+ return nsCString(std::move(storageOrigin));
+}
+
+Result<nsCString, nsresult> QuotaManager::GetOriginFromStorageOrigin(
+ const nsACString& aStorageOrigin) {
+ MutexAutoLock lock(mQuotaMutex);
+
+ auto maybeOrigin = mStorageOriginToOriginMap.MaybeGet(aStorageOrigin);
+ if (maybeOrigin.isNothing()) {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ return maybeOrigin.ref();
+}
+
+int64_t QuotaManager::GenerateDirectoryLockId() {
+ const int64_t directorylockId = mNextDirectoryLockId;
+
+ if (CheckedInt64 result = CheckedInt64(mNextDirectoryLockId) + 1;
+ result.isValid()) {
+ mNextDirectoryLockId = result.value();
+ } else {
+ NS_WARNING("Quota manager has run out of ids for directory locks!");
+
+ // There's very little chance for this to happen given the max size of
+ // 64 bit integer but if it happens we can just reset mNextDirectoryLockId
+ // to zero since such old directory locks shouldn't exist anymore.
+ mNextDirectoryLockId = 0;
+ }
+
+ // TODO: Maybe add an assertion here to check that there is no existing
+ // directory lock with given id.
+
+ return directorylockId;
+}
+
+template <typename Func>
+auto QuotaManager::ExecuteInitialization(const Initialization aInitialization,
+ Func&& aFunc)
+ -> std::invoke_result_t<Func, const FirstInitializationAttempt<
+ Initialization, StringGenerator>&> {
+ return quota::ExecuteInitialization(mInitializationInfo, aInitialization,
+ std::forward<Func>(aFunc));
+}
+
+template <typename Func>
+auto QuotaManager::ExecuteInitialization(const Initialization aInitialization,
+ const nsACString& aContext,
+ Func&& aFunc)
+ -> std::invoke_result_t<Func, const FirstInitializationAttempt<
+ Initialization, StringGenerator>&> {
+ return quota::ExecuteInitialization(mInitializationInfo, aInitialization,
+ aContext, std::forward<Func>(aFunc));
+}
+
+template <typename Func>
+auto QuotaManager::ExecuteOriginInitialization(
+ const nsACString& aOrigin, const OriginInitialization aInitialization,
+ const nsACString& aContext, Func&& aFunc)
+ -> std::invoke_result_t<Func, const FirstInitializationAttempt<
+ Initialization, StringGenerator>&> {
+ return quota::ExecuteInitialization(
+ mInitializationInfo.MutableOriginInitializationInfoRef(
+ aOrigin, CreateIfNonExistent{}),
+ aInitialization, aContext, std::forward<Func>(aFunc));
+}
+
+/*******************************************************************************
+ * Local class implementations
+ ******************************************************************************/
+
+CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex& aMutex,
+ uint64_t aMinSizeToBeFreed)
+ : Runnable("dom::quota::CollectOriginsHelper"),
+ mMinSizeToBeFreed(aMinSizeToBeFreed),
+ mMutex(aMutex),
+ mCondVar(aMutex, "CollectOriginsHelper::mCondVar"),
+ mSizeToBeFreed(0),
+ mWaiting(true) {
+ MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
+ mMutex.AssertCurrentThreadOwns();
+}
+
+int64_t CollectOriginsHelper::BlockAndReturnOriginsForEviction(
+ nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
+ MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
+ mMutex.AssertCurrentThreadOwns();
+
+ while (mWaiting) {
+ mCondVar.Wait();
+ }
+
+ mLocks.SwapElements(aLocks);
+ return mSizeToBeFreed;
+}
+
+NS_IMETHODIMP
+CollectOriginsHelper::Run() {
+ AssertIsOnBackgroundThread();
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ NS_ASSERTION(quotaManager, "Shouldn't be null!");
+
+ // We use extra stack vars here to avoid race detector warnings (the same
+ // memory accessed with and without the lock held).
+ nsTArray<RefPtr<OriginDirectoryLock>> locks;
+ uint64_t sizeToBeFreed =
+ quotaManager->CollectOriginsForEviction(mMinSizeToBeFreed, locks);
+
+ MutexAutoLock lock(mMutex);
+
+ NS_ASSERTION(mWaiting, "Huh?!");
+
+ mLocks.SwapElements(locks);
+ mSizeToBeFreed = sizeToBeFreed;
+ mWaiting = false;
+ mCondVar.Notify();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StoragePressureRunnable::Run() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!obsSvc)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsISupportsPRUint64> wrapper =
+ do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
+ if (NS_WARN_IF(!wrapper)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ wrapper->SetData(mUsage);
+
+ obsSvc->NotifyObservers(wrapper, "QuotaManager::StoragePressure", u"");
+
+ return NS_OK;
+}
+
+TimeStamp RecordTimeDeltaHelper::Start() {
+ MOZ_ASSERT(IsOnIOThread() || IsOnBackgroundThread());
+
+ // XXX: If a OS sleep/wake occur after mStartTime is initialized but before
+ // gLastOSWake is set, then this time duration would still be recorded with
+ // key "Normal". We are assumming this is rather rare to happen.
+ mStartTime.init(TimeStamp::Now());
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+
+ return *mStartTime;
+}
+
+TimeStamp RecordTimeDeltaHelper::End() {
+ MOZ_ASSERT(IsOnIOThread() || IsOnBackgroundThread());
+
+ mEndTime.init(TimeStamp::Now());
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+
+ return *mEndTime;
+}
+
+NS_IMETHODIMP
+RecordTimeDeltaHelper::Run() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mInitializedTime.isSome()) {
+ // Keys for QM_QUOTA_INFO_LOAD_TIME_V0 and QM_SHUTDOWN_TIME_V0:
+ // Normal: Normal conditions.
+ // WasSuspended: There was a OS sleep so that it was suspended.
+ // TimeStampErr1: The recorded start time is unexpectedly greater than the
+ // end time.
+ // TimeStampErr2: The initialized time for the recording class is unexpectly
+ // greater than the last OS wake time.
+ const auto key = [this, wasSuspended = gLastOSWake > *mInitializedTime]() {
+ if (wasSuspended) {
+ return "WasSuspended"_ns;
+ }
+
+ // XXX File a bug if we have data for this key.
+ // We found negative values in our query in STMO for
+ // ScalarID::QM_REPOSITORIES_INITIALIZATION_TIME. This shouldn't happen
+ // because the documentation for TimeStamp::Now() says it returns a
+ // monotonically increasing number.
+ if (*mStartTime > *mEndTime) {
+ return "TimeStampErr1"_ns;
+ }
+
+ if (*mInitializedTime > gLastOSWake) {
+ return "TimeStampErr2"_ns;
+ }
+
+ return "Normal"_ns;
+ }();
+
+ Telemetry::AccumulateTimeDelta(mHistogram, key, *mStartTime, *mEndTime);
+
+ return NS_OK;
+ }
+
+ gLastOSWake = TimeStamp::Now();
+ mInitializedTime.init(gLastOSWake);
+
+ return NS_OK;
+}
+
+nsresult StorageOperationBase::GetDirectoryMetadata(nsIFile* aDirectory,
+ int64_t& aTimestamp,
+ nsACString& aGroup,
+ nsACString& aOrigin,
+ Nullable<bool>& aIsApp) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aDirectory);
+
+ QM_TRY_INSPECT(
+ const auto& binaryStream,
+ GetBinaryInputStream(*aDirectory, nsLiteralString(METADATA_FILE_NAME)));
+
+ QM_TRY_INSPECT(const uint64_t& timestamp,
+ MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
+
+ QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCString, binaryStream, ReadCString));
+
+ QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCString, binaryStream, ReadCString));
+
+ Nullable<bool> isApp;
+ bool value;
+ if (NS_SUCCEEDED(binaryStream->ReadBoolean(&value))) {
+ isApp.SetValue(value);
+ }
+
+ aTimestamp = timestamp;
+ aGroup = group;
+ aOrigin = origin;
+ aIsApp = std::move(isApp);
+ return NS_OK;
+}
+
+nsresult StorageOperationBase::GetDirectoryMetadata2(
+ nsIFile* aDirectory, int64_t& aTimestamp, nsACString& aSuffix,
+ nsACString& aGroup, nsACString& aOrigin, bool& aIsApp) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aDirectory);
+
+ QM_TRY_INSPECT(const auto& binaryStream,
+ GetBinaryInputStream(*aDirectory,
+ nsLiteralString(METADATA_V2_FILE_NAME)));
+
+ QM_TRY_INSPECT(const uint64_t& timestamp,
+ MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
+
+ QM_TRY_INSPECT(const bool& persisted,
+ MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
+ Unused << persisted;
+
+ QM_TRY_INSPECT(const bool& reservedData1,
+ MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
+ Unused << reservedData1;
+
+ QM_TRY_INSPECT(const bool& reservedData2,
+ MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
+ Unused << reservedData2;
+
+ QM_TRY_INSPECT(const auto& suffix, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCString, binaryStream, ReadCString));
+
+ QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCString, binaryStream, ReadCString));
+
+ QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCString, binaryStream, ReadCString));
+
+ QM_TRY_INSPECT(const bool& isApp,
+ MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
+
+ aTimestamp = timestamp;
+ aSuffix = suffix;
+ aGroup = group;
+ aOrigin = origin;
+ aIsApp = isApp;
+ return NS_OK;
+}
+
+int64_t StorageOperationBase::GetOriginLastModifiedTime(
+ const OriginProps& aOriginProps) {
+ return GetLastModifiedTime(*aOriginProps.mPersistenceType,
+ *aOriginProps.mDirectory);
+}
+
+nsresult StorageOperationBase::RemoveObsoleteOrigin(
+ const OriginProps& aOriginProps) {
+ AssertIsOnIOThread();
+
+ QM_WARNING(
+ "Deleting obsolete %s directory that is no longer a legal "
+ "origin!",
+ NS_ConvertUTF16toUTF8(aOriginProps.mLeafName).get());
+
+ QM_TRY(MOZ_TO_RESULT(aOriginProps.mDirectory->Remove(/* recursive */ true)));
+
+ return NS_OK;
+}
+
+Result<bool, nsresult> StorageOperationBase::MaybeRenameOrigin(
+ const OriginProps& aOriginProps) {
+ AssertIsOnIOThread();
+
+ const nsAString& oldLeafName = aOriginProps.mLeafName;
+
+ const auto newLeafName =
+ MakeSanitizedOriginString(aOriginProps.mOriginMetadata.mOrigin);
+
+ if (oldLeafName == newLeafName) {
+ return false;
+ }
+
+ QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ aOriginProps.mOriginMetadata)));
+
+ QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ /* aPersisted */ false, aOriginProps.mOriginMetadata)));
+
+ QM_TRY_INSPECT(const auto& newFile,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCOMPtr<nsIFile>, *aOriginProps.mDirectory, GetParent));
+
+ QM_TRY(MOZ_TO_RESULT(newFile->Append(newLeafName)));
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(newFile, Exists));
+
+ if (exists) {
+ QM_WARNING(
+ "Can't rename %s directory to %s, the target already exists, removing "
+ "instead of renaming!",
+ NS_ConvertUTF16toUTF8(oldLeafName).get(),
+ NS_ConvertUTF16toUTF8(newLeafName).get());
+ }
+
+ QM_TRY(CallWithDelayedRetriesIfAccessDenied(
+ [&exists, &aOriginProps, &newLeafName] {
+ if (exists) {
+ QM_TRY_RETURN(MOZ_TO_RESULT(
+ aOriginProps.mDirectory->Remove(/* recursive */ true)));
+ }
+ QM_TRY_RETURN(MOZ_TO_RESULT(
+ aOriginProps.mDirectory->RenameTo(nullptr, newLeafName)));
+ },
+ StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_maxRetries(),
+ StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_delayMs()));
+
+ return true;
+}
+
+nsresult StorageOperationBase::ProcessOriginDirectories() {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(!mOriginProps.IsEmpty());
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ for (auto& originProps : mOriginProps) {
+ switch (originProps.mType) {
+ case OriginProps::eChrome: {
+ originProps.mOriginMetadata = {QuotaManager::GetInfoForChrome(),
+ *originProps.mPersistenceType};
+ break;
+ }
+
+ case OriginProps::eContent: {
+ nsCOMPtr<nsIURI> uri;
+ QM_TRY(
+ MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), originProps.mSpec)));
+
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(uri, originProps.mAttrs);
+ QM_TRY(MOZ_TO_RESULT(principal));
+
+ PrincipalInfo principalInfo;
+ QM_TRY(
+ MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
+
+ QM_WARNONLY_TRY_UNWRAP(
+ auto valid,
+ MOZ_TO_RESULT(quotaManager->IsPrincipalInfoValid(principalInfo)));
+
+ if (!valid) {
+ // Unknown directories during upgrade are allowed. Just warn if we
+ // find them.
+ UNKNOWN_FILE_WARNING(originProps.mLeafName);
+ originProps.mIgnore = true;
+ break;
+ }
+
+ QM_TRY_UNWRAP(
+ auto principalMetadata,
+ quotaManager->GetInfoFromValidatedPrincipalInfo(principalInfo));
+
+ originProps.mOriginMetadata = {std::move(principalMetadata),
+ *originProps.mPersistenceType};
+
+ break;
+ }
+
+ case OriginProps::eObsolete: {
+ // There's no way to get info for obsolete origins.
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Bad type!");
+ }
+ }
+
+ // Don't try to upgrade obsolete origins, remove them right after we detect
+ // them.
+ for (const auto& originProps : mOriginProps) {
+ if (originProps.mType == OriginProps::eObsolete) {
+ MOZ_ASSERT(originProps.mOriginMetadata.mSuffix.IsEmpty());
+ MOZ_ASSERT(originProps.mOriginMetadata.mGroup.IsEmpty());
+ MOZ_ASSERT(originProps.mOriginMetadata.mOrigin.IsEmpty());
+
+ QM_TRY(MOZ_TO_RESULT(RemoveObsoleteOrigin(originProps)));
+ } else if (!originProps.mIgnore) {
+ MOZ_ASSERT(!originProps.mOriginMetadata.mGroup.IsEmpty());
+ MOZ_ASSERT(!originProps.mOriginMetadata.mOrigin.IsEmpty());
+
+ QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectory(originProps)));
+ }
+ }
+
+ return NS_OK;
+}
+
+// XXX Do the fallible initialization in a separate non-static member function
+// of StorageOperationBase and eventually get rid of this method and use a
+// normal constructor instead.
+template <typename PersistenceTypeFunc>
+nsresult StorageOperationBase::OriginProps::Init(
+ PersistenceTypeFunc&& aPersistenceTypeFunc) {
+ AssertIsOnIOThread();
+
+ QM_TRY_INSPECT(const auto& leafName,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, *mDirectory,
+ GetLeafName));
+
+ // XXX Consider using QuotaManager::ParseOrigin here.
+ nsCString spec;
+ OriginAttributes attrs;
+ nsCString originalSuffix;
+ OriginParser::ResultType result = OriginParser::ParseOrigin(
+ NS_ConvertUTF16toUTF8(leafName), spec, &attrs, originalSuffix);
+ if (NS_WARN_IF(result == OriginParser::InvalidOrigin)) {
+ mType = OriginProps::eInvalid;
+ return NS_OK;
+ }
+
+ const auto persistenceType = [&]() -> PersistenceType {
+ // XXX We shouldn't continue with initialization if OriginParser returned
+ // anything else but ValidOrigin. Otherwise, we have to deal with empty
+ // spec when the origin is obsolete, like here. The caller should handle
+ // the errors. Until it's fixed, we have to treat obsolete origins as
+ // origins with unknown/invalid persistence type.
+ if (result != OriginParser::ValidOrigin) {
+ return PERSISTENCE_TYPE_INVALID;
+ }
+ return std::forward<PersistenceTypeFunc>(aPersistenceTypeFunc)(spec);
+ }();
+
+ mLeafName = leafName;
+ mSpec = spec;
+ mAttrs = attrs;
+ mOriginalSuffix = originalSuffix;
+ mPersistenceType.init(persistenceType);
+ if (result == OriginParser::ObsoleteOrigin) {
+ mType = eObsolete;
+ } else if (mSpec.EqualsLiteral(kChromeOrigin)) {
+ mType = eChrome;
+ } else {
+ mType = eContent;
+ }
+
+ return NS_OK;
+}
+
+nsresult RepositoryOperationBase::ProcessRepository() {
+ AssertIsOnIOThread();
+
+#ifdef DEBUG
+ {
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(mDirectory, Exists),
+ QM_ASSERT_UNREACHABLE);
+ MOZ_ASSERT(exists);
+ }
+#endif
+
+ QM_TRY(CollectEachFileEntry(
+ *mDirectory,
+ [](const auto& originFile) -> Result<mozilla::Ok, nsresult> {
+ QM_TRY_INSPECT(const auto& leafName,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsAutoString, originFile, GetLeafName));
+
+ // Unknown files during upgrade are allowed. Just warn if we find
+ // them.
+ if (!IsOSMetadata(leafName)) {
+ UNKNOWN_FILE_WARNING(leafName);
+ }
+
+ return mozilla::Ok{};
+ },
+ [&self = *this](const auto& originDir) -> Result<mozilla::Ok, nsresult> {
+ OriginProps originProps(WrapMovingNotNullUnchecked(originDir));
+ QM_TRY(MOZ_TO_RESULT(originProps.Init([&self](const auto& aSpec) {
+ return self.PersistenceTypeFromSpec(aSpec);
+ })));
+ // Bypass invalid origins while upgrading
+ QM_TRY(OkIf(originProps.mType != OriginProps::eInvalid), mozilla::Ok{});
+
+ if (originProps.mType != OriginProps::eObsolete) {
+ QM_TRY_INSPECT(const bool& removed,
+ MOZ_TO_RESULT_INVOKE_MEMBER(
+ self, PrepareOriginDirectory, originProps));
+ if (removed) {
+ return mozilla::Ok{};
+ }
+ }
+
+ self.mOriginProps.AppendElement(std::move(originProps));
+
+ return mozilla::Ok{};
+ }));
+
+ if (mOriginProps.IsEmpty()) {
+ return NS_OK;
+ }
+
+ QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectories()));
+
+ return NS_OK;
+}
+
+template <typename UpgradeMethod>
+nsresult RepositoryOperationBase::MaybeUpgradeClients(
+ const OriginProps& aOriginProps, UpgradeMethod aMethod) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aMethod);
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ QM_TRY(CollectEachFileEntry(
+ *aOriginProps.mDirectory,
+ [](const auto& file) -> Result<mozilla::Ok, nsresult> {
+ QM_TRY_INSPECT(
+ const auto& leafName,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file, GetLeafName));
+
+ if (!IsOriginMetadata(leafName) && !IsTempMetadata(leafName)) {
+ UNKNOWN_FILE_WARNING(leafName);
+ }
+
+ return mozilla::Ok{};
+ },
+ [quotaManager, &aMethod,
+ &self = *this](const auto& dir) -> Result<mozilla::Ok, nsresult> {
+ QM_TRY_INSPECT(
+ const auto& leafName,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, dir, GetLeafName));
+
+ QM_TRY_INSPECT(const bool& removed,
+ MOZ_TO_RESULT_INVOKE_MEMBER(self, PrepareClientDirectory,
+ dir, leafName));
+ if (removed) {
+ return mozilla::Ok{};
+ }
+
+ Client::Type clientType;
+ bool ok = Client::TypeFromText(leafName, clientType, fallible);
+ if (!ok) {
+ UNKNOWN_FILE_WARNING(leafName);
+ return mozilla::Ok{};
+ }
+
+ Client* client = quotaManager->GetClient(clientType);
+ MOZ_ASSERT(client);
+
+ QM_TRY(MOZ_TO_RESULT((client->*aMethod)(dir)));
+
+ return mozilla::Ok{};
+ }));
+
+ return NS_OK;
+}
+
+nsresult RepositoryOperationBase::PrepareClientDirectory(
+ nsIFile* aFile, const nsAString& aLeafName, bool& aRemoved) {
+ AssertIsOnIOThread();
+
+ aRemoved = false;
+ return NS_OK;
+}
+
+nsresult CreateOrUpgradeDirectoryMetadataHelper::Init() {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(mDirectory);
+
+ const auto maybeLegacyPersistenceType =
+ LegacyPersistenceTypeFromFile(*mDirectory, fallible);
+ QM_TRY(OkIf(maybeLegacyPersistenceType.isSome()), Err(NS_ERROR_FAILURE));
+
+ mLegacyPersistenceType.init(maybeLegacyPersistenceType.value());
+
+ return NS_OK;
+}
+
+Maybe<CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceType>
+CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceTypeFromFile(
+ nsIFile& aFile, const fallible_t&) {
+ nsAutoString leafName;
+ MOZ_ALWAYS_SUCCEEDS(aFile.GetLeafName(leafName));
+
+ if (leafName.Equals(u"persistent"_ns)) {
+ return Some(LegacyPersistenceType::Persistent);
+ }
+
+ if (leafName.Equals(u"temporary"_ns)) {
+ return Some(LegacyPersistenceType::Temporary);
+ }
+
+ return Nothing();
+}
+
+PersistenceType
+CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromLegacyPersistentSpec(
+ const nsCString& aSpec) {
+ if (QuotaManager::IsOriginInternal(aSpec)) {
+ return PERSISTENCE_TYPE_PERSISTENT;
+ }
+
+ return PERSISTENCE_TYPE_DEFAULT;
+}
+
+PersistenceType CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromSpec(
+ const nsCString& aSpec) {
+ switch (*mLegacyPersistenceType) {
+ case LegacyPersistenceType::Persistent:
+ return PersistenceTypeFromLegacyPersistentSpec(aSpec);
+ case LegacyPersistenceType::Temporary:
+ return PERSISTENCE_TYPE_TEMPORARY;
+ }
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad legacy persistence type value!");
+}
+
+nsresult CreateOrUpgradeDirectoryMetadataHelper::MaybeUpgradeOriginDirectory(
+ nsIFile* aDirectory) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aDirectory);
+
+ QM_TRY_INSPECT(
+ const auto& metadataFile,
+ CloneFileAndAppend(*aDirectory, nsLiteralString(METADATA_FILE_NAME)));
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(metadataFile, Exists));
+
+ if (!exists) {
+ // Directory structure upgrade needed.
+ // Move all files to IDB specific directory.
+
+ nsString idbDirectoryName;
+ QM_TRY(OkIf(Client::TypeToText(Client::IDB, idbDirectoryName, fallible)),
+ NS_ERROR_FAILURE);
+
+ QM_TRY_INSPECT(const auto& idbDirectory,
+ CloneFileAndAppend(*aDirectory, idbDirectoryName));
+
+ // Usually we only use QM_OR_ELSE_LOG_VERBOSE/QM_OR_ELSE_LOG_VERBOSE_IF
+ // with Create and NS_ERROR_FILE_ALREADY_EXISTS check, but typically the
+ // idb directory shouldn't exist during the upgrade and the upgrade runs
+ // only once in most of the cases, so the use of QM_OR_ELSE_WARN_IF is ok
+ // here.
+ QM_TRY(QM_OR_ELSE_WARN_IF(
+ // Expression.
+ MOZ_TO_RESULT(idbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)),
+ // Predicate.
+ IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
+ // Fallback.
+ ([&idbDirectory](const nsresult rv) -> Result<Ok, nsresult> {
+ QM_TRY_INSPECT(
+ const bool& isDirectory,
+ MOZ_TO_RESULT_INVOKE_MEMBER(idbDirectory, IsDirectory));
+
+ QM_TRY(OkIf(isDirectory), Err(NS_ERROR_UNEXPECTED));
+
+ return Ok{};
+ })));
+
+ QM_TRY(CollectEachFile(
+ *aDirectory,
+ [&idbDirectory, &idbDirectoryName](
+ const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
+ QM_TRY_INSPECT(const auto& leafName,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file,
+ GetLeafName));
+
+ if (!leafName.Equals(idbDirectoryName)) {
+ QM_TRY(MOZ_TO_RESULT(file->MoveTo(idbDirectory, u""_ns)));
+ }
+
+ return Ok{};
+ }));
+
+ QM_TRY(
+ MOZ_TO_RESULT(metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644)));
+ }
+
+ return NS_OK;
+}
+
+nsresult CreateOrUpgradeDirectoryMetadataHelper::PrepareOriginDirectory(
+ OriginProps& aOriginProps, bool* aRemoved) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aRemoved);
+
+ if (*mLegacyPersistenceType == LegacyPersistenceType::Persistent) {
+ QM_TRY(MOZ_TO_RESULT(
+ MaybeUpgradeOriginDirectory(aOriginProps.mDirectory.get())));
+
+ aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
+ } else {
+ int64_t timestamp;
+ nsCString group;
+ nsCString origin;
+ Nullable<bool> isApp;
+
+ QM_WARNONLY_TRY_UNWRAP(
+ const auto maybeDirectoryMetadata,
+ MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
+ timestamp, group, origin, isApp)));
+ if (!maybeDirectoryMetadata) {
+ aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
+ aOriginProps.mNeedsRestore = true;
+ } else if (!isApp.IsNull()) {
+ aOriginProps.mIgnore = true;
+ }
+ }
+
+ *aRemoved = false;
+ return NS_OK;
+}
+
+nsresult CreateOrUpgradeDirectoryMetadataHelper::ProcessOriginDirectory(
+ const OriginProps& aOriginProps) {
+ AssertIsOnIOThread();
+
+ if (*mLegacyPersistenceType == LegacyPersistenceType::Persistent) {
+ QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ aOriginProps.mOriginMetadata)));
+
+ // Move internal origins to new persistent storage.
+ if (PersistenceTypeFromLegacyPersistentSpec(aOriginProps.mSpec) ==
+ PERSISTENCE_TYPE_PERSISTENT) {
+ if (!mPermanentStorageDir) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ const nsString& permanentStoragePath =
+ quotaManager->GetStoragePath(PERSISTENCE_TYPE_PERSISTENT);
+
+ QM_TRY_UNWRAP(mPermanentStorageDir,
+ QM_NewLocalFile(permanentStoragePath));
+ }
+
+ const nsAString& leafName = aOriginProps.mLeafName;
+
+ QM_TRY_INSPECT(const auto& newDirectory,
+ CloneFileAndAppend(*mPermanentStorageDir, leafName));
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(newDirectory, Exists));
+
+ if (exists) {
+ QM_WARNING("Found %s in storage/persistent and storage/permanent !",
+ NS_ConvertUTF16toUTF8(leafName).get());
+
+ QM_TRY(MOZ_TO_RESULT(
+ aOriginProps.mDirectory->Remove(/* recursive */ true)));
+ } else {
+ QM_TRY(MOZ_TO_RESULT(
+ aOriginProps.mDirectory->MoveTo(mPermanentStorageDir, u""_ns)));
+ }
+ }
+ } else if (aOriginProps.mNeedsRestore) {
+ QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ aOriginProps.mOriginMetadata)));
+ } else if (!aOriginProps.mIgnore) {
+ QM_TRY_INSPECT(const auto& file,
+ CloneFileAndAppend(*aOriginProps.mDirectory,
+ nsLiteralString(METADATA_FILE_NAME)));
+
+ QM_TRY_INSPECT(const auto& stream,
+ GetBinaryOutputStream(*file, FileFlag::Append));
+
+ MOZ_ASSERT(stream);
+
+ // Currently unused (used to be isApp).
+ QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(false)));
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeStorageHelperBase::Init() {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(mDirectory);
+
+ const auto maybePersistenceType =
+ PersistenceTypeFromFile(*mDirectory, fallible);
+ QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
+
+ mPersistenceType.init(maybePersistenceType.value());
+
+ return NS_OK;
+}
+
+PersistenceType UpgradeStorageHelperBase::PersistenceTypeFromSpec(
+ const nsCString& aSpec) {
+ // There's no moving of origin directories between repositories like in the
+ // CreateOrUpgradeDirectoryMetadataHelper
+ return *mPersistenceType;
+}
+
+nsresult UpgradeStorageFrom0_0To1_0Helper::PrepareOriginDirectory(
+ OriginProps& aOriginProps, bool* aRemoved) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aRemoved);
+
+ int64_t timestamp;
+ nsCString group;
+ nsCString origin;
+ Nullable<bool> isApp;
+
+ QM_WARNONLY_TRY_UNWRAP(
+ const auto maybeDirectoryMetadata,
+ MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
+ timestamp, group, origin, isApp)));
+ if (!maybeDirectoryMetadata || isApp.IsNull()) {
+ aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
+ aOriginProps.mNeedsRestore = true;
+ } else {
+ aOriginProps.mTimestamp = timestamp;
+ }
+
+ *aRemoved = false;
+ return NS_OK;
+}
+
+nsresult UpgradeStorageFrom0_0To1_0Helper::ProcessOriginDirectory(
+ const OriginProps& aOriginProps) {
+ AssertIsOnIOThread();
+
+ // This handles changes in origin string generation from nsIPrincipal,
+ // especially the change from: appId+inMozBrowser+originNoSuffix
+ // to: origin (with origin suffix).
+ QM_TRY_INSPECT(const bool& renamed, MaybeRenameOrigin(aOriginProps));
+ if (renamed) {
+ return NS_OK;
+ }
+
+ if (aOriginProps.mNeedsRestore) {
+ QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ aOriginProps.mOriginMetadata)));
+ }
+
+ QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ /* aPersisted */ false, aOriginProps.mOriginMetadata)));
+
+ return NS_OK;
+}
+
+nsresult UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveMorgueDirectory(
+ const OriginProps& aOriginProps) {
+ AssertIsOnIOThread();
+
+ // The Cache API was creating top level morgue directories by accident for
+ // a short time in nightly. This unfortunately prevents all storage from
+ // working. So recover these profiles permanently by removing these corrupt
+ // directories as part of this upgrade.
+
+ QM_TRY_INSPECT(const auto& morgueDir,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCOMPtr<nsIFile>, *aOriginProps.mDirectory, Clone));
+
+ QM_TRY(MOZ_TO_RESULT(morgueDir->Append(u"morgue"_ns)));
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(morgueDir, Exists));
+
+ if (exists) {
+ QM_WARNING("Deleting accidental morgue directory!");
+
+ QM_TRY(MOZ_TO_RESULT(morgueDir->Remove(/* recursive */ true)));
+ }
+
+ return NS_OK;
+}
+
+Result<bool, nsresult> UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveAppsData(
+ const OriginProps& aOriginProps) {
+ AssertIsOnIOThread();
+
+ // TODO: This method was empty for some time due to accidental changes done
+ // in bug 1320404. This led to renaming of origin directories like:
+ // https+++developer.cdn.mozilla.net^appId=1007&inBrowser=1
+ // to:
+ // https+++developer.cdn.mozilla.net^inBrowser=1
+ // instead of just removing them.
+
+ const nsCString& originalSuffix = aOriginProps.mOriginalSuffix;
+ if (!originalSuffix.IsEmpty()) {
+ MOZ_ASSERT(originalSuffix[0] == '^');
+
+ if (!URLParams::Parse(
+ Substring(originalSuffix, 1, originalSuffix.Length() - 1),
+ [](const nsAString& aName, const nsAString& aValue) {
+ if (aName.EqualsLiteral("appId")) {
+ return false;
+ }
+
+ return true;
+ })) {
+ QM_TRY(MOZ_TO_RESULT(RemoveObsoleteOrigin(aOriginProps)));
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult UpgradeStorageFrom1_0To2_0Helper::PrepareOriginDirectory(
+ OriginProps& aOriginProps, bool* aRemoved) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aRemoved);
+
+ QM_TRY(MOZ_TO_RESULT(MaybeRemoveMorgueDirectory(aOriginProps)));
+
+ QM_TRY(MOZ_TO_RESULT(
+ MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom1_0To2_0)));
+
+ QM_TRY_INSPECT(const bool& removed, MaybeRemoveAppsData(aOriginProps));
+ if (removed) {
+ *aRemoved = true;
+ return NS_OK;
+ }
+
+ int64_t timestamp;
+ nsCString group;
+ nsCString origin;
+ Nullable<bool> isApp;
+ QM_WARNONLY_TRY_UNWRAP(
+ const auto maybeDirectoryMetadata,
+ MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
+ timestamp, group, origin, isApp)));
+ if (!maybeDirectoryMetadata || isApp.IsNull()) {
+ aOriginProps.mNeedsRestore = true;
+ }
+
+ nsCString suffix;
+ QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
+ MOZ_TO_RESULT(GetDirectoryMetadata2(
+ aOriginProps.mDirectory.get(), timestamp, suffix,
+ group, origin, isApp.SetValue())));
+ if (!maybeDirectoryMetadata2) {
+ aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
+ aOriginProps.mNeedsRestore2 = true;
+ } else {
+ aOriginProps.mTimestamp = timestamp;
+ }
+
+ *aRemoved = false;
+ return NS_OK;
+}
+
+nsresult UpgradeStorageFrom1_0To2_0Helper::ProcessOriginDirectory(
+ const OriginProps& aOriginProps) {
+ AssertIsOnIOThread();
+
+ // This handles changes in origin string generation from nsIPrincipal,
+ // especially the stripping of obsolete origin attributes like addonId.
+ QM_TRY_INSPECT(const bool& renamed, MaybeRenameOrigin(aOriginProps));
+ if (renamed) {
+ return NS_OK;
+ }
+
+ if (aOriginProps.mNeedsRestore) {
+ QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ aOriginProps.mOriginMetadata)));
+ }
+
+ if (aOriginProps.mNeedsRestore2) {
+ QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ /* aPersisted */ false, aOriginProps.mOriginMetadata)));
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeStorageFrom2_0To2_1Helper::PrepareOriginDirectory(
+ OriginProps& aOriginProps, bool* aRemoved) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aRemoved);
+
+ QM_TRY(MOZ_TO_RESULT(
+ MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom2_0To2_1)));
+
+ int64_t timestamp;
+ nsCString group;
+ nsCString origin;
+ Nullable<bool> isApp;
+ QM_WARNONLY_TRY_UNWRAP(
+ const auto maybeDirectoryMetadata,
+ MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
+ timestamp, group, origin, isApp)));
+ if (!maybeDirectoryMetadata || isApp.IsNull()) {
+ aOriginProps.mNeedsRestore = true;
+ }
+
+ nsCString suffix;
+ QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
+ MOZ_TO_RESULT(GetDirectoryMetadata2(
+ aOriginProps.mDirectory.get(), timestamp, suffix,
+ group, origin, isApp.SetValue())));
+ if (!maybeDirectoryMetadata2) {
+ aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
+ aOriginProps.mNeedsRestore2 = true;
+ } else {
+ aOriginProps.mTimestamp = timestamp;
+ }
+
+ *aRemoved = false;
+ return NS_OK;
+}
+
+nsresult UpgradeStorageFrom2_0To2_1Helper::ProcessOriginDirectory(
+ const OriginProps& aOriginProps) {
+ AssertIsOnIOThread();
+
+ if (aOriginProps.mNeedsRestore) {
+ QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ aOriginProps.mOriginMetadata)));
+ }
+
+ if (aOriginProps.mNeedsRestore2) {
+ QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ /* aPersisted */ false, aOriginProps.mOriginMetadata)));
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeStorageFrom2_1To2_2Helper::PrepareOriginDirectory(
+ OriginProps& aOriginProps, bool* aRemoved) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aRemoved);
+
+ QM_TRY(MOZ_TO_RESULT(
+ MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom2_1To2_2)));
+
+ int64_t timestamp;
+ nsCString group;
+ nsCString origin;
+ Nullable<bool> isApp;
+ QM_WARNONLY_TRY_UNWRAP(
+ const auto maybeDirectoryMetadata,
+ MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
+ timestamp, group, origin, isApp)));
+ if (!maybeDirectoryMetadata || isApp.IsNull()) {
+ aOriginProps.mNeedsRestore = true;
+ }
+
+ nsCString suffix;
+ QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
+ MOZ_TO_RESULT(GetDirectoryMetadata2(
+ aOriginProps.mDirectory.get(), timestamp, suffix,
+ group, origin, isApp.SetValue())));
+ if (!maybeDirectoryMetadata2) {
+ aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
+ aOriginProps.mNeedsRestore2 = true;
+ } else {
+ aOriginProps.mTimestamp = timestamp;
+ }
+
+ *aRemoved = false;
+ return NS_OK;
+}
+
+nsresult UpgradeStorageFrom2_1To2_2Helper::ProcessOriginDirectory(
+ const OriginProps& aOriginProps) {
+ AssertIsOnIOThread();
+
+ if (aOriginProps.mNeedsRestore) {
+ QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ aOriginProps.mOriginMetadata)));
+ }
+
+ if (aOriginProps.mNeedsRestore2) {
+ QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ /* aPersisted */ false, aOriginProps.mOriginMetadata)));
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeStorageFrom2_1To2_2Helper::PrepareClientDirectory(
+ nsIFile* aFile, const nsAString& aLeafName, bool& aRemoved) {
+ AssertIsOnIOThread();
+
+ if (Client::IsDeprecatedClient(aLeafName)) {
+ QM_WARNING("Deleting deprecated %s client!",
+ NS_ConvertUTF16toUTF8(aLeafName).get());
+
+ QM_TRY(MOZ_TO_RESULT(aFile->Remove(true)));
+
+ aRemoved = true;
+ } else {
+ aRemoved = false;
+ }
+
+ return NS_OK;
+}
+
+nsresult RestoreDirectoryMetadata2Helper::Init() {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(mDirectory);
+
+ nsCOMPtr<nsIFile> parentDir;
+ QM_TRY(MOZ_TO_RESULT(mDirectory->GetParent(getter_AddRefs(parentDir))));
+
+ const auto maybePersistenceType =
+ PersistenceTypeFromFile(*parentDir, fallible);
+ QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
+
+ mPersistenceType.init(maybePersistenceType.value());
+
+ return NS_OK;
+}
+
+nsresult RestoreDirectoryMetadata2Helper::RestoreMetadata2File() {
+ OriginProps originProps(WrapMovingNotNull(mDirectory));
+ QM_TRY(MOZ_TO_RESULT(originProps.Init(
+ [&self = *this](const auto& aSpec) { return *self.mPersistenceType; })));
+
+ QM_TRY(OkIf(originProps.mType != OriginProps::eInvalid), NS_ERROR_FAILURE);
+
+ originProps.mTimestamp = GetOriginLastModifiedTime(originProps);
+
+ mOriginProps.AppendElement(std::move(originProps));
+
+ QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectories()));
+
+ return NS_OK;
+}
+
+nsresult RestoreDirectoryMetadata2Helper::ProcessOriginDirectory(
+ const OriginProps& aOriginProps) {
+ AssertIsOnIOThread();
+
+ // We don't have any approach to restore aPersisted, so reset it to false.
+ QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ /* aPersisted */ false, aOriginProps.mOriginMetadata)));
+
+ return NS_OK;
+}
+
+} // namespace mozilla::dom::quota