summaryrefslogtreecommitdiffstats
path: root/dom/quota/ActorsParent.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/quota/ActorsParent.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/quota/ActorsParent.cpp')
-rw-r--r--dom/quota/ActorsParent.cpp11260
1 files changed, 11260 insertions, 0 deletions
diff --git a/dom/quota/ActorsParent.cpp b/dom/quota/ActorsParent.cpp
new file mode 100644
index 0000000000..eb0f8706fd
--- /dev/null
+++ b/dom/quota/ActorsParent.cpp
@@ -0,0 +1,11260 @@
+/* -*- 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 "OriginScope.h"
+#include "OriginInfo.h"
+#include "QuotaCommon.h"
+#include "QuotaManager.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/ProfilerLabels.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/FileSystemQuotaClient.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/Constants.h"
+#include "mozilla/dom/quota/DirectoryLock.h"
+#include "mozilla/dom/quota/PersistenceType.h"
+#include "mozilla/dom/quota/PQuota.h"
+#include "mozilla/dom/quota/PQuotaParent.h"
+#include "mozilla/dom/quota/PQuotaRequest.h"
+#include "mozilla/dom/quota/PQuotaRequestParent.h"
+#include "mozilla/dom/quota/PQuotaUsageRequest.h"
+#include "mozilla/dom/quota/PQuotaUsageRequestParent.h"
+#include "mozilla/dom/quota/QuotaManagerService.h"
+#include "mozilla/dom/quota/QuotaManagerImpl.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "mozilla/dom/quota/ScopedLogExtraInfo.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 "nsCharSeparatedTokenizer.h"
+#include "nsClassHashtable.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsTHashMap.h"
+#include "nsDebug.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsError.h"
+#include "nsHashKeys.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 "prthread.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 kChromeOrigin[] = "chrome";
+const char kAboutHomeOriginPrefix[] = "moz-safe-about:home";
+const char kIndexedDBOriginPrefix[] = "indexeddb://";
+const char kResourceOriginPrefix[] = "resource://";
+
+constexpr auto kStorageName = u"storage"_ns;
+constexpr auto kSQLiteSuffix = u".sqlite"_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 DEFAULT_PRIVATE_DIRECTORY_NAME u"private"
+
+// The name of the file that we use to load/save the last access time of an
+// origin.
+// XXX We should get rid of old metadata files at some point, bug 1343576.
+#define METADATA_FILE_NAME u".metadata"
+#define METADATA_TMP_FILE_NAME u".metadata-tmp"
+#define METADATA_V2_FILE_NAME u".metadata-v2"
+#define METADATA_V2_TMP_FILE_NAME u".metadata-v2-tmp"
+
+#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;
+};
+
+class OriginOperationBase : public BackgroundThreadObject, public Runnable {
+ protected:
+ nsresult mResultCode;
+
+ enum State {
+ // Not yet run.
+ State_Initial,
+
+ // Running on the owning thread in the listener for OpenDirectory.
+ State_DirectoryOpenPending,
+
+ // Running on the IO thread.
+ State_DirectoryWorkOpen,
+
+ // Running on the owning thread after all work is done.
+ State_UnblockingOpen,
+
+ // All done.
+ State_Complete
+ };
+
+ private:
+ State mState;
+ bool mActorDestroyed;
+
+ protected:
+ bool mNeedsStorageInit;
+
+ public:
+ void NoteActorDestroyed() {
+ AssertIsOnOwningThread();
+
+ mActorDestroyed = true;
+ }
+
+ bool IsActorDestroyed() const {
+ AssertIsOnOwningThread();
+
+ return mActorDestroyed;
+ }
+
+ protected:
+ explicit OriginOperationBase(nsISerialEventTarget* aOwningThread,
+ const char* aRunnableName)
+ : BackgroundThreadObject(aOwningThread),
+ Runnable(aRunnableName),
+ mResultCode(NS_OK),
+ mState(State_Initial),
+ mActorDestroyed(false),
+ mNeedsStorageInit(false) {}
+
+ // Reference counted.
+ virtual ~OriginOperationBase() {
+ MOZ_ASSERT(mState == State_Complete);
+ MOZ_ASSERT(mActorDestroyed);
+ }
+
+#ifdef DEBUG
+ State GetState() const { return mState; }
+#endif
+
+ void SetState(State aState) {
+ MOZ_ASSERT(mState == State_Initial);
+ mState = aState;
+ }
+
+ void AdvanceState() {
+ switch (mState) {
+ case State_Initial:
+ mState = State_DirectoryOpenPending;
+ return;
+ case State_DirectoryOpenPending:
+ mState = State_DirectoryWorkOpen;
+ return;
+ case State_DirectoryWorkOpen:
+ mState = State_UnblockingOpen;
+ return;
+ case State_UnblockingOpen:
+ mState = State_Complete;
+ return;
+ default:
+ MOZ_CRASH("Bad state!");
+ }
+ }
+
+ NS_IMETHOD
+ Run() override;
+
+ virtual nsresult DoInit(QuotaManager& aQuotaManager);
+
+ virtual void Open() = 0;
+
+#ifdef DEBUG
+ virtual nsresult DirectoryOpen();
+#else
+ nsresult DirectoryOpen();
+#endif
+
+ virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) = 0;
+
+ void Finish(nsresult aResult);
+
+ virtual void UnblockOpen() = 0;
+
+ private:
+ nsresult Init();
+
+ nsresult FinishInit();
+
+ nsresult DirectoryWork();
+};
+
+class FinalizeOriginEvictionOp : public OriginOperationBase {
+ nsTArray<RefPtr<OriginDirectoryLock>> mLocks;
+
+ public:
+ FinalizeOriginEvictionOp(nsISerialEventTarget* aBackgroundThread,
+ nsTArray<RefPtr<OriginDirectoryLock>>&& aLocks)
+ : OriginOperationBase(aBackgroundThread,
+ "dom::quota::FinalizeOriginEvictionOp"),
+ mLocks(std::move(aLocks)) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ void Dispatch();
+
+ void RunOnIOThreadImmediately();
+
+ private:
+ ~FinalizeOriginEvictionOp() = default;
+
+ virtual void Open() override;
+
+ virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ virtual void UnblockOpen() override;
+};
+
+class NormalOriginOperationBase
+ : public OriginOperationBase,
+ public OpenDirectoryListener,
+ public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
+ protected:
+ OriginScope mOriginScope;
+ RefPtr<DirectoryLock> mDirectoryLock;
+ Nullable<PersistenceType> mPersistenceType;
+ Nullable<Client::Type> mClientType;
+ mozilla::Atomic<bool> mCanceled;
+ const bool mExclusive;
+
+ public:
+ void RunImmediately() {
+ MOZ_ASSERT(GetState() == State_Initial);
+
+ MOZ_ALWAYS_SUCCEEDS(this->Run());
+ }
+
+ protected:
+ NormalOriginOperationBase(const char* aRunnableName,
+ const Nullable<PersistenceType>& aPersistenceType,
+ const OriginScope& aOriginScope,
+ const Nullable<Client::Type> aClientType,
+ bool aExclusive)
+ : OriginOperationBase(GetCurrentSerialEventTarget(), aRunnableName),
+ mOriginScope(aOriginScope),
+ mPersistenceType(aPersistenceType),
+ mClientType(aClientType),
+ mExclusive(aExclusive) {
+ AssertIsOnOwningThread();
+ }
+
+ ~NormalOriginOperationBase() = default;
+
+ virtual RefPtr<DirectoryLock> CreateDirectoryLock();
+
+ private:
+ // Need to declare refcounting unconditionally, because
+ // OpenDirectoryListener has pure-virtual refcounting.
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual void Open() override;
+
+ virtual void UnblockOpen() override;
+
+ // OpenDirectoryListener overrides.
+ virtual void DirectoryLockAcquired(DirectoryLock* aLock) override;
+
+ virtual void DirectoryLockFailed() override;
+
+ // Used to send results before unblocking open.
+ virtual void SendResults() = 0;
+};
+
+class SaveOriginAccessTimeOp : public NormalOriginOperationBase {
+ const OriginMetadata mOriginMetadata;
+ int64_t mTimestamp;
+
+ public:
+ SaveOriginAccessTimeOp(const OriginMetadata& aOriginMetadata,
+ int64_t aTimestamp)
+ : NormalOriginOperationBase(
+ "dom::quota::SaveOriginAccessTimeOp",
+ Nullable<PersistenceType>(aOriginMetadata.mPersistenceType),
+ OriginScope::FromOrigin(aOriginMetadata.mOrigin),
+ Nullable<Client::Type>(),
+ /* aExclusive */ false),
+ mOriginMetadata(aOriginMetadata),
+ mTimestamp(aTimestamp) {
+ AssertIsOnOwningThread();
+ }
+
+ private:
+ ~SaveOriginAccessTimeOp() = default;
+
+ virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ virtual void SendResults() override;
+};
+
+// XXX This class is a copy of ClearPrivateBrowsingOp because
+// ClearPrivateBrowsingOp is supposed to work as a parent actor. We could maybe
+// still inherit from ClearPrivateBrowsingOp instead of inheriting
+// NormalOriginOperationBase and override SendResults, but that's still not
+// very clean. It would be better to refactor the classes to have operations
+// which can be used independently from IPC and then have wrappers (actors)
+// around them for IPC.
+class ClearPrivateRepositoryOp : public NormalOriginOperationBase {
+ MozPromiseHolder<BoolPromise> mPromiseHolder;
+
+ public:
+ ClearPrivateRepositoryOp()
+ : NormalOriginOperationBase(
+ "dom::quota::ClearPrivateRepositoryOp",
+ Nullable<PersistenceType>(PERSISTENCE_TYPE_PRIVATE),
+ OriginScope::FromNull(), Nullable<Client::Type>(),
+ /* aExclusive */ true) {
+ AssertIsOnOwningThread();
+ }
+
+ RefPtr<BoolPromise> OnResults() {
+ AssertIsOnOwningThread();
+
+ return mPromiseHolder.Ensure(__func__);
+ }
+
+ private:
+ ~ClearPrivateRepositoryOp() = default;
+
+ nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ void SendResults() override;
+};
+
+class ShutdownStorageOp : public NormalOriginOperationBase {
+ MozPromiseHolder<BoolPromise> mPromiseHolder;
+
+ public:
+ ShutdownStorageOp()
+ : NormalOriginOperationBase(
+ "dom::quota::ShutdownStorageOp", Nullable<PersistenceType>(),
+ OriginScope::FromNull(), Nullable<Client::Type>(),
+ /* aExclusive */ true) {
+ AssertIsOnOwningThread();
+ }
+
+ RefPtr<BoolPromise> OnResults() {
+ AssertIsOnOwningThread();
+
+ return mPromiseHolder.Ensure(__func__);
+ }
+
+ private:
+ ~ShutdownStorageOp() = default;
+
+#ifdef DEBUG
+ nsresult DirectoryOpen() override;
+#endif
+
+ nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ void SendResults() override;
+};
+
+/*******************************************************************************
+ * Actor class declarations
+ ******************************************************************************/
+
+class Quota final : public PQuotaParent {
+#ifdef DEBUG
+ bool mActorDestroyed;
+#endif
+
+ public:
+ Quota();
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::quota::Quota)
+
+ private:
+ ~Quota();
+
+ bool VerifyRequestParams(const UsageRequestParams& aParams) const;
+
+ bool VerifyRequestParams(const RequestParams& aParams) const;
+
+ // IPDL methods.
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual PQuotaUsageRequestParent* AllocPQuotaUsageRequestParent(
+ const UsageRequestParams& aParams) override;
+
+ virtual mozilla::ipc::IPCResult RecvPQuotaUsageRequestConstructor(
+ PQuotaUsageRequestParent* aActor,
+ const UsageRequestParams& aParams) override;
+
+ virtual bool DeallocPQuotaUsageRequestParent(
+ PQuotaUsageRequestParent* aActor) override;
+
+ virtual PQuotaRequestParent* AllocPQuotaRequestParent(
+ const RequestParams& aParams) override;
+
+ virtual mozilla::ipc::IPCResult RecvPQuotaRequestConstructor(
+ PQuotaRequestParent* aActor, const RequestParams& aParams) override;
+
+ virtual bool DeallocPQuotaRequestParent(PQuotaRequestParent* aActor) override;
+
+ virtual mozilla::ipc::IPCResult RecvStartIdleMaintenance() override;
+
+ virtual mozilla::ipc::IPCResult RecvStopIdleMaintenance() override;
+
+ virtual mozilla::ipc::IPCResult RecvAbortOperationsForProcess(
+ const ContentParentId& aContentParentId) override;
+};
+
+class QuotaUsageRequestBase : public NormalOriginOperationBase,
+ public PQuotaUsageRequestParent {
+ public:
+ // May be overridden by subclasses if they need to perform work on the
+ // background thread before being run.
+ virtual void Init(Quota& aQuota);
+
+ protected:
+ QuotaUsageRequestBase(const char* aRunnableName)
+ : NormalOriginOperationBase(aRunnableName, Nullable<PersistenceType>(),
+ OriginScope::FromNull(),
+ Nullable<Client::Type>(),
+ /* aExclusive */ false) {}
+
+ mozilla::Result<UsageInfo, nsresult> GetUsageForOrigin(
+ QuotaManager& aQuotaManager, PersistenceType aPersistenceType,
+ const OriginMetadata& aOriginMetadata);
+
+ // Subclasses use this override to set the IPDL response value.
+ virtual void GetResponse(UsageRequestResponse& aResponse) = 0;
+
+ private:
+ mozilla::Result<UsageInfo, nsresult> GetUsageForOriginEntries(
+ QuotaManager& aQuotaManager, PersistenceType aPersistenceType,
+ const OriginMetadata& aOriginMetadata, nsIFile& aDirectory,
+ bool aInitialized);
+
+ void SendResults() override;
+
+ // IPDL methods.
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvCancel() final;
+};
+
+// A mix-in class to simplify operations that need to process every origin in
+// one or more repositories. Sub-classes should call TraverseRepository in their
+// DoDirectoryWork and implement a ProcessOrigin method for their per-origin
+// logic.
+class TraverseRepositoryHelper {
+ public:
+ TraverseRepositoryHelper() = default;
+
+ protected:
+ virtual ~TraverseRepositoryHelper() = default;
+
+ // If ProcessOrigin returns an error, TraverseRepository will immediately
+ // terminate and return the received error code to its caller.
+ nsresult TraverseRepository(QuotaManager& aQuotaManager,
+ PersistenceType aPersistenceType);
+
+ private:
+ virtual const Atomic<bool>& GetIsCanceledFlag() = 0;
+
+ virtual nsresult ProcessOrigin(QuotaManager& aQuotaManager,
+ nsIFile& aOriginDir, const bool aPersistent,
+ const PersistenceType aPersistenceType) = 0;
+};
+
+class GetUsageOp final : public QuotaUsageRequestBase,
+ public TraverseRepositoryHelper {
+ nsTArray<OriginUsage> mOriginUsages;
+ nsTHashMap<nsCStringHashKey, uint32_t> mOriginUsagesIndex;
+
+ bool mGetAll;
+
+ public:
+ explicit GetUsageOp(const UsageRequestParams& aParams);
+
+ private:
+ ~GetUsageOp() = default;
+
+ void ProcessOriginInternal(QuotaManager* aQuotaManager,
+ const PersistenceType aPersistenceType,
+ const nsACString& aOrigin,
+ const int64_t aTimestamp, const bool aPersisted,
+ const uint64_t aUsage);
+
+ nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ const Atomic<bool>& GetIsCanceledFlag() override;
+
+ nsresult ProcessOrigin(QuotaManager& aQuotaManager, nsIFile& aOriginDir,
+ const bool aPersistent,
+ const PersistenceType aPersistenceType) override;
+
+ void GetResponse(UsageRequestResponse& aResponse) override;
+};
+
+class GetOriginUsageOp final : public QuotaUsageRequestBase {
+ const OriginUsageParams mParams;
+ nsCString mSuffix;
+ nsCString mGroup;
+ nsCString mStorageOrigin;
+ uint64_t mUsage;
+ uint64_t mFileUsage;
+ bool mIsPrivate;
+ bool mFromMemory;
+
+ public:
+ explicit GetOriginUsageOp(const UsageRequestParams& aParams);
+
+ private:
+ ~GetOriginUsageOp() = default;
+
+ nsresult DoInit(QuotaManager& aQuotaManager) override;
+
+ RefPtr<DirectoryLock> CreateDirectoryLock() override;
+
+ virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ void GetResponse(UsageRequestResponse& aResponse) override;
+};
+
+class QuotaRequestBase : public NormalOriginOperationBase,
+ public PQuotaRequestParent {
+ public:
+ // May be overridden by subclasses if they need to perform work on the
+ // background thread before being run.
+ virtual void Init(Quota& aQuota);
+
+ protected:
+ explicit QuotaRequestBase(const char* aRunnableName, bool aExclusive)
+ : NormalOriginOperationBase(aRunnableName, Nullable<PersistenceType>(),
+ OriginScope::FromNull(),
+ Nullable<Client::Type>(), aExclusive) {}
+
+ QuotaRequestBase(const char* aRunnableName,
+ const Nullable<PersistenceType>& aPersistenceType,
+ const OriginScope& aOriginScope,
+ const Nullable<Client::Type>& aClientType, bool aExclusive)
+ : NormalOriginOperationBase(aRunnableName, aPersistenceType, aOriginScope,
+ aClientType, aExclusive) {}
+
+ // Subclasses use this override to set the IPDL response value.
+ virtual void GetResponse(RequestResponse& aResponse) = 0;
+
+ private:
+ virtual void SendResults() override;
+
+ // IPDL methods.
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+};
+
+class StorageNameOp final : public QuotaRequestBase {
+ nsString mName;
+
+ public:
+ StorageNameOp();
+
+ void Init(Quota& aQuota) override;
+
+ private:
+ ~StorageNameOp() = default;
+
+ RefPtr<DirectoryLock> CreateDirectoryLock() override;
+
+ nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ void GetResponse(RequestResponse& aResponse) override;
+};
+
+class InitializedRequestBase : public QuotaRequestBase {
+ protected:
+ bool mInitialized;
+
+ public:
+ void Init(Quota& aQuota) override;
+
+ protected:
+ InitializedRequestBase(const char* aRunnableName);
+
+ private:
+ RefPtr<DirectoryLock> CreateDirectoryLock() override;
+};
+
+class StorageInitializedOp final : public InitializedRequestBase {
+ public:
+ StorageInitializedOp()
+ : InitializedRequestBase("dom::quota::StorageInitializedOp") {}
+
+ private:
+ ~StorageInitializedOp() = default;
+
+ nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ void GetResponse(RequestResponse& aResponse) override;
+};
+
+class TemporaryStorageInitializedOp final : public InitializedRequestBase {
+ public:
+ TemporaryStorageInitializedOp()
+ : InitializedRequestBase("dom::quota::StorageInitializedOp") {}
+
+ private:
+ ~TemporaryStorageInitializedOp() = default;
+
+ nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ void GetResponse(RequestResponse& aResponse) override;
+};
+
+class InitOp final : public QuotaRequestBase {
+ public:
+ InitOp();
+
+ void Init(Quota& aQuota) override;
+
+ private:
+ ~InitOp() = default;
+
+ nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ void GetResponse(RequestResponse& aResponse) override;
+};
+
+class InitTemporaryStorageOp final : public QuotaRequestBase {
+ public:
+ InitTemporaryStorageOp();
+
+ void Init(Quota& aQuota) override;
+
+ private:
+ ~InitTemporaryStorageOp() = default;
+
+ nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ void GetResponse(RequestResponse& aResponse) override;
+};
+
+class InitializeOriginRequestBase : public QuotaRequestBase {
+ protected:
+ const PrincipalInfo mPrincipalInfo;
+ nsCString mSuffix;
+ nsCString mGroup;
+ nsCString mStorageOrigin;
+ bool mIsPrivate;
+ bool mCreated;
+
+ public:
+ void Init(Quota& aQuota) override;
+
+ protected:
+ InitializeOriginRequestBase(const char* aRunnableName,
+ PersistenceType aPersistenceType,
+ const PrincipalInfo& aPrincipalInfo);
+
+ nsresult DoInit(QuotaManager& aQuotaManager) override;
+};
+
+class InitializePersistentOriginOp final : public InitializeOriginRequestBase {
+ public:
+ explicit InitializePersistentOriginOp(const RequestParams& aParams);
+
+ private:
+ ~InitializePersistentOriginOp() = default;
+
+ nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ void GetResponse(RequestResponse& aResponse) override;
+};
+
+class InitializeTemporaryOriginOp final : public InitializeOriginRequestBase {
+ public:
+ explicit InitializeTemporaryOriginOp(const RequestParams& aParams);
+
+ private:
+ ~InitializeTemporaryOriginOp() = default;
+
+ nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ void GetResponse(RequestResponse& aResponse) override;
+};
+
+class GetFullOriginMetadataOp : public QuotaRequestBase {
+ const GetFullOriginMetadataParams mParams;
+ // XXX Consider wrapping with LazyInitializedOnce
+ OriginMetadata mOriginMetadata;
+ Maybe<FullOriginMetadata> mMaybeFullOriginMetadata;
+
+ public:
+ explicit GetFullOriginMetadataOp(const GetFullOriginMetadataParams& aParams);
+
+ private:
+ nsresult DoInit(QuotaManager& aQuotaManager) override;
+
+ RefPtr<DirectoryLock> CreateDirectoryLock() override;
+
+ nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ void GetResponse(RequestResponse& aResponse) override;
+};
+
+class ResetOrClearOp final : public QuotaRequestBase {
+ const bool mClear;
+
+ public:
+ explicit ResetOrClearOp(bool aClear);
+
+ void Init(Quota& aQuota) override;
+
+ private:
+ ~ResetOrClearOp() = default;
+
+ void DeleteFiles(QuotaManager& aQuotaManager);
+
+ void DeleteStorageFile(QuotaManager& aQuotaManager);
+
+ virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ virtual void GetResponse(RequestResponse& aResponse) override;
+};
+
+class ClearPrivateBrowsingOp final : public QuotaRequestBase {
+ public:
+ ClearPrivateBrowsingOp();
+
+ private:
+ ~ClearPrivateBrowsingOp() = default;
+
+ nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ void GetResponse(RequestResponse& aResponse) override;
+};
+
+class ClearRequestBase : public QuotaRequestBase {
+ protected:
+ explicit ClearRequestBase(const char* aRunnableName, bool aExclusive)
+ : QuotaRequestBase(aRunnableName, aExclusive) {
+ AssertIsOnOwningThread();
+ }
+
+ ClearRequestBase(const char* aRunnableName,
+ const Nullable<PersistenceType>& aPersistenceType,
+ const OriginScope& aOriginScope,
+ const Nullable<Client::Type>& aClientType, bool aExclusive)
+ : QuotaRequestBase(aRunnableName, aPersistenceType, aOriginScope,
+ aClientType, aExclusive) {}
+
+ void DeleteFiles(QuotaManager& aQuotaManager,
+ PersistenceType aPersistenceType);
+
+ nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+};
+
+class ClearOriginOp final : public ClearRequestBase {
+ const ClearResetOriginParams mParams;
+ const bool mMatchAll;
+
+ public:
+ explicit ClearOriginOp(const RequestParams& aParams);
+
+ void Init(Quota& aQuota) override;
+
+ private:
+ ~ClearOriginOp() = default;
+
+ void GetResponse(RequestResponse& aResponse) override;
+};
+
+class ClearDataOp final : public ClearRequestBase {
+ const ClearDataParams mParams;
+
+ public:
+ explicit ClearDataOp(const RequestParams& aParams);
+
+ void Init(Quota& aQuota) override;
+
+ private:
+ ~ClearDataOp() = default;
+
+ void GetResponse(RequestResponse& aResponse) override;
+};
+
+class ResetOriginOp final : public QuotaRequestBase {
+ public:
+ explicit ResetOriginOp(const RequestParams& aParams);
+
+ void Init(Quota& aQuota) override;
+
+ private:
+ ~ResetOriginOp() = default;
+
+ nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ void GetResponse(RequestResponse& aResponse) override;
+};
+
+class PersistRequestBase : public QuotaRequestBase {
+ const PrincipalInfo mPrincipalInfo;
+
+ protected:
+ nsCString mSuffix;
+ nsCString mGroup;
+ nsCString mStorageOrigin;
+ bool mIsPrivate;
+
+ public:
+ void Init(Quota& aQuota) override;
+
+ protected:
+ explicit PersistRequestBase(const PrincipalInfo& aPrincipalInfo);
+
+ nsresult DoInit(QuotaManager& aQuotaManager) override;
+};
+
+class PersistedOp final : public PersistRequestBase {
+ bool mPersisted;
+
+ public:
+ explicit PersistedOp(const RequestParams& aParams);
+
+ private:
+ ~PersistedOp() = default;
+
+ nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ void GetResponse(RequestResponse& aResponse) override;
+};
+
+class PersistOp final : public PersistRequestBase {
+ public:
+ explicit PersistOp(const RequestParams& aParams);
+
+ private:
+ ~PersistOp() = default;
+
+ nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ void GetResponse(RequestResponse& aResponse) override;
+};
+
+class EstimateOp final : public QuotaRequestBase {
+ const EstimateParams mParams;
+ OriginMetadata mOriginMetadata;
+ std::pair<uint64_t, uint64_t> mUsageAndLimit;
+
+ public:
+ explicit EstimateOp(const EstimateParams& aParams);
+
+ private:
+ ~EstimateOp() = default;
+
+ nsresult DoInit(QuotaManager& aQuotaManager) override;
+
+ RefPtr<DirectoryLock> CreateDirectoryLock() override;
+
+ virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ void GetResponse(RequestResponse& aResponse) override;
+};
+
+class ListOriginsOp final : public QuotaRequestBase,
+ public TraverseRepositoryHelper {
+ // XXX Bug 1521541 will make each origin has it's own state.
+ nsTArray<nsCString> mOrigins;
+
+ public:
+ ListOriginsOp();
+
+ void Init(Quota& aQuota) override;
+
+ private:
+ ~ListOriginsOp() = default;
+
+ nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
+
+ const Atomic<bool>& GetIsCanceledFlag() override;
+
+ nsresult ProcessOrigin(QuotaManager& aQuotaManager, nsIFile& aOriginDir,
+ const bool aPersistent,
+ const PersistenceType aPersistenceType) override;
+
+ void GetResponse(RequestResponse& aResponse) 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 RecordQuotaInfoLoadTimeHelper final : public Runnable {
+ // 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:
+ RecordQuotaInfoLoadTimeHelper()
+ : Runnable("dom::quota::RecordQuotaInfoLoadTimeHelper") {}
+
+ TimeStamp Start();
+
+ TimeStamp End();
+
+ private:
+ ~RecordQuotaInfoLoadTimeHelper() = default;
+
+ NS_DECL_NSIRUNNABLE
+};
+
+/*******************************************************************************
+ * Helper classes
+ ******************************************************************************/
+
+/*******************************************************************************
+ * Helper Functions
+ ******************************************************************************/
+
+inline bool IsDotFile(const nsAString& aFileName) {
+ return QuotaManager::IsDotFile(aFileName);
+}
+
+inline bool IsOSMetadata(const nsAString& aFileName) {
+ return QuotaManager::IsOSMetadata(aFileName);
+}
+
+bool IsOriginMetadata(const nsAString& aFileName) {
+ return aFileName.EqualsLiteral(METADATA_FILE_NAME) ||
+ aFileName.EqualsLiteral(METADATA_V2_FILE_NAME) ||
+ IsOSMetadata(aFileName);
+}
+
+bool IsTempMetadata(const nsAString& aFileName) {
+ return aFileName.EqualsLiteral(METADATA_TMP_FILE_NAME) ||
+ aFileName.EqualsLiteral(METADATA_V2_TMP_FILE_NAME);
+}
+
+// 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;
+}
+
+bool IsOnIOThread() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ NS_ASSERTION(quotaManager, "Must have a manager here!");
+
+ bool currentThread;
+ return NS_SUCCEEDED(
+ quotaManager->IOThread()->IsOnCurrentThread(&currentThread)) &&
+ currentThread;
+}
+
+void AssertIsOnIOThread() {
+ NS_ASSERTION(IsOnIOThread(), "Running on the wrong thread!");
+}
+
+void DiagnosticAssertIsOnIOThread() { MOZ_DIAGNOSTIC_ASSERT(IsOnIOThread()); }
+
+void AssertCurrentThreadOwnsQuotaMutex() {
+#ifdef DEBUG
+ QuotaManager* quotaManager = QuotaManager::Get();
+ NS_ASSERTION(quotaManager, "Must have a manager here!");
+
+ quotaManager->AssertCurrentThreadOwnsQuotaMutex();
+#endif
+}
+
+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;
+
+using NormalOriginOpArray =
+ nsTArray<CheckedUnsafePtr<NormalOriginOperationBase>>;
+StaticAutoPtr<NormalOriginOpArray> gNormalOriginOps;
+
+void RegisterNormalOriginOp(NormalOriginOperationBase& aNormalOriginOp) {
+ AssertIsOnBackgroundThread();
+
+ if (!gNormalOriginOps) {
+ gNormalOriginOps = new NormalOriginOpArray();
+ }
+
+ gNormalOriginOps->AppendElement(&aNormalOriginOp);
+}
+
+void UnregisterNormalOriginOp(NormalOriginOperationBase& aNormalOriginOp) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(gNormalOriginOps);
+
+ gNormalOriginOps->RemoveElement(&aNormalOriginOp);
+
+ if (gNormalOriginOps->IsEmpty()) {
+ gNormalOriginOps = nullptr;
+ }
+}
+
+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 MOZ_STACK_CLASS OriginParser final {
+ public:
+ enum ResultType { InvalidOrigin, ObsoleteOrigin, ValidOrigin };
+
+ private:
+ using Tokenizer =
+ nsCCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing>;
+
+ enum SchemeType { eNone, eFile, eAbout, eChrome };
+
+ enum State {
+ eExpectingAppIdOrScheme,
+ eExpectingInMozBrowser,
+ eExpectingScheme,
+ eExpectingEmptyToken1,
+ eExpectingEmptyToken2,
+ eExpectingEmptyTokenOrUniversalFileOrigin,
+ eExpectingHost,
+ eExpectingPort,
+ eExpectingEmptyTokenOrDriveLetterOrPathnameComponent,
+ eExpectingEmptyTokenOrPathnameComponent,
+ eExpectingEmptyToken1OrHost,
+
+ // We transit from eExpectingHost to this state when we encounter a host
+ // beginning with "[" which indicates an IPv6 literal. Because we mangle the
+ // IPv6 ":" delimiter to be a "+", we will receive separate tokens for each
+ // portion of the IPv6 address, including a final token that ends with "]".
+ // (Note that we do not mangle "[" or "]".) Note that the URL spec
+ // explicitly disclaims support for "<zone_id>" and so we don't have to deal
+ // with that.
+ eExpectingIPV6Token,
+ eComplete,
+ eHandledTrailingSeparator
+ };
+
+ const nsCString mOrigin;
+ Tokenizer mTokenizer;
+
+ nsCString mScheme;
+ nsCString mHost;
+ Nullable<uint32_t> mPort;
+ nsTArray<nsCString> mPathnameComponents;
+ nsCString mHandledTokens;
+
+ SchemeType mSchemeType;
+ State mState;
+ bool mInIsolatedMozBrowser;
+ bool mUniversalFileOrigin;
+ bool mMaybeDriveLetter;
+ bool mError;
+ bool mMaybeObsolete;
+
+ // Number of group which a IPv6 address has. Should be less than 9.
+ uint8_t mIPGroup;
+
+ public:
+ explicit OriginParser(const nsACString& aOrigin)
+ : mOrigin(aOrigin),
+ mTokenizer(aOrigin, '+'),
+ mPort(),
+ mSchemeType(eNone),
+ mState(eExpectingAppIdOrScheme),
+ mInIsolatedMozBrowser(false),
+ mUniversalFileOrigin(false),
+ mMaybeDriveLetter(false),
+ mError(false),
+ mMaybeObsolete(false),
+ mIPGroup(0) {}
+
+ static ResultType ParseOrigin(const nsACString& aOrigin, nsCString& aSpec,
+ OriginAttributes* aAttrs,
+ nsCString& aOriginalSuffix);
+
+ ResultType Parse(nsACString& aSpec);
+
+ private:
+ void HandleScheme(const nsDependentCSubstring& aToken);
+
+ void HandlePathnameComponent(const nsDependentCSubstring& aToken);
+
+ void HandleToken(const nsDependentCSubstring& aToken);
+
+ void HandleTrailingSeparator();
+};
+
+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;
+};
+
+auto MakeSanitizedOriginCString(const nsACString& aOrigin) {
+#ifdef XP_WIN
+ NS_ASSERTION(!strcmp(QuotaManager::kReplaceChars,
+ FILE_ILLEGAL_CHARACTERS FILE_PATH_SEPARATOR),
+ "Illegal file characters have changed!");
+#endif
+
+ nsAutoCString res{aOrigin};
+
+ res.ReplaceChar(QuotaManager::kReplaceChars, '+');
+
+ return res;
+}
+
+auto MakeSanitizedOriginString(const nsACString& aOrigin) {
+ // An origin string is ASCII-only, since it is obtained via
+ // nsIPrincipal::GetOrigin, which returns an ACString.
+ return NS_ConvertASCIItoUTF16(MakeSanitizedOriginCString(aOrigin));
+}
+
+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;
+}
+
+enum FileFlag { Truncate, Update, Append };
+
+Result<nsCOMPtr<nsIOutputStream>, nsresult> GetOutputStream(
+ nsIFile& aFile, FileFlag aFileFlag) {
+ AssertIsOnIOThread();
+
+ switch (aFileFlag) {
+ case FileFlag::Truncate:
+ QM_TRY_RETURN(NS_NewLocalFileOutputStream(&aFile));
+
+ case FileFlag::Update: {
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(&aFile, Exists));
+
+ if (!exists) {
+ return nsCOMPtr<nsIOutputStream>();
+ }
+
+ QM_TRY_INSPECT(const auto& stream,
+ NS_NewLocalFileRandomAccessStream(&aFile));
+
+ nsCOMPtr<nsIOutputStream> outputStream = do_QueryInterface(stream);
+ QM_TRY(OkIf(outputStream), Err(NS_ERROR_FAILURE));
+
+ return outputStream;
+ }
+
+ case FileFlag::Append:
+ QM_TRY_RETURN(NS_NewLocalFileOutputStream(
+ &aFile, PR_WRONLY | PR_CREATE_FILE | PR_APPEND));
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+}
+
+Result<nsCOMPtr<nsIBinaryOutputStream>, nsresult> GetBinaryOutputStream(
+ nsIFile& aFile, FileFlag aFileFlag) {
+ QM_TRY_UNWRAP(auto outputStream, GetOutputStream(aFile, aFileFlag));
+
+ QM_TRY(OkIf(outputStream), Err(NS_ERROR_UNEXPECTED));
+
+ return nsCOMPtr<nsIBinaryOutputStream>(
+ NS_NewObjectOutputStream(outputStream));
+}
+
+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('+');
+}
+
+nsresult 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;
+}
+
+nsresult 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;
+}
+
+Result<nsCOMPtr<nsIBinaryInputStream>, nsresult> GetBinaryInputStream(
+ nsIFile& aDirectory, const nsAString& aFilename) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCOMPtr<nsIFile>, aDirectory, Clone));
+
+ QM_TRY(MOZ_TO_RESULT(file->Append(aFilename)));
+
+ QM_TRY_UNWRAP(auto stream, NS_NewLocalFileInputStream(file));
+
+ QM_TRY_INSPECT(const auto& bufferedStream,
+ NS_NewBufferedInputStream(stream.forget(), 512));
+
+ QM_TRY(OkIf(bufferedStream), Err(NS_ERROR_FAILURE));
+
+ return nsCOMPtr<nsIBinaryInputStream>(
+ NS_NewObjectInputStream(bufferedStream));
+}
+
+// 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);
+
+#ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
+ ScopedLogExtraInfo::Initialize();
+#endif
+
+ 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
+}
+
+PQuotaParent* AllocPQuotaParent() {
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
+ return nullptr;
+ }
+
+ auto actor = MakeRefPtr<Quota>();
+
+ return actor.forget().take();
+}
+
+bool DeallocPQuotaParent(PQuotaParent* aActor) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ RefPtr<Quota> actor = dont_AddRef(static_cast<Quota*>(aActor));
+ return true;
+}
+
+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),
+ mTemporaryStorageInitialized(false),
+ mCacheUsable(false),
+ mShuttingDownStorage(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) {
+ gInstance->Shutdown();
+
+ 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 aFileName.EqualsLiteral(DSSTORE_FILE_NAME) ||
+ aFileName.EqualsLiteral(DESKTOP_FILE_NAME) ||
+ aFileName.LowerCaseEqualsLiteral(DESKTOP_INI_FILE_NAME) ||
+ aFileName.LowerCaseEqualsLiteral(THUMBS_DB_FILE_NAME);
+}
+
+// static
+bool QuotaManager::IsDotFile(const nsAString& aFileName) {
+ return aFileName.First() == char16_t('.');
+}
+
+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());
+
+ nsTArray<NotNull<DirectoryLockImpl*>>* array;
+ MOZ_ALWAYS_TRUE(directoryLockTable.Get(aLock.Origin(), &array));
+
+ MOZ_ALWAYS_TRUE(array->RemoveElement(&aLock));
+ if (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;
+}
+
+template <typename P>
+void QuotaManager::CollectPendingOriginsForListing(P aPredicate) {
+ 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_DEFAULT);
+ if (groupInfo) {
+ for (const auto& originInfo : groupInfo->mOriginInfos) {
+ if (!originInfo->mDirectoryExists) {
+ aPredicate(originInfo);
+ }
+ }
+ }
+ }
+}
+
+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(DEFAULT_PRIVATE_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 MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ for (const auto& op : *gNormalOriginOps) {
+ nsCString name;
+ op->GetName(name);
+ annotation.AppendPrintf("Op: %s pending\n", name.get());
+ }
+#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 = MakeRefPtr<SaveOriginAccessTimeOp>(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(!mTemporaryStorageInitialized);
+
+ // 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 recordQuotaInfoLoadTimeHelper =
+ MakeRefPtr<RecordQuotaInfoLoadTimeHelper>();
+
+ const auto startTime = recordQuotaInfoLoadTimeHelper->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_FAILURE));
+
+ QM_TRY_INSPECT(const bool& isDirectory,
+ MOZ_TO_RESULT_INVOKE_MEMBER(directory, IsDirectory));
+
+ QM_TRY(OkIf(isDirectory), Err(NS_ERROR_FAILURE));
+
+ // 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_TRY(OkIf(fullOriginMetadata.mLastAccessTime ==
+ metadata.mLastAccessTime),
+ Err(NS_ERROR_FAILURE));
+
+ 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 = recordQuotaInfoLoadTimeHelper->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(mTemporaryStorageInitialized);
+ 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_CRASH("Getting quota object for an unregistered directory lock?");
+}
+
+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;
+}
+
+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));
+
+ 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()};
+}
+
+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)
+ // EnsureStorageIsInitialized 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::AssertStorageIsInitialized() const {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(IsStorageInitialized());
+}
+
+#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{};
+}
+
+nsresult QuotaManager::EnsureStorageIsInitialized() {
+ 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<ClientDirectoryLock> QuotaManager::CreateDirectoryLock(
+ PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
+ Client::Type aClientType, bool aExclusive) {
+ AssertIsOnOwningThread();
+
+ return DirectoryLockImpl::Create(WrapNotNullUnchecked(this), aPersistenceType,
+ aOriginMetadata, aClientType, aExclusive);
+}
+
+RefPtr<UniversalDirectoryLock> QuotaManager::CreateDirectoryLockInternal(
+ const Nullable<PersistenceType>& aPersistenceType,
+ const OriginScope& aOriginScope, const Nullable<Client::Type>& aClientType,
+ bool aExclusive) {
+ AssertIsOnOwningThread();
+
+ return DirectoryLockImpl::CreateInternal(WrapNotNullUnchecked(this),
+ aPersistenceType, aOriginScope,
+ aClientType, aExclusive);
+}
+
+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);
+}
+
+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(mTemporaryStorageInitialized);
+
+ 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);
+}
+
+nsresult QuotaManager::EnsureTemporaryStorageIsInitialized() {
+ AssertIsOnIOThread();
+ MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
+
+ const auto innerFunc =
+ [&](const auto& firstInitializationAttempt) -> nsresult {
+ if (mTemporaryStorageInitialized) {
+ 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()));
+
+ mTemporaryStorageInitialized = true;
+
+ CleanupTemporaryStorage();
+
+ if (mCacheUsable) {
+ QM_TRY(InvalidateCache(*mStorageConnection));
+ }
+
+ return NS_OK;
+ };
+
+ return ExecuteInitialization(
+ Initialization::TemporaryStorage,
+ "dom::quota::FirstInitializationAttempt::TemporaryStorage"_ns, innerFunc);
+}
+
+RefPtr<BoolPromise> QuotaManager::ClearPrivateRepository() {
+ auto clearPrivateRepositoryOp = MakeRefPtr<ClearPrivateRepositoryOp>();
+
+ RegisterNormalOriginOp(*clearPrivateRepositoryOp);
+
+ clearPrivateRepositoryOp->RunImmediately();
+
+ return clearPrivateRepositoryOp->OnResults();
+}
+
+RefPtr<BoolPromise> QuotaManager::ShutdownStorage() {
+ if (!mShuttingDownStorage) {
+ mShuttingDownStorage = true;
+
+ auto shutdownStorageOp = MakeRefPtr<ShutdownStorageOp>();
+
+ RegisterNormalOriginOp(*shutdownStorageOp);
+
+ shutdownStorageOp->RunImmediately();
+
+ shutdownStorageOp->OnResults()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr<QuotaManager>(this)](bool aResolveValue) {
+ self->mShuttingDownStorage = false;
+
+ self->mShutdownStoragePromiseHolder.ResolveIfExists(aResolveValue,
+ __func__);
+ },
+ [self = RefPtr<QuotaManager>(this)](nsresult aRejectValue) {
+ self->mShuttingDownStorage = false;
+
+ self->mShutdownStoragePromiseHolder.RejectIfExists(aRejectValue,
+ __func__);
+ });
+ }
+
+ return mShutdownStoragePromiseHolder.Ensure(__func__);
+}
+
+void QuotaManager::ShutdownStorageInternal() {
+ AssertIsOnIOThread();
+
+ if (mStorageConnection) {
+ mInitializationInfo.ResetOriginInitializationInfos();
+ mInitializedOrigins.Clear();
+
+ if (mTemporaryStorageInitialized) {
+ if (mCacheUsable) {
+ UnloadQuota();
+ } else {
+ RemoveQuota();
+ }
+
+ mTemporaryStorageInitialized = 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(mTemporaryStorageInitialized);
+
+ 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 (StringBeginsWith(origin, kUUIDOriginScheme)) {
+ 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.
+
+ if (aOrigin.Equals(kChromeOrigin)) {
+ return PrincipalInfo{SystemPrincipalInfo{}};
+ }
+
+ ContentPrincipalInfo contentPrincipalInfo;
+
+ nsCString originalSuffix;
+ const OriginParser::ResultType result = OriginParser::ParseOrigin(
+ MakeSanitizedOriginCString(aOrigin), contentPrincipalInfo.spec(),
+ &contentPrincipalInfo.attrs(), originalSuffix);
+ QM_TRY(OkIf(result == OriginParser::ValidOrigin), Err(NS_ERROR_FAILURE));
+
+ return PrincipalInfo{std::move(contentPrincipalInfo)};
+}
+
+// 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) {
+ 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!");
+
+ RefPtr<FinalizeOriginEvictionOp> op =
+ new FinalizeOriginEvictionOp(mOwningThread, std::move(aLocks));
+
+ if (IsOnIOThread()) {
+ op->RunOnIOThreadImmediately();
+ } else {
+ op->Dispatch();
+ }
+}
+
+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!");
+ }
+}
+
+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;
+}
+
+/*******************************************************************************
+ * OriginOperationBase
+ ******************************************************************************/
+
+NS_IMETHODIMP
+OriginOperationBase::Run() {
+ nsresult rv;
+
+ switch (mState) {
+ case State_Initial: {
+ rv = Init();
+ break;
+ }
+
+ case State_DirectoryOpenPending: {
+ rv = DirectoryOpen();
+ break;
+ }
+
+ case State_DirectoryWorkOpen: {
+ rv = DirectoryWork();
+ break;
+ }
+
+ case State_UnblockingOpen: {
+ UnblockOpen();
+ return NS_OK;
+ }
+
+ default:
+ MOZ_CRASH("Bad state!");
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv)) && mState != State_UnblockingOpen) {
+ Finish(rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult OriginOperationBase::DoInit(QuotaManager& aQuotaManager) {
+ AssertIsOnOwningThread();
+
+ return NS_OK;
+}
+
+nsresult OriginOperationBase::DirectoryOpen() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State_DirectoryOpenPending);
+
+ QuotaManager* const quotaManager = QuotaManager::Get();
+ QM_TRY(OkIf(quotaManager), NS_ERROR_FAILURE);
+
+ // Must set this before dispatching otherwise we will race with the IO thread.
+ AdvanceState();
+
+ QM_TRY(MOZ_TO_RESULT(
+ quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)),
+ NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+void OriginOperationBase::Finish(nsresult aResult) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = aResult;
+ }
+
+ // Must set mState before dispatching otherwise we will race with the main
+ // thread.
+ mState = State_UnblockingOpen;
+
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+}
+
+nsresult OriginOperationBase::Init() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State_Initial);
+
+ if (QuotaManager::IsShuttingDown()) {
+ return NS_ERROR_ABORT;
+ }
+
+ QuotaManager* const quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ QM_TRY(MOZ_TO_RESULT(DoInit(*quotaManager)));
+
+ Open();
+
+ return NS_OK;
+}
+
+nsresult OriginOperationBase::DirectoryWork() {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(mState == State_DirectoryWorkOpen);
+
+ QuotaManager* const quotaManager = QuotaManager::Get();
+ QM_TRY(OkIf(quotaManager), NS_ERROR_FAILURE);
+
+ if (mNeedsStorageInit) {
+ QM_TRY(MOZ_TO_RESULT(quotaManager->EnsureStorageIsInitialized()));
+ }
+
+ QM_TRY(MOZ_TO_RESULT(DoDirectoryWork(*quotaManager)));
+
+ // Must set mState before dispatching otherwise we will race with the owning
+ // thread.
+ AdvanceState();
+
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+
+ return NS_OK;
+}
+
+void FinalizeOriginEvictionOp::Dispatch() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(GetState() == State_Initial);
+
+ SetState(State_DirectoryOpenPending);
+
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+}
+
+void FinalizeOriginEvictionOp::RunOnIOThreadImmediately() {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(GetState() == State_Initial);
+
+ SetState(State_DirectoryWorkOpen);
+
+ MOZ_ALWAYS_SUCCEEDS(this->Run());
+}
+
+void FinalizeOriginEvictionOp::Open() { MOZ_CRASH("Shouldn't get here!"); }
+
+nsresult FinalizeOriginEvictionOp::DoDirectoryWork(
+ QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("FinalizeOriginEvictionOp::DoDirectoryWork", OTHER);
+
+ for (const auto& lock : mLocks) {
+ aQuotaManager.OriginClearCompleted(
+ lock->GetPersistenceType(), lock->Origin(), Nullable<Client::Type>());
+ }
+
+ return NS_OK;
+}
+
+void FinalizeOriginEvictionOp::UnblockOpen() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(GetState() == State_UnblockingOpen);
+
+#ifdef DEBUG
+ NoteActorDestroyed();
+#endif
+
+ mLocks.Clear();
+
+ AdvanceState();
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(NormalOriginOperationBase, Runnable)
+
+RefPtr<DirectoryLock> NormalOriginOperationBase::CreateDirectoryLock() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(GetState() == State_DirectoryOpenPending);
+ MOZ_ASSERT(QuotaManager::Get());
+
+ return QuotaManager::Get()->CreateDirectoryLockInternal(
+ mPersistenceType, mOriginScope, mClientType, mExclusive);
+}
+
+void NormalOriginOperationBase::Open() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(GetState() == State_Initial);
+ MOZ_ASSERT(QuotaManager::Get());
+
+ AdvanceState();
+
+ RefPtr<DirectoryLock> directoryLock = CreateDirectoryLock();
+ if (directoryLock) {
+ directoryLock->Acquire(this);
+ } else {
+ QM_TRY(MOZ_TO_RESULT(DirectoryOpen()), QM_VOID,
+ [this](const nsresult rv) { Finish(rv); });
+ }
+}
+
+void NormalOriginOperationBase::UnblockOpen() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(GetState() == State_UnblockingOpen);
+
+ SendResults();
+
+ if (mDirectoryLock) {
+ mDirectoryLock = nullptr;
+ }
+
+ UnregisterNormalOriginOp(*this);
+
+ AdvanceState();
+}
+
+void NormalOriginOperationBase::DirectoryLockAcquired(DirectoryLock* aLock) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aLock);
+ MOZ_ASSERT(GetState() == State_DirectoryOpenPending);
+ MOZ_ASSERT(!mDirectoryLock);
+
+ mDirectoryLock = aLock;
+
+ QM_TRY(MOZ_TO_RESULT(DirectoryOpen()), QM_VOID,
+ [this](const nsresult rv) { Finish(rv); });
+}
+
+void NormalOriginOperationBase::DirectoryLockFailed() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(GetState() == State_DirectoryOpenPending);
+ MOZ_ASSERT(!mDirectoryLock);
+
+ Finish(NS_ERROR_FAILURE);
+}
+
+nsresult SaveOriginAccessTimeOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(!mPersistenceType.IsNull());
+ MOZ_ASSERT(mOriginScope.IsOrigin());
+
+ AUTO_PROFILER_LABEL("SaveOriginAccessTimeOp::DoDirectoryWork", OTHER);
+
+ QM_TRY_INSPECT(const auto& file,
+ aQuotaManager.GetOriginDirectory(mOriginMetadata));
+
+ // The origin directory might not exist
+ // anymore, because it was deleted by a clear operation.
+ QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE_MEMBER(file, Exists));
+
+ if (exists) {
+ QM_TRY(MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_V2_FILE_NAME))));
+
+ QM_TRY_INSPECT(const auto& stream,
+ GetBinaryOutputStream(*file, FileFlag::Update));
+ MOZ_ASSERT(stream);
+
+ QM_TRY(MOZ_TO_RESULT(stream->Write64(mTimestamp)));
+ }
+
+ return NS_OK;
+}
+
+void SaveOriginAccessTimeOp::SendResults() {
+#ifdef DEBUG
+ NoteActorDestroyed();
+#endif
+}
+
+nsresult ClearPrivateRepositoryOp::DoDirectoryWork(
+ QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(!mPersistenceType.IsNull());
+ MOZ_ASSERT(mPersistenceType.Value() == PERSISTENCE_TYPE_PRIVATE);
+
+ AUTO_PROFILER_LABEL("ClearPrivateRepositoryOp::DoDirectoryWork", OTHER);
+
+ QM_TRY_INSPECT(
+ const auto& directory,
+ QM_NewLocalFile(aQuotaManager.GetStoragePath(mPersistenceType.Value())));
+
+ 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...
+ MOZ_ASSERT(false, "Failed to remove directory!");
+ }
+
+ aQuotaManager.RemoveQuotaForRepository(mPersistenceType.Value());
+
+ aQuotaManager.RepositoryClearCompleted(mPersistenceType.Value());
+
+ return NS_OK;
+}
+
+void ClearPrivateRepositoryOp::SendResults() {
+#ifdef DEBUG
+ NoteActorDestroyed();
+#endif
+
+ if (NS_SUCCEEDED(mResultCode)) {
+ mPromiseHolder.ResolveIfExists(true, __func__);
+ } else {
+ mPromiseHolder.RejectIfExists(mResultCode, __func__);
+ }
+}
+
+#ifdef DEBUG
+nsresult ShutdownStorageOp::DirectoryOpen() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mDirectoryLock);
+ mDirectoryLock->AssertIsAcquiredExclusively();
+
+ return NormalOriginOperationBase::DirectoryOpen();
+}
+#endif
+
+nsresult ShutdownStorageOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("ShutdownStorageOp::DoDirectoryWork", OTHER);
+
+ aQuotaManager.ShutdownStorageInternal();
+
+ return NS_OK;
+}
+
+void ShutdownStorageOp::SendResults() {
+#ifdef DEBUG
+ NoteActorDestroyed();
+#endif
+
+ if (NS_SUCCEEDED(mResultCode)) {
+ mPromiseHolder.ResolveIfExists(true, __func__);
+ } else {
+ mPromiseHolder.RejectIfExists(mResultCode, __func__);
+ }
+}
+
+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 RecordQuotaInfoLoadTimeHelper::Start() {
+ AssertIsOnIOThread();
+
+ // 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 RecordQuotaInfoLoadTimeHelper::End() {
+ AssertIsOnIOThread();
+
+ mEndTime.init(TimeStamp::Now());
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+
+ return *mEndTime;
+}
+
+NS_IMETHODIMP
+RecordQuotaInfoLoadTimeHelper::Run() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mInitializedTime.isSome()) {
+ // Keys for QM_QUOTA_INFO_LOAD_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(Telemetry::QM_QUOTA_INFO_LOAD_TIME_V0, key,
+ *mStartTime, *mEndTime);
+
+ return NS_OK;
+ }
+
+ gLastOSWake = TimeStamp::Now();
+ mInitializedTime.init(gLastOSWake);
+
+ return NS_OK;
+}
+
+/*******************************************************************************
+ * Quota
+ ******************************************************************************/
+
+Quota::Quota()
+#ifdef DEBUG
+ : mActorDestroyed(false)
+#endif
+{
+}
+
+Quota::~Quota() { MOZ_ASSERT(mActorDestroyed); }
+
+bool Quota::VerifyRequestParams(const UsageRequestParams& aParams) const {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None);
+
+ switch (aParams.type()) {
+ case UsageRequestParams::TAllUsageParams:
+ break;
+
+ case UsageRequestParams::TOriginUsageParams: {
+ const OriginUsageParams& params = aParams.get_OriginUsageParams();
+
+ if (NS_WARN_IF(
+ !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ return true;
+}
+
+bool Quota::VerifyRequestParams(const RequestParams& aParams) const {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != RequestParams::T__None);
+
+ switch (aParams.type()) {
+ case RequestParams::TStorageNameParams:
+ case RequestParams::TStorageInitializedParams:
+ case RequestParams::TTemporaryStorageInitializedParams:
+ case RequestParams::TInitParams:
+ case RequestParams::TInitTemporaryStorageParams:
+ break;
+
+ case RequestParams::TInitializePersistentOriginParams: {
+ const InitializePersistentOriginParams& params =
+ aParams.get_InitializePersistentOriginParams();
+
+ if (NS_WARN_IF(
+ !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case RequestParams::TInitializeTemporaryOriginParams: {
+ const InitializeTemporaryOriginParams& params =
+ aParams.get_InitializeTemporaryOriginParams();
+
+ if (NS_WARN_IF(!IsBestEffortPersistenceType(params.persistenceType()))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(
+ !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case RequestParams::TGetFullOriginMetadataParams: {
+ const GetFullOriginMetadataParams& params =
+ aParams.get_GetFullOriginMetadataParams();
+ if (NS_WARN_IF(!IsBestEffortPersistenceType(params.persistenceType()))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(
+ !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case RequestParams::TClearOriginParams: {
+ const ClearResetOriginParams& params =
+ aParams.get_ClearOriginParams().commonParams();
+
+ if (NS_WARN_IF(
+ !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (params.persistenceTypeIsExplicit()) {
+ if (NS_WARN_IF(!IsValidPersistenceType(params.persistenceType()))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+ }
+
+ if (params.clientTypeIsExplicit()) {
+ if (NS_WARN_IF(!Client::IsValidType(params.clientType()))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+ }
+
+ break;
+ }
+
+ case RequestParams::TResetOriginParams: {
+ const ClearResetOriginParams& params =
+ aParams.get_ResetOriginParams().commonParams();
+
+ if (NS_WARN_IF(
+ !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (params.persistenceTypeIsExplicit()) {
+ if (NS_WARN_IF(!IsValidPersistenceType(params.persistenceType()))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+ }
+
+ if (params.clientTypeIsExplicit()) {
+ if (NS_WARN_IF(!Client::IsValidType(params.clientType()))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+ }
+
+ break;
+ }
+
+ case RequestParams::TClearDataParams: {
+ if (BackgroundParent::IsOtherProcessActor(Manager())) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case RequestParams::TClearPrivateBrowsingParams:
+ case RequestParams::TClearAllParams:
+ case RequestParams::TResetAllParams:
+ case RequestParams::TListOriginsParams:
+ break;
+
+ case RequestParams::TPersistedParams: {
+ const PersistedParams& params = aParams.get_PersistedParams();
+
+ if (NS_WARN_IF(
+ !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case RequestParams::TPersistParams: {
+ const PersistParams& params = aParams.get_PersistParams();
+
+ if (NS_WARN_IF(
+ !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case RequestParams::TEstimateParams: {
+ const EstimateParams& params = aParams.get_EstimateParams();
+
+ if (NS_WARN_IF(
+ !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ return true;
+}
+
+void Quota::ActorDestroy(ActorDestroyReason aWhy) {
+ AssertIsOnBackgroundThread();
+#ifdef DEBUG
+ MOZ_ASSERT(!mActorDestroyed);
+ mActorDestroyed = true;
+#endif
+}
+
+PQuotaUsageRequestParent* Quota::AllocPQuotaUsageRequestParent(
+ const UsageRequestParams& aParams) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None);
+
+ if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
+ return nullptr;
+ }
+
+#ifdef DEBUG
+ // Always verify parameters in DEBUG builds!
+ bool trustParams = false;
+#else
+ bool trustParams = !BackgroundParent::IsOtherProcessActor(Manager());
+#endif
+
+ if (!trustParams && NS_WARN_IF(!VerifyRequestParams(aParams))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ QM_TRY(QuotaManager::EnsureCreated(), nullptr);
+
+ auto actor = [&]() -> RefPtr<QuotaUsageRequestBase> {
+ switch (aParams.type()) {
+ case UsageRequestParams::TAllUsageParams:
+ return MakeRefPtr<GetUsageOp>(aParams);
+
+ case UsageRequestParams::TOriginUsageParams:
+ return MakeRefPtr<GetOriginUsageOp>(aParams);
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+ }();
+
+ MOZ_ASSERT(actor);
+
+ RegisterNormalOriginOp(*actor);
+
+ // Transfer ownership to IPDL.
+ return actor.forget().take();
+}
+
+mozilla::ipc::IPCResult Quota::RecvPQuotaUsageRequestConstructor(
+ PQuotaUsageRequestParent* aActor, const UsageRequestParams& aParams) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None);
+ MOZ_ASSERT(!QuotaManager::IsShuttingDown());
+
+ auto* op = static_cast<QuotaUsageRequestBase*>(aActor);
+
+ op->Init(*this);
+
+ op->RunImmediately();
+ return IPC_OK();
+}
+
+bool Quota::DeallocPQuotaUsageRequestParent(PQuotaUsageRequestParent* aActor) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ // Transfer ownership back from IPDL.
+ RefPtr<QuotaUsageRequestBase> actor =
+ dont_AddRef(static_cast<QuotaUsageRequestBase*>(aActor));
+ return true;
+}
+
+PQuotaRequestParent* Quota::AllocPQuotaRequestParent(
+ const RequestParams& aParams) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != RequestParams::T__None);
+
+ if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
+ return nullptr;
+ }
+
+#ifdef DEBUG
+ // Always verify parameters in DEBUG builds!
+ bool trustParams = false;
+#else
+ bool trustParams = !BackgroundParent::IsOtherProcessActor(Manager());
+#endif
+
+ if (!trustParams && NS_WARN_IF(!VerifyRequestParams(aParams))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ QM_TRY(QuotaManager::EnsureCreated(), nullptr);
+
+ auto actor = [&]() -> RefPtr<QuotaRequestBase> {
+ switch (aParams.type()) {
+ case RequestParams::TStorageNameParams:
+ return MakeRefPtr<StorageNameOp>();
+
+ case RequestParams::TStorageInitializedParams:
+ return MakeRefPtr<StorageInitializedOp>();
+
+ case RequestParams::TTemporaryStorageInitializedParams:
+ return MakeRefPtr<TemporaryStorageInitializedOp>();
+
+ case RequestParams::TInitParams:
+ return MakeRefPtr<InitOp>();
+
+ case RequestParams::TInitTemporaryStorageParams:
+ return MakeRefPtr<InitTemporaryStorageOp>();
+
+ case RequestParams::TInitializePersistentOriginParams:
+ return MakeRefPtr<InitializePersistentOriginOp>(aParams);
+
+ case RequestParams::TInitializeTemporaryOriginParams:
+ return MakeRefPtr<InitializeTemporaryOriginOp>(aParams);
+
+ case RequestParams::TGetFullOriginMetadataParams:
+ return MakeRefPtr<GetFullOriginMetadataOp>(
+ aParams.get_GetFullOriginMetadataParams());
+
+ case RequestParams::TClearOriginParams:
+ return MakeRefPtr<ClearOriginOp>(aParams);
+
+ case RequestParams::TResetOriginParams:
+ return MakeRefPtr<ResetOriginOp>(aParams);
+
+ case RequestParams::TClearDataParams:
+ return MakeRefPtr<ClearDataOp>(aParams);
+
+ case RequestParams::TClearPrivateBrowsingParams:
+ return MakeRefPtr<ClearPrivateBrowsingOp>();
+
+ case RequestParams::TClearAllParams:
+ return MakeRefPtr<ResetOrClearOp>(/* aClear */ true);
+
+ case RequestParams::TResetAllParams:
+ return MakeRefPtr<ResetOrClearOp>(/* aClear */ false);
+
+ case RequestParams::TPersistedParams:
+ return MakeRefPtr<PersistedOp>(aParams);
+
+ case RequestParams::TPersistParams:
+ return MakeRefPtr<PersistOp>(aParams);
+
+ case RequestParams::TEstimateParams:
+ return MakeRefPtr<EstimateOp>(aParams.get_EstimateParams());
+
+ case RequestParams::TListOriginsParams:
+ return MakeRefPtr<ListOriginsOp>();
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+ }();
+
+ MOZ_ASSERT(actor);
+
+ RegisterNormalOriginOp(*actor);
+
+ // Transfer ownership to IPDL.
+ return actor.forget().take();
+}
+
+mozilla::ipc::IPCResult Quota::RecvPQuotaRequestConstructor(
+ PQuotaRequestParent* aActor, const RequestParams& aParams) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aParams.type() != RequestParams::T__None);
+ MOZ_ASSERT(!QuotaManager::IsShuttingDown());
+
+ auto* op = static_cast<QuotaRequestBase*>(aActor);
+
+ op->Init(*this);
+
+ op->RunImmediately();
+ return IPC_OK();
+}
+
+bool Quota::DeallocPQuotaRequestParent(PQuotaRequestParent* aActor) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ // Transfer ownership back from IPDL.
+ RefPtr<QuotaRequestBase> actor =
+ dont_AddRef(static_cast<QuotaRequestBase*>(aActor));
+ return true;
+}
+
+mozilla::ipc::IPCResult Quota::RecvStartIdleMaintenance() {
+ AssertIsOnBackgroundThread();
+
+ PBackgroundParent* actor = Manager();
+ MOZ_ASSERT(actor);
+
+ if (BackgroundParent::IsOtherProcessActor(actor)) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return IPC_FAIL(this, "Wrong actor");
+ }
+
+ if (QuotaManager::IsShuttingDown()) {
+ return IPC_OK();
+ }
+
+ QM_TRY(QuotaManager::EnsureCreated(), IPC_OK());
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ quotaManager->StartIdleMaintenance();
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult Quota::RecvStopIdleMaintenance() {
+ AssertIsOnBackgroundThread();
+
+ PBackgroundParent* actor = Manager();
+ MOZ_ASSERT(actor);
+
+ if (BackgroundParent::IsOtherProcessActor(actor)) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return IPC_FAIL(this, "Wrong actor");
+ }
+
+ if (QuotaManager::IsShuttingDown()) {
+ return IPC_OK();
+ }
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ if (!quotaManager) {
+ return IPC_OK();
+ }
+
+ quotaManager->StopIdleMaintenance();
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult Quota::RecvAbortOperationsForProcess(
+ const ContentParentId& aContentParentId) {
+ AssertIsOnBackgroundThread();
+
+ PBackgroundParent* actor = Manager();
+ MOZ_ASSERT(actor);
+
+ if (BackgroundParent::IsOtherProcessActor(actor)) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return IPC_FAIL(this, "Wrong actor");
+ }
+
+ if (QuotaManager::IsShuttingDown()) {
+ return IPC_OK();
+ }
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ if (!quotaManager) {
+ return IPC_OK();
+ }
+
+ quotaManager->AbortOperationsForProcess(aContentParentId);
+
+ return IPC_OK();
+}
+
+void QuotaUsageRequestBase::Init(Quota& aQuota) {
+ AssertIsOnOwningThread();
+
+ mNeedsStorageInit = true;
+}
+
+Result<UsageInfo, nsresult> QuotaUsageRequestBase::GetUsageForOrigin(
+ QuotaManager& aQuotaManager, PersistenceType aPersistenceType,
+ const OriginMetadata& aOriginMetadata) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType);
+
+ QM_TRY_INSPECT(const auto& directory,
+ aQuotaManager.GetOriginDirectory(aOriginMetadata));
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
+
+ if (!exists || mCanceled) {
+ return UsageInfo();
+ }
+
+ // If the directory exists then enumerate all the files inside, adding up
+ // the sizes to get the final usage statistic.
+ bool initialized;
+
+ if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
+ initialized = aQuotaManager.IsOriginInitialized(aOriginMetadata.mOrigin);
+ } else {
+ initialized = aQuotaManager.IsTemporaryStorageInitialized();
+ }
+
+ return GetUsageForOriginEntries(aQuotaManager, aPersistenceType,
+ aOriginMetadata, *directory, initialized);
+}
+
+Result<UsageInfo, nsresult> QuotaUsageRequestBase::GetUsageForOriginEntries(
+ QuotaManager& aQuotaManager, PersistenceType aPersistenceType,
+ const OriginMetadata& aOriginMetadata, nsIFile& aDirectory,
+ const bool aInitialized) {
+ AssertIsOnIOThread();
+
+ QM_TRY_RETURN((ReduceEachFileAtomicCancelable(
+ aDirectory, mCanceled, UsageInfo{},
+ [&](UsageInfo oldUsageInfo, const nsCOMPtr<nsIFile>& file)
+ -> mozilla::Result<UsageInfo, 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 getting usage for an origin (even
+ // for an uninitialized origin) are now allowed. Just warn if we
+ // find them.
+ UNKNOWN_FILE_WARNING(leafName);
+ break;
+ }
+
+ Client* const client = aQuotaManager.GetClient(clientType);
+ MOZ_ASSERT(client);
+
+ QM_TRY_INSPECT(
+ const auto& usageInfo,
+ aInitialized ? client->GetUsageForOrigin(
+ aPersistenceType, aOriginMetadata, mCanceled)
+ : client->InitOrigin(aPersistenceType,
+ aOriginMetadata, mCanceled));
+ return oldUsageInfo + usageInfo;
+ }
+
+ case nsIFileKind::ExistsAsFile:
+ // We are maintaining existing behavior for unknown files here (just
+ // continuing).
+ // This can possibly be used by developers to add temporary backups
+ // into origin directories without losing get usage functionality.
+ if (IsTempMetadata(leafName)) {
+ if (!aInitialized) {
+ QM_TRY(MOZ_TO_RESULT(file->Remove(/* recursive */ false)));
+ }
+
+ break;
+ }
+
+ if (IsOriginMetadata(leafName) || IsOSMetadata(leafName) ||
+ IsDotFile(leafName)) {
+ break;
+ }
+
+ // Unknown files during getting usage for an origin (even for an
+ // uninitialized origin) 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 oldUsageInfo;
+ })));
+}
+
+void QuotaUsageRequestBase::SendResults() {
+ AssertIsOnOwningThread();
+
+ if (IsActorDestroyed()) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = NS_ERROR_FAILURE;
+ }
+ } else {
+ if (mCanceled) {
+ mResultCode = NS_ERROR_FAILURE;
+ }
+
+ UsageRequestResponse response;
+
+ if (NS_SUCCEEDED(mResultCode)) {
+ GetResponse(response);
+ } else {
+ response = mResultCode;
+ }
+
+ Unused << PQuotaUsageRequestParent::Send__delete__(this, response);
+ }
+}
+
+void QuotaUsageRequestBase::ActorDestroy(ActorDestroyReason aWhy) {
+ AssertIsOnOwningThread();
+
+ NoteActorDestroyed();
+}
+
+mozilla::ipc::IPCResult QuotaUsageRequestBase::RecvCancel() {
+ AssertIsOnOwningThread();
+
+ if (mCanceled.exchange(true)) {
+ NS_WARNING("Canceled more than once?!");
+ return IPC_FAIL(this, "Request canceled more than once");
+ }
+
+ return IPC_OK();
+}
+
+nsresult TraverseRepositoryHelper::TraverseRepository(
+ QuotaManager& aQuotaManager, PersistenceType aPersistenceType) {
+ AssertIsOnIOThread();
+
+ QM_TRY_INSPECT(
+ const auto& directory,
+ QM_NewLocalFile(aQuotaManager.GetStoragePath(aPersistenceType)));
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
+
+ if (!exists) {
+ return NS_OK;
+ }
+
+ QM_TRY(CollectEachFileAtomicCancelable(
+ *directory, GetIsCanceledFlag(),
+ [this, aPersistenceType, &aQuotaManager,
+ persistent = aPersistenceType == PERSISTENCE_TYPE_PERSISTENT](
+ const nsCOMPtr<nsIFile>& originDir) -> Result<Ok, nsresult> {
+ QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*originDir));
+
+ switch (dirEntryKind) {
+ case nsIFileKind::ExistsAsDirectory:
+ QM_TRY(MOZ_TO_RESULT(ProcessOrigin(aQuotaManager, *originDir,
+ persistent, aPersistenceType)));
+ break;
+
+ case nsIFileKind::ExistsAsFile: {
+ QM_TRY_INSPECT(const auto& leafName,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsAutoString, originDir, GetLeafName));
+
+ // Unknown files during getting usages 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;
+}
+
+GetUsageOp::GetUsageOp(const UsageRequestParams& aParams)
+ : QuotaUsageRequestBase("dom::quota::GetUsageOp"),
+ mGetAll(aParams.get_AllUsageParams().getAll()) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aParams.type() == UsageRequestParams::TAllUsageParams);
+}
+
+void GetUsageOp::ProcessOriginInternal(QuotaManager* aQuotaManager,
+ const PersistenceType aPersistenceType,
+ const nsACString& aOrigin,
+ const int64_t aTimestamp,
+ const bool aPersisted,
+ const uint64_t aUsage) {
+ if (!mGetAll && aQuotaManager->IsOriginInternal(aOrigin)) {
+ return;
+ }
+
+ // We can't store pointers to OriginUsage objects in the hashtable
+ // since AppendElement() reallocates its internal array buffer as number
+ // of elements grows.
+ const auto& originUsage =
+ mOriginUsagesIndex.WithEntryHandle(aOrigin, [&](auto&& entry) {
+ if (entry) {
+ return WrapNotNullUnchecked(&mOriginUsages[entry.Data()]);
+ }
+
+ entry.Insert(mOriginUsages.Length());
+
+ return mOriginUsages.EmplaceBack(nsCString{aOrigin}, false, 0, 0);
+ });
+
+ if (aPersistenceType == PERSISTENCE_TYPE_DEFAULT) {
+ originUsage->persisted() = aPersisted;
+ }
+
+ originUsage->usage() = originUsage->usage() + aUsage;
+
+ originUsage->lastAccessed() =
+ std::max<int64_t>(originUsage->lastAccessed(), aTimestamp);
+}
+
+const Atomic<bool>& GetUsageOp::GetIsCanceledFlag() {
+ AssertIsOnIOThread();
+
+ return mCanceled;
+}
+
+// XXX Remove aPersistent
+// XXX Remove aPersistenceType once GetUsageForOrigin uses the persistence
+// type from OriginMetadata
+nsresult GetUsageOp::ProcessOrigin(QuotaManager& aQuotaManager,
+ nsIFile& aOriginDir, const bool aPersistent,
+ const PersistenceType aPersistenceType) {
+ AssertIsOnIOThread();
+
+ QM_TRY_UNWRAP(auto maybeMetadata,
+ QM_OR_ELSE_WARN_IF(
+ // Expression
+ aQuotaManager.LoadFullOriginMetadataWithRestore(&aOriginDir)
+ .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 getting usage are allowed. Just warn if we
+ // find them.
+ QM_TRY_INSPECT(const auto& leafName,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, aOriginDir,
+ GetLeafName));
+
+ UNKNOWN_FILE_WARNING(leafName);
+ return NS_OK;
+ }
+
+ auto metadata = maybeMetadata.extract();
+
+ QM_TRY_INSPECT(const auto& usageInfo,
+ GetUsageForOrigin(aQuotaManager, aPersistenceType, metadata));
+
+ ProcessOriginInternal(&aQuotaManager, aPersistenceType, metadata.mOrigin,
+ metadata.mLastAccessTime, metadata.mPersisted,
+ usageInfo.TotalUsage().valueOr(0));
+
+ return NS_OK;
+}
+
+nsresult GetUsageOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+ aQuotaManager.AssertStorageIsInitialized();
+
+ AUTO_PROFILER_LABEL("GetUsageOp::DoDirectoryWork", OTHER);
+
+ nsresult rv;
+
+ for (const PersistenceType type : kAllPersistenceTypes) {
+ rv = TraverseRepository(aQuotaManager, type);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // TraverseRepository above only consulted the filesystem. We also need to
+ // consider origins which may have pending quota usage, such as buffered
+ // LocalStorage writes for an origin which didn't previously have any
+ // LocalStorage data.
+
+ aQuotaManager.CollectPendingOriginsForListing(
+ [this, &aQuotaManager](const auto& originInfo) {
+ ProcessOriginInternal(
+ &aQuotaManager, originInfo->GetGroupInfo()->GetPersistenceType(),
+ originInfo->Origin(), originInfo->LockedAccessTime(),
+ originInfo->LockedPersisted(), originInfo->LockedUsage());
+ });
+
+ return NS_OK;
+}
+
+void GetUsageOp::GetResponse(UsageRequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ aResponse = AllUsageResponse();
+
+ aResponse.get_AllUsageResponse().originUsages() = std::move(mOriginUsages);
+}
+
+GetOriginUsageOp::GetOriginUsageOp(const UsageRequestParams& aParams)
+ : QuotaUsageRequestBase("dom::quota::GetOriginUsageOp"),
+ mParams(aParams.get_OriginUsageParams()),
+ mUsage(0),
+ mFileUsage(0) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aParams.type() == UsageRequestParams::TOriginUsageParams);
+
+ // Overwrite OriginOperationBase default values.
+ mNeedsStorageInit = true;
+
+ // Overwrite GetOriginUsageOp default values.
+ mFromMemory = mParams.fromMemory();
+}
+
+nsresult GetOriginUsageOp::DoInit(QuotaManager& aQuotaManager) {
+ AssertIsOnOwningThread();
+
+ QM_TRY_UNWRAP(
+ PrincipalMetadata principalMetadata,
+ aQuotaManager.GetInfoFromValidatedPrincipalInfo(mParams.principalInfo()));
+
+ principalMetadata.AssertInvariants();
+
+ mSuffix = std::move(principalMetadata.mSuffix);
+ mGroup = std::move(principalMetadata.mGroup);
+ mOriginScope.SetFromOrigin(principalMetadata.mOrigin);
+ mStorageOrigin = std::move(principalMetadata.mStorageOrigin);
+ mIsPrivate = principalMetadata.mIsPrivate;
+
+ return NS_OK;
+}
+
+RefPtr<DirectoryLock> GetOriginUsageOp::CreateDirectoryLock() {
+ if (mFromMemory) {
+ return nullptr;
+ }
+
+ return QuotaUsageRequestBase::CreateDirectoryLock();
+}
+
+nsresult GetOriginUsageOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+ aQuotaManager.AssertStorageIsInitialized();
+ MOZ_ASSERT(mUsage == 0);
+ MOZ_ASSERT(mFileUsage == 0);
+
+ AUTO_PROFILER_LABEL("GetOriginUsageOp::DoDirectoryWork", OTHER);
+
+ if (mFromMemory) {
+ const PrincipalMetadata principalMetadata = {
+ mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()}, mStorageOrigin,
+ mIsPrivate};
+
+ // Ensure temporary storage is initialized. If temporary storage hasn't been
+ // initialized yet, the method will initialize it by traversing the
+ // repositories for temporary and default storage (including our origin).
+ QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureTemporaryStorageIsInitialized()));
+
+ // Get cached usage (the method doesn't have to stat any files). File usage
+ // is not tracked in memory separately, so just add to the total usage.
+ mUsage = aQuotaManager.GetOriginUsage(principalMetadata);
+
+ return NS_OK;
+ }
+
+ UsageInfo usageInfo;
+
+ // Add all the persistent/temporary/default storage files we care about.
+ for (const PersistenceType type : kAllPersistenceTypes) {
+ const OriginMetadata originMetadata = {
+ mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()},
+ mStorageOrigin, mIsPrivate, type};
+
+ auto usageInfoOrErr =
+ GetUsageForOrigin(aQuotaManager, type, originMetadata);
+ if (NS_WARN_IF(usageInfoOrErr.isErr())) {
+ return usageInfoOrErr.unwrapErr();
+ }
+
+ usageInfo += usageInfoOrErr.unwrap();
+ }
+
+ mUsage = usageInfo.TotalUsage().valueOr(0);
+ mFileUsage = usageInfo.FileUsage().valueOr(0);
+
+ return NS_OK;
+}
+
+void GetOriginUsageOp::GetResponse(UsageRequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ OriginUsageResponse usageResponse;
+
+ usageResponse.usage() = mUsage;
+ usageResponse.fileUsage() = mFileUsage;
+
+ aResponse = usageResponse;
+}
+
+void QuotaRequestBase::Init(Quota& aQuota) {
+ AssertIsOnOwningThread();
+
+ mNeedsStorageInit = true;
+}
+
+void QuotaRequestBase::SendResults() {
+ AssertIsOnOwningThread();
+
+ if (IsActorDestroyed()) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = NS_ERROR_FAILURE;
+ }
+ } else {
+ RequestResponse response;
+
+ if (NS_SUCCEEDED(mResultCode)) {
+ GetResponse(response);
+ } else {
+ response = mResultCode;
+ }
+
+ Unused << PQuotaRequestParent::Send__delete__(this, response);
+ }
+}
+
+void QuotaRequestBase::ActorDestroy(ActorDestroyReason aWhy) {
+ AssertIsOnOwningThread();
+
+ NoteActorDestroyed();
+}
+
+StorageNameOp::StorageNameOp()
+ : QuotaRequestBase("dom::quota::StorageNameOp", /* aExclusive */ false) {
+ AssertIsOnOwningThread();
+
+ // Overwrite OriginOperationBase default values.
+ mNeedsStorageInit = false;
+}
+
+void StorageNameOp::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
+
+RefPtr<DirectoryLock> StorageNameOp::CreateDirectoryLock() { return nullptr; }
+
+nsresult StorageNameOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("StorageNameOp::DoDirectoryWork", OTHER);
+
+ mName = aQuotaManager.GetStorageName();
+
+ return NS_OK;
+}
+
+void StorageNameOp::GetResponse(RequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ StorageNameResponse storageNameResponse;
+
+ storageNameResponse.name() = mName;
+
+ aResponse = storageNameResponse;
+}
+
+InitializedRequestBase::InitializedRequestBase(const char* aRunnableName)
+ : QuotaRequestBase(aRunnableName, /* aExclusive */ false),
+ mInitialized(false) {
+ AssertIsOnOwningThread();
+
+ // Overwrite OriginOperationBase default values.
+ mNeedsStorageInit = false;
+}
+
+void InitializedRequestBase::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
+
+RefPtr<DirectoryLock> InitializedRequestBase::CreateDirectoryLock() {
+ return nullptr;
+}
+
+nsresult StorageInitializedOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("StorageInitializedOp::DoDirectoryWork", OTHER);
+
+ mInitialized = aQuotaManager.IsStorageInitialized();
+
+ return NS_OK;
+}
+
+void StorageInitializedOp::GetResponse(RequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ StorageInitializedResponse storageInitializedResponse;
+
+ storageInitializedResponse.initialized() = mInitialized;
+
+ aResponse = storageInitializedResponse;
+}
+
+nsresult TemporaryStorageInitializedOp::DoDirectoryWork(
+ QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("TemporaryStorageInitializedOp::DoDirectoryWork", OTHER);
+
+ mInitialized = aQuotaManager.IsTemporaryStorageInitialized();
+
+ return NS_OK;
+}
+
+void TemporaryStorageInitializedOp::GetResponse(RequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ TemporaryStorageInitializedResponse temporaryStorageInitializedResponse;
+
+ temporaryStorageInitializedResponse.initialized() = mInitialized;
+
+ aResponse = temporaryStorageInitializedResponse;
+}
+
+InitOp::InitOp()
+ : QuotaRequestBase("dom::quota::InitOp", /* aExclusive */ false) {
+ AssertIsOnOwningThread();
+
+ // Overwrite OriginOperationBase default values.
+ mNeedsStorageInit = false;
+}
+
+void InitOp::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
+
+nsresult InitOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("InitOp::DoDirectoryWork", OTHER);
+
+ QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureStorageIsInitialized()));
+
+ return NS_OK;
+}
+
+void InitOp::GetResponse(RequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ aResponse = InitResponse();
+}
+
+InitTemporaryStorageOp::InitTemporaryStorageOp()
+ : QuotaRequestBase("dom::quota::InitTemporaryStorageOp",
+ /* aExclusive */ false) {
+ AssertIsOnOwningThread();
+
+ // Overwrite OriginOperationBase default values.
+ mNeedsStorageInit = false;
+}
+
+void InitTemporaryStorageOp::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
+
+nsresult InitTemporaryStorageOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("InitTemporaryStorageOp::DoDirectoryWork", OTHER);
+
+ QM_TRY(OkIf(aQuotaManager.IsStorageInitialized()), NS_ERROR_FAILURE);
+
+ QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureTemporaryStorageIsInitialized()));
+
+ return NS_OK;
+}
+
+void InitTemporaryStorageOp::GetResponse(RequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ aResponse = InitTemporaryStorageResponse();
+}
+
+InitializeOriginRequestBase::InitializeOriginRequestBase(
+ const char* aRunnableName, const PersistenceType aPersistenceType,
+ const PrincipalInfo& aPrincipalInfo)
+ : QuotaRequestBase(aRunnableName,
+ /* aExclusive */ false),
+ mPrincipalInfo(aPrincipalInfo),
+ mCreated(false) {
+ AssertIsOnOwningThread();
+
+ // Overwrite OriginOperationBase default values.
+ mNeedsStorageInit = false;
+
+ // Overwrite NormalOriginOperationBase default values.
+ mPersistenceType.SetValue(aPersistenceType);
+}
+
+void InitializeOriginRequestBase::Init(Quota& aQuota) {
+ AssertIsOnOwningThread();
+}
+
+nsresult InitializeOriginRequestBase::DoInit(QuotaManager& aQuotaManager) {
+ AssertIsOnOwningThread();
+
+ QM_TRY_UNWRAP(
+ auto principalMetadata,
+ aQuotaManager.GetInfoFromValidatedPrincipalInfo(mPrincipalInfo));
+
+ principalMetadata.AssertInvariants();
+
+ mSuffix = std::move(principalMetadata.mSuffix);
+ mGroup = std::move(principalMetadata.mGroup);
+ mOriginScope.SetFromOrigin(principalMetadata.mOrigin);
+ mStorageOrigin = std::move(principalMetadata.mStorageOrigin);
+ mIsPrivate = principalMetadata.mIsPrivate;
+
+ return NS_OK;
+}
+
+InitializePersistentOriginOp::InitializePersistentOriginOp(
+ const RequestParams& aParams)
+ : InitializeOriginRequestBase(
+ "dom::quota::InitializePersistentOriginOp",
+ PERSISTENCE_TYPE_PERSISTENT,
+ aParams.get_InitializePersistentOriginParams().principalInfo()) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aParams.type() ==
+ RequestParams::TInitializePersistentOriginParams);
+}
+
+nsresult InitializePersistentOriginOp::DoDirectoryWork(
+ QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(!mPersistenceType.IsNull());
+
+ AUTO_PROFILER_LABEL("InitializePersistentOriginOp::DoDirectoryWork", OTHER);
+
+ QM_TRY(OkIf(aQuotaManager.IsStorageInitialized()), NS_ERROR_FAILURE);
+
+ QM_TRY_UNWRAP(
+ mCreated,
+ (aQuotaManager
+ .EnsurePersistentOriginIsInitialized(OriginMetadata{
+ mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()},
+ mStorageOrigin, mIsPrivate, PERSISTENCE_TYPE_PERSISTENT})
+ .map([](const auto& res) { return res.second; })));
+
+ return NS_OK;
+}
+
+void InitializePersistentOriginOp::GetResponse(RequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ aResponse = InitializePersistentOriginResponse(mCreated);
+}
+
+InitializeTemporaryOriginOp::InitializeTemporaryOriginOp(
+ const RequestParams& aParams)
+ : InitializeOriginRequestBase(
+ "dom::quota::InitializeTemporaryOriginOp",
+ aParams.get_InitializeTemporaryOriginParams().persistenceType(),
+ aParams.get_InitializeTemporaryOriginParams().principalInfo()) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aParams.type() == RequestParams::TInitializeTemporaryOriginParams);
+}
+
+nsresult InitializeTemporaryOriginOp::DoDirectoryWork(
+ QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(!mPersistenceType.IsNull());
+
+ AUTO_PROFILER_LABEL("InitializeTemporaryOriginOp::DoDirectoryWork", OTHER);
+
+ QM_TRY(OkIf(aQuotaManager.IsStorageInitialized()), NS_ERROR_FAILURE);
+
+ QM_TRY(OkIf(aQuotaManager.IsTemporaryStorageInitialized()), NS_ERROR_FAILURE);
+
+ QM_TRY_UNWRAP(
+ mCreated,
+ (aQuotaManager
+ .EnsureTemporaryOriginIsInitialized(
+ mPersistenceType.Value(),
+ OriginMetadata{
+ mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()},
+ mStorageOrigin, mIsPrivate, mPersistenceType.Value()})
+ .map([](const auto& res) { return res.second; })));
+
+ return NS_OK;
+}
+
+void InitializeTemporaryOriginOp::GetResponse(RequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ aResponse = InitializeTemporaryOriginResponse(mCreated);
+}
+
+GetFullOriginMetadataOp::GetFullOriginMetadataOp(
+ const GetFullOriginMetadataParams& aParams)
+ : QuotaRequestBase("dom::quota::GetFullOriginMetadataOp",
+ /* aExclusive */ false),
+ mParams(aParams) {
+ AssertIsOnOwningThread();
+}
+
+nsresult GetFullOriginMetadataOp::DoInit(QuotaManager& aQuotaManager) {
+ AssertIsOnOwningThread();
+
+ QM_TRY_UNWRAP(
+ PrincipalMetadata principalMetadata,
+ aQuotaManager.GetInfoFromValidatedPrincipalInfo(mParams.principalInfo()));
+
+ principalMetadata.AssertInvariants();
+
+ mOriginMetadata = {std::move(principalMetadata), mParams.persistenceType()};
+
+ return NS_OK;
+}
+
+RefPtr<DirectoryLock> GetFullOriginMetadataOp::CreateDirectoryLock() {
+ return nullptr;
+}
+
+nsresult GetFullOriginMetadataOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("GetFullOriginMetadataOp::DoDirectoryWork", OTHER);
+
+ // Ensure temporary storage is initialized. If temporary storage hasn't
+ // been initialized yet, the method will initialize it by traversing the
+ // repositories for temporary and default storage (including our origin).
+ QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureTemporaryStorageIsInitialized()));
+
+ // Get metadata cached in memory (the method doesn't have to stat any
+ // files).
+ mMaybeFullOriginMetadata =
+ aQuotaManager.GetFullOriginMetadata(mOriginMetadata);
+
+ return NS_OK;
+}
+
+void GetFullOriginMetadataOp::GetResponse(RequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ aResponse = GetFullOriginMetadataResponse();
+ aResponse.get_GetFullOriginMetadataResponse().maybeFullOriginMetadata() =
+ std::move(mMaybeFullOriginMetadata);
+}
+
+ResetOrClearOp::ResetOrClearOp(bool aClear)
+ : QuotaRequestBase("dom::quota::ResetOrClearOp", /* aExclusive */ true),
+ mClear(aClear) {
+ AssertIsOnOwningThread();
+
+ // Overwrite OriginOperationBase default values.
+ mNeedsStorageInit = false;
+}
+
+void ResetOrClearOp::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
+
+void ResetOrClearOp::DeleteFiles(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+
+ nsresult rv = aQuotaManager.AboutToClearOrigins(Nullable<PersistenceType>(),
+ OriginScope::FromNull(),
+ Nullable<Client::Type>());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ auto directoryOrErr = QM_NewLocalFile(aQuotaManager.GetStoragePath());
+ if (NS_WARN_IF(directoryOrErr.isErr())) {
+ return;
+ }
+
+ nsCOMPtr<nsIFile> directory = directoryOrErr.unwrap();
+
+ 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...
+ MOZ_ASSERT(false, "Failed to remove storage directory!");
+ }
+}
+
+void ResetOrClearOp::DeleteStorageFile(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+
+ QM_TRY_INSPECT(const auto& storageFile,
+ QM_NewLocalFile(aQuotaManager.GetBasePath()), QM_VOID);
+
+ QM_TRY(MOZ_TO_RESULT(storageFile->Append(aQuotaManager.GetStorageName() +
+ kSQLiteSuffix)),
+ QM_VOID);
+
+ const nsresult rv = storageFile->Remove(true);
+ if (rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
+ // This should never fail if we've closed the storage connection
+ // correctly...
+ MOZ_ASSERT(false, "Failed to remove storage file!");
+ }
+}
+
+nsresult ResetOrClearOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("ResetOrClearOp::DoDirectoryWork", OTHER);
+
+ if (mClear) {
+ DeleteFiles(aQuotaManager);
+
+ aQuotaManager.RemoveQuota();
+ }
+
+ aQuotaManager.ShutdownStorageInternal();
+
+ if (mClear) {
+ DeleteStorageFile(aQuotaManager);
+ }
+
+ return NS_OK;
+}
+
+void ResetOrClearOp::GetResponse(RequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+ if (mClear) {
+ aResponse = ClearAllResponse();
+ } else {
+ aResponse = ResetAllResponse();
+ }
+}
+
+ClearPrivateBrowsingOp::ClearPrivateBrowsingOp()
+ : QuotaRequestBase("dom::quota::ClearPrivateBrowsingOp",
+ Nullable<PersistenceType>(PERSISTENCE_TYPE_PRIVATE),
+ OriginScope::FromNull(), Nullable<Client::Type>(),
+ /* aExclusive */ true) {
+ AssertIsOnOwningThread();
+}
+
+nsresult ClearPrivateBrowsingOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(!mPersistenceType.IsNull());
+ MOZ_ASSERT(mPersistenceType.Value() == PERSISTENCE_TYPE_PRIVATE);
+
+ AUTO_PROFILER_LABEL("ClearPrivateBrowsingOp::DoDirectoryWork", OTHER);
+
+ QM_TRY_INSPECT(
+ const auto& directory,
+ QM_NewLocalFile(aQuotaManager.GetStoragePath(mPersistenceType.Value())));
+
+ 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...
+ MOZ_ASSERT(false, "Failed to remove directory!");
+ }
+
+ aQuotaManager.RemoveQuotaForRepository(mPersistenceType.Value());
+
+ aQuotaManager.RepositoryClearCompleted(mPersistenceType.Value());
+
+ return NS_OK;
+}
+
+void ClearPrivateBrowsingOp::GetResponse(RequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ aResponse = ClearPrivateBrowsingResponse();
+}
+
+static Result<nsCOMPtr<nsIFile>, QMResult> OpenToBeRemovedDirectory(
+ const nsAString& aStoragePath) {
+ QM_TRY_INSPECT(const auto& dir,
+ QM_TO_RESULT_TRANSFORM(QM_NewLocalFile(aStoragePath)));
+ QM_TRY(QM_TO_RESULT(dir->Append(u"to-be-removed"_ns)));
+
+ nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) {
+ return dir;
+ }
+ return Err(QMResult(rv));
+}
+
+static Result<Ok, QMResult> RemoveOrMoveToDir(nsIFile& aFile,
+ nsIFile* aMoveTargetDir) {
+ if (!aMoveTargetDir) {
+ QM_TRY(QM_TO_RESULT(aFile.Remove(true)));
+ return Ok();
+ }
+
+ nsIDToCString uuid(nsID::GenerateUUID());
+ NS_ConvertUTF8toUTF16 subDirName(uuid.get(), NSID_LENGTH - 1);
+ QM_TRY(QM_TO_RESULT(aFile.MoveTo(aMoveTargetDir, subDirName)));
+ return Ok();
+}
+
+void ClearRequestBase::DeleteFiles(QuotaManager& aQuotaManager,
+ PersistenceType aPersistenceType) {
+ AssertIsOnIOThread();
+
+ QM_TRY(MOZ_TO_RESULT(aQuotaManager.AboutToClearOrigins(
+ Nullable<PersistenceType>(aPersistenceType), mOriginScope,
+ mClientType)),
+ QM_VOID);
+
+ QM_TRY_INSPECT(
+ const auto& directory,
+ QM_NewLocalFile(aQuotaManager.GetStoragePath(aPersistenceType)), QM_VOID);
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists), QM_VOID);
+
+ if (!exists) {
+ return;
+ }
+
+ nsTArray<nsCOMPtr<nsIFile>> directoriesForRemovalRetry;
+
+ aQuotaManager.MaybeRecordQuotaManagerShutdownStep(
+ "ClearRequestBase: Starting deleting files"_ns);
+ nsCOMPtr<nsIFile> toBeRemovedDir;
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownTeardown)) {
+ QM_WARNONLY_TRY_UNWRAP(
+ auto result, OpenToBeRemovedDirectory(aQuotaManager.GetStoragePath()));
+ toBeRemovedDir = result.valueOr(nullptr);
+ }
+ QM_TRY(
+ CollectEachFile(
+ *directory,
+ [&originScope = mOriginScope, aPersistenceType, &aQuotaManager,
+ &directoriesForRemovalRetry, &toBeRemovedDir,
+ this](nsCOMPtr<nsIFile>&& file) -> mozilla::Result<Ok, nsresult> {
+ QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
+
+ QM_TRY_INSPECT(const auto& leafName,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file,
+ GetLeafName));
+
+ switch (dirEntryKind) {
+ case nsIFileKind::ExistsAsDirectory: {
+ QM_TRY_UNWRAP(
+ auto maybeMetadata,
+ QM_OR_ELSE_WARN_IF(
+ // Expression
+ aQuotaManager.GetOriginMetadata(file).map(
+ [](auto metadata) -> Maybe<OriginMetadata> {
+ return Some(std::move(metadata));
+ }),
+ // Predicate.
+ IsSpecificError<NS_ERROR_MALFORMED_URI>,
+ // Fallback.
+ ErrToDefaultOk<Maybe<OriginMetadata>>));
+
+ if (!maybeMetadata) {
+ // Unknown directories during clearing are allowed. Just warn
+ // if we find them.
+ UNKNOWN_FILE_WARNING(leafName);
+ break;
+ }
+
+ auto metadata = maybeMetadata.extract();
+
+ MOZ_ASSERT(metadata.mPersistenceType == aPersistenceType);
+
+ // Skip the origin directory if it doesn't match the pattern.
+ if (!originScope.Matches(
+ OriginScope::FromOrigin(metadata.mOrigin))) {
+ break;
+ }
+
+ if (!mClientType.IsNull()) {
+ nsAutoString clientDirectoryName;
+ QM_TRY(
+ OkIf(Client::TypeToText(mClientType.Value(),
+ clientDirectoryName, fallible)),
+ Err(NS_ERROR_FAILURE));
+
+ QM_TRY(MOZ_TO_RESULT(file->Append(clientDirectoryName)));
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(file, Exists));
+
+ if (!exists) {
+ break;
+ }
+ }
+
+ // We can't guarantee that this will always succeed on
+ // Windows...
+ QM_WARNONLY_TRY(
+ RemoveOrMoveToDir(*file, toBeRemovedDir), [&](const auto&) {
+ directoriesForRemovalRetry.AppendElement(std::move(file));
+ });
+
+ const bool initialized =
+ aPersistenceType == PERSISTENCE_TYPE_PERSISTENT
+ ? aQuotaManager.IsOriginInitialized(metadata.mOrigin)
+ : aQuotaManager.IsTemporaryStorageInitialized();
+
+ // If it hasn't been initialized, we don't need to update the
+ // quota and notify the removing client.
+ if (!initialized) {
+ break;
+ }
+
+ if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) {
+ if (mClientType.IsNull()) {
+ aQuotaManager.RemoveQuotaForOrigin(aPersistenceType,
+ metadata);
+ } else {
+ aQuotaManager.ResetUsageForClient(
+ ClientMetadata{metadata, mClientType.Value()});
+ }
+ }
+
+ aQuotaManager.OriginClearCompleted(
+ aPersistenceType, metadata.mOrigin, mClientType);
+
+ break;
+ }
+
+ case nsIFileKind::ExistsAsFile: {
+ // Unknown files during clearing 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{};
+ }),
+ QM_VOID);
+
+ // Retry removing any directories that failed to be removed earlier now.
+ //
+ // XXX This will still block this operation. We might instead dispatch a
+ // runnable to our own thread for each retry round with a timer. We must
+ // ensure that the directory lock is upheld until we complete or give up
+ // though.
+ for (uint32_t index = 0; index < 10; index++) {
+ aQuotaManager.MaybeRecordQuotaManagerShutdownStepWith([index]() {
+ return nsPrintfCString(
+ "ClearRequestBase: Starting repeated directory removal #%d", index);
+ });
+
+ for (auto&& file : std::exchange(directoriesForRemovalRetry,
+ nsTArray<nsCOMPtr<nsIFile>>{})) {
+ QM_WARNONLY_TRY(
+ QM_TO_RESULT(file->Remove(true)),
+ ([&directoriesForRemovalRetry, &file](const auto&) {
+ directoriesForRemovalRetry.AppendElement(std::move(file));
+ }));
+ }
+
+ aQuotaManager.MaybeRecordQuotaManagerShutdownStepWith([index]() {
+ return nsPrintfCString(
+ "ClearRequestBase: Completed repeated directory removal #%d", index);
+ });
+
+ if (directoriesForRemovalRetry.IsEmpty()) {
+ break;
+ }
+
+ aQuotaManager.MaybeRecordQuotaManagerShutdownStepWith([index]() {
+ return nsPrintfCString("ClearRequestBase: Before sleep #%d", index);
+ });
+
+ PR_Sleep(PR_MillisecondsToInterval(200));
+
+ aQuotaManager.MaybeRecordQuotaManagerShutdownStepWith([index]() {
+ return nsPrintfCString("ClearRequestBase: After sleep #%d", index);
+ });
+ }
+
+ QM_WARNONLY_TRY(OkIf(directoriesForRemovalRetry.IsEmpty()));
+
+ aQuotaManager.MaybeRecordQuotaManagerShutdownStep(
+ "ClearRequestBase: Completed deleting files"_ns);
+}
+
+nsresult ClearRequestBase::DoDirectoryWork(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+ aQuotaManager.AssertStorageIsInitialized();
+
+ AUTO_PROFILER_LABEL("ClearRequestBase::DoDirectoryWork", OTHER);
+
+ if (mPersistenceType.IsNull()) {
+ for (const PersistenceType type : kAllPersistenceTypes) {
+ DeleteFiles(aQuotaManager, type);
+ }
+ } else {
+ DeleteFiles(aQuotaManager, mPersistenceType.Value());
+ }
+
+ return NS_OK;
+}
+
+ClearOriginOp::ClearOriginOp(const RequestParams& aParams)
+ : ClearRequestBase("dom::quota::ClearOriginOp", /* aExclusive */ true),
+ mParams(aParams.get_ClearOriginParams().commonParams()),
+ mMatchAll(aParams.get_ClearOriginParams().matchAll()) {
+ MOZ_ASSERT(aParams.type() == RequestParams::TClearOriginParams);
+}
+
+void ClearOriginOp::Init(Quota& aQuota) {
+ AssertIsOnOwningThread();
+
+ QuotaRequestBase::Init(aQuota);
+
+ if (mParams.persistenceTypeIsExplicit()) {
+ mPersistenceType.SetValue(mParams.persistenceType());
+ }
+
+ // Figure out which origin we're dealing with.
+ const auto origin = QuotaManager::GetOriginFromValidatedPrincipalInfo(
+ mParams.principalInfo());
+
+ if (mMatchAll) {
+ mOriginScope.SetFromPrefix(origin);
+ } else {
+ mOriginScope.SetFromOrigin(origin);
+ }
+
+ if (mParams.clientTypeIsExplicit()) {
+ mClientType.SetValue(mParams.clientType());
+ }
+}
+
+void ClearOriginOp::GetResponse(RequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ aResponse = ClearOriginResponse();
+}
+
+ClearDataOp::ClearDataOp(const RequestParams& aParams)
+ : ClearRequestBase("dom::quota::ClearDataOp", /* aExclusive */ true),
+ mParams(aParams) {
+ MOZ_ASSERT(aParams.type() == RequestParams::TClearDataParams);
+}
+
+void ClearDataOp::Init(Quota& aQuota) {
+ AssertIsOnOwningThread();
+
+ QuotaRequestBase::Init(aQuota);
+
+ mOriginScope.SetFromPattern(mParams.pattern());
+}
+
+void ClearDataOp::GetResponse(RequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ aResponse = ClearDataResponse();
+}
+
+ResetOriginOp::ResetOriginOp(const RequestParams& aParams)
+ : QuotaRequestBase("dom::quota::ResetOriginOp", /* aExclusive */ true) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aParams.type() == RequestParams::TResetOriginParams);
+
+ const ClearResetOriginParams& params =
+ aParams.get_ResetOriginParams().commonParams();
+
+ const auto origin =
+ QuotaManager::GetOriginFromValidatedPrincipalInfo(params.principalInfo());
+
+ // Overwrite OriginOperationBase default values.
+ mNeedsStorageInit = false;
+
+ // Overwrite NormalOriginOperationBase default values.
+ if (params.persistenceTypeIsExplicit()) {
+ mPersistenceType.SetValue(params.persistenceType());
+ }
+
+ mOriginScope.SetFromOrigin(origin);
+
+ if (params.clientTypeIsExplicit()) {
+ mClientType.SetValue(params.clientType());
+ }
+}
+
+void ResetOriginOp::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
+
+nsresult ResetOriginOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("ResetOriginOp::DoDirectoryWork", OTHER);
+
+ // All the work is handled by NormalOriginOperationBase parent class. In this
+ // particular case, we just needed to acquire an exclusive directory lock and
+ // that's it.
+
+ return NS_OK;
+}
+
+void ResetOriginOp::GetResponse(RequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ aResponse = ResetOriginResponse();
+}
+
+PersistRequestBase::PersistRequestBase(const PrincipalInfo& aPrincipalInfo)
+ : QuotaRequestBase("dom::quota::PersistRequestBase",
+ /* aExclusive */ false),
+ mPrincipalInfo(aPrincipalInfo) {
+ AssertIsOnOwningThread();
+}
+
+void PersistRequestBase::Init(Quota& aQuota) {
+ AssertIsOnOwningThread();
+
+ QuotaRequestBase::Init(aQuota);
+
+ mPersistenceType.SetValue(PERSISTENCE_TYPE_DEFAULT);
+}
+
+nsresult PersistRequestBase::DoInit(QuotaManager& aQuotaManager) {
+ AssertIsOnOwningThread();
+
+ // Figure out which origin we're dealing with.
+ QM_TRY_UNWRAP(
+ PrincipalMetadata principalMetadata,
+ aQuotaManager.GetInfoFromValidatedPrincipalInfo(mPrincipalInfo));
+
+ principalMetadata.AssertInvariants();
+
+ mSuffix = std::move(principalMetadata.mSuffix);
+ mGroup = std::move(principalMetadata.mGroup);
+ mOriginScope.SetFromOrigin(principalMetadata.mOrigin);
+ mStorageOrigin = std::move(principalMetadata.mStorageOrigin);
+ mIsPrivate = principalMetadata.mIsPrivate;
+
+ return NS_OK;
+}
+
+PersistedOp::PersistedOp(const RequestParams& aParams)
+ : PersistRequestBase(aParams.get_PersistedParams().principalInfo()),
+ mPersisted(false) {
+ MOZ_ASSERT(aParams.type() == RequestParams::TPersistedParams);
+}
+
+nsresult PersistedOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+ aQuotaManager.AssertStorageIsInitialized();
+ MOZ_ASSERT(!mPersistenceType.IsNull());
+ MOZ_ASSERT(mPersistenceType.Value() == PERSISTENCE_TYPE_DEFAULT);
+ MOZ_ASSERT(mOriginScope.IsOrigin());
+
+ AUTO_PROFILER_LABEL("PersistedOp::DoDirectoryWork", OTHER);
+
+ const OriginMetadata originMetadata = {
+ mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()},
+ mStorageOrigin, mIsPrivate, mPersistenceType.Value()};
+
+ Nullable<bool> persisted = aQuotaManager.OriginPersisted(originMetadata);
+
+ if (!persisted.IsNull()) {
+ mPersisted = persisted.Value();
+ return NS_OK;
+ }
+
+ // If we get here, it means the origin hasn't been initialized yet.
+ // Try to get the persisted flag from directory metadata on disk.
+
+ QM_TRY_INSPECT(const auto& directory,
+ aQuotaManager.GetOriginDirectory(originMetadata));
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
+
+ if (exists) {
+ // Get the metadata. We only use the persisted flag.
+ QM_TRY_INSPECT(const auto& metadata,
+ aQuotaManager.LoadFullOriginMetadataWithRestore(directory));
+
+ mPersisted = metadata.mPersisted;
+ } else {
+ // The directory has not been created yet.
+ mPersisted = false;
+ }
+
+ return NS_OK;
+}
+
+void PersistedOp::GetResponse(RequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ PersistedResponse persistedResponse;
+ persistedResponse.persisted() = mPersisted;
+
+ aResponse = persistedResponse;
+}
+
+PersistOp::PersistOp(const RequestParams& aParams)
+ : PersistRequestBase(aParams.get_PersistParams().principalInfo()) {
+ MOZ_ASSERT(aParams.type() == RequestParams::TPersistParams);
+}
+
+nsresult PersistOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+ aQuotaManager.AssertStorageIsInitialized();
+ MOZ_ASSERT(!mPersistenceType.IsNull());
+ MOZ_ASSERT(mPersistenceType.Value() == PERSISTENCE_TYPE_DEFAULT);
+ MOZ_ASSERT(mOriginScope.IsOrigin());
+
+ const OriginMetadata originMetadata = {
+ mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()},
+ mStorageOrigin, mIsPrivate, mPersistenceType.Value()};
+
+ AUTO_PROFILER_LABEL("PersistOp::DoDirectoryWork", OTHER);
+
+ // Update directory metadata on disk first. Then, create/update the originInfo
+ // if needed.
+ QM_TRY_INSPECT(const auto& directory,
+ aQuotaManager.GetOriginDirectory(originMetadata));
+
+ QM_TRY_INSPECT(const bool& created,
+ aQuotaManager.EnsureOriginDirectory(*directory));
+
+ if (created) {
+ int64_t timestamp;
+
+ // Origin directory has been successfully created.
+ // Create OriginInfo too if temporary storage was already initialized.
+ if (aQuotaManager.IsTemporaryStorageInitialized()) {
+ timestamp = aQuotaManager.NoteOriginDirectoryCreated(
+ originMetadata, /* aPersisted */ true);
+ } else {
+ timestamp = PR_Now();
+ }
+
+ QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(*directory, timestamp,
+ /* aPersisted */ true,
+ originMetadata)));
+ } else {
+ // Get the metadata (restore the metadata file if necessary). We only use
+ // the persisted flag.
+ QM_TRY_INSPECT(const auto& metadata,
+ aQuotaManager.LoadFullOriginMetadataWithRestore(directory));
+
+ if (!metadata.mPersisted) {
+ QM_TRY_INSPECT(const auto& file,
+ CloneFileAndAppend(
+ *directory, nsLiteralString(METADATA_V2_FILE_NAME)));
+
+ QM_TRY_INSPECT(const auto& stream,
+ GetBinaryOutputStream(*file, FileFlag::Update));
+
+ MOZ_ASSERT(stream);
+
+ // Update origin access time while we are here.
+ QM_TRY(MOZ_TO_RESULT(stream->Write64(PR_Now())));
+
+ // Set the persisted flag to true.
+ QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(true)));
+ }
+
+ // Directory metadata has been successfully updated.
+ // Update OriginInfo too if temporary storage was already initialized.
+ if (aQuotaManager.IsTemporaryStorageInitialized()) {
+ aQuotaManager.PersistOrigin(originMetadata);
+ }
+ }
+
+ return NS_OK;
+}
+
+void PersistOp::GetResponse(RequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ aResponse = PersistResponse();
+}
+
+EstimateOp::EstimateOp(const EstimateParams& aParams)
+ : QuotaRequestBase("dom::quota::EstimateOp", /* aExclusive */ false),
+ mParams(aParams) {
+ AssertIsOnOwningThread();
+
+ // Overwrite OriginOperationBase default values.
+ mNeedsStorageInit = true;
+}
+
+nsresult EstimateOp::DoInit(QuotaManager& aQuotaManager) {
+ AssertIsOnOwningThread();
+
+ QM_TRY_UNWRAP(
+ PrincipalMetadata principalMetadata,
+ aQuotaManager.GetInfoFromValidatedPrincipalInfo(mParams.principalInfo()));
+
+ principalMetadata.AssertInvariants();
+
+ mOriginMetadata = {std::move(principalMetadata), PERSISTENCE_TYPE_DEFAULT};
+
+ return NS_OK;
+}
+
+RefPtr<DirectoryLock> EstimateOp::CreateDirectoryLock() { return nullptr; }
+
+nsresult EstimateOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+ aQuotaManager.AssertStorageIsInitialized();
+
+ AUTO_PROFILER_LABEL("EstimateOp::DoDirectoryWork", OTHER);
+
+ // Ensure temporary storage is initialized. If temporary storage hasn't been
+ // initialized yet, the method will initialize it by traversing the
+ // repositories for temporary and default storage (including origins belonging
+ // to our group).
+ QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureTemporaryStorageIsInitialized()));
+
+ // Get cached usage (the method doesn't have to stat any files).
+ mUsageAndLimit = aQuotaManager.GetUsageAndLimitForEstimate(mOriginMetadata);
+
+ return NS_OK;
+}
+
+void EstimateOp::GetResponse(RequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ EstimateResponse estimateResponse;
+
+ estimateResponse.usage() = mUsageAndLimit.first;
+ estimateResponse.limit() = mUsageAndLimit.second;
+
+ aResponse = estimateResponse;
+}
+
+ListOriginsOp::ListOriginsOp()
+ : QuotaRequestBase("dom::quota::ListOriginsOp", /* aExclusive */ false),
+ TraverseRepositoryHelper() {
+ AssertIsOnOwningThread();
+}
+
+void ListOriginsOp::Init(Quota& aQuota) {
+ AssertIsOnOwningThread();
+
+ mNeedsStorageInit = true;
+}
+
+nsresult ListOriginsOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
+ AssertIsOnIOThread();
+ aQuotaManager.AssertStorageIsInitialized();
+
+ AUTO_PROFILER_LABEL("ListOriginsOp::DoDirectoryWork", OTHER);
+
+ for (const PersistenceType type : kAllPersistenceTypes) {
+ QM_TRY(MOZ_TO_RESULT(TraverseRepository(aQuotaManager, type)));
+ }
+
+ // TraverseRepository above only consulted the file-system to get a list of
+ // known origins, but we also need to include origins that have pending quota
+ // usage.
+
+ aQuotaManager.CollectPendingOriginsForListing([this](const auto& originInfo) {
+ mOrigins.AppendElement(originInfo->Origin());
+ });
+
+ return NS_OK;
+}
+
+const Atomic<bool>& ListOriginsOp::GetIsCanceledFlag() {
+ AssertIsOnIOThread();
+
+ return mCanceled;
+}
+
+nsresult ListOriginsOp::ProcessOrigin(QuotaManager& aQuotaManager,
+ nsIFile& aOriginDir,
+ const bool aPersistent,
+ const PersistenceType aPersistenceType) {
+ AssertIsOnIOThread();
+
+ QM_TRY_UNWRAP(auto maybeMetadata,
+ QM_OR_ELSE_WARN_IF(
+ // Expression
+ aQuotaManager.GetOriginMetadata(&aOriginDir)
+ .map([](auto metadata) -> Maybe<OriginMetadata> {
+ return Some(std::move(metadata));
+ }),
+ // Predicate.
+ IsSpecificError<NS_ERROR_MALFORMED_URI>,
+ // Fallback.
+ ErrToDefaultOk<Maybe<OriginMetadata>>));
+
+ if (!maybeMetadata) {
+ // Unknown directories during listing are allowed. Just warn if we find
+ // them.
+ QM_TRY_INSPECT(const auto& leafName,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, aOriginDir,
+ GetLeafName));
+
+ UNKNOWN_FILE_WARNING(leafName);
+ return NS_OK;
+ }
+
+ auto metadata = maybeMetadata.extract();
+
+ if (aQuotaManager.IsOriginInternal(metadata.mOrigin)) {
+ return NS_OK;
+ }
+
+ mOrigins.AppendElement(std::move(metadata.mOrigin));
+
+ return NS_OK;
+}
+
+void ListOriginsOp::GetResponse(RequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ aResponse = ListOriginsResponse();
+ if (mOrigins.IsEmpty()) {
+ return;
+ }
+
+ nsTArray<nsCString>& origins = aResponse.get_ListOriginsResponse().origins();
+ mOrigins.SwapElements(origins);
+}
+
+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(CreateDirectoryMetadata(*aOriginProps.mDirectory,
+ aOriginProps.mTimestamp,
+ aOriginProps.mOriginMetadata)));
+
+ QM_TRY(MOZ_TO_RESULT(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));
+
+ 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;
+}
+
+// static
+auto OriginParser::ParseOrigin(const nsACString& aOrigin, nsCString& aSpec,
+ OriginAttributes* aAttrs,
+ nsCString& aOriginalSuffix) -> ResultType {
+ MOZ_ASSERT(!aOrigin.IsEmpty());
+ MOZ_ASSERT(aAttrs);
+
+ nsCString origin(aOrigin);
+ int32_t pos = origin.RFindChar('^');
+
+ if (pos == kNotFound) {
+ aOriginalSuffix.Truncate();
+ } else {
+ aOriginalSuffix = Substring(origin, pos);
+ }
+
+ OriginAttributes originAttributes;
+
+ nsCString originNoSuffix;
+ bool ok = originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix);
+ if (!ok) {
+ return InvalidOrigin;
+ }
+
+ OriginParser parser(originNoSuffix);
+
+ *aAttrs = originAttributes;
+ return parser.Parse(aSpec);
+}
+
+auto OriginParser::Parse(nsACString& aSpec) -> ResultType {
+ while (mTokenizer.hasMoreTokens()) {
+ const nsDependentCSubstring& token = mTokenizer.nextToken();
+
+ HandleToken(token);
+
+ if (mError) {
+ break;
+ }
+
+ if (!mHandledTokens.IsEmpty()) {
+ mHandledTokens.AppendLiteral(", ");
+ }
+ mHandledTokens.Append('\'');
+ mHandledTokens.Append(token);
+ mHandledTokens.Append('\'');
+ }
+
+ if (!mError && mTokenizer.separatorAfterCurrentToken()) {
+ HandleTrailingSeparator();
+ }
+
+ if (mError) {
+ QM_WARNING("Origin '%s' failed to parse, handled tokens: %s", mOrigin.get(),
+ mHandledTokens.get());
+
+ return (mSchemeType == eChrome || mSchemeType == eAbout) ? ObsoleteOrigin
+ : InvalidOrigin;
+ }
+
+ MOZ_ASSERT(mState == eComplete || mState == eHandledTrailingSeparator);
+
+ // For IPv6 URL, it should at least have three groups.
+ MOZ_ASSERT_IF(mIPGroup > 0, mIPGroup >= 3);
+
+ nsAutoCString spec(mScheme);
+
+ if (mSchemeType == eFile) {
+ spec.AppendLiteral("://");
+
+ if (mUniversalFileOrigin) {
+ MOZ_ASSERT(mPathnameComponents.Length() == 1);
+
+ spec.Append(mPathnameComponents[0]);
+ } else {
+ for (uint32_t count = mPathnameComponents.Length(), index = 0;
+ index < count; index++) {
+ spec.Append('/');
+ spec.Append(mPathnameComponents[index]);
+ }
+ }
+
+ aSpec = spec;
+
+ return ValidOrigin;
+ }
+
+ if (mSchemeType == eAbout) {
+ if (mMaybeObsolete) {
+ // The "moz-safe-about+++home" was acciedntally created by a buggy nightly
+ // and can be safely removed.
+ return mHost.EqualsLiteral("home") ? ObsoleteOrigin : InvalidOrigin;
+ }
+ spec.Append(':');
+ } else if (mSchemeType != eChrome) {
+ spec.AppendLiteral("://");
+ }
+
+ spec.Append(mHost);
+
+ if (!mPort.IsNull()) {
+ spec.Append(':');
+ spec.AppendInt(mPort.Value());
+ }
+
+ aSpec = spec;
+
+ return mScheme.EqualsLiteral("app") ? ObsoleteOrigin : ValidOrigin;
+}
+
+void OriginParser::HandleScheme(const nsDependentCSubstring& aToken) {
+ MOZ_ASSERT(!aToken.IsEmpty());
+ MOZ_ASSERT(mState == eExpectingAppIdOrScheme || mState == eExpectingScheme);
+
+ bool isAbout = false;
+ bool isMozSafeAbout = false;
+ bool isFile = false;
+ bool isChrome = false;
+ if (aToken.EqualsLiteral("http") || aToken.EqualsLiteral("https") ||
+ (isAbout = aToken.EqualsLiteral("about") ||
+ (isMozSafeAbout = aToken.EqualsLiteral("moz-safe-about"))) ||
+ aToken.EqualsLiteral("indexeddb") ||
+ (isFile = aToken.EqualsLiteral("file")) || aToken.EqualsLiteral("app") ||
+ aToken.EqualsLiteral("resource") ||
+ aToken.EqualsLiteral("moz-extension") ||
+ (isChrome = aToken.EqualsLiteral(kChromeOrigin)) ||
+ aToken.EqualsLiteral("uuid")) {
+ mScheme = aToken;
+
+ if (isAbout) {
+ mSchemeType = eAbout;
+ mState = isMozSafeAbout ? eExpectingEmptyToken1OrHost : eExpectingHost;
+ } else if (isChrome) {
+ mSchemeType = eChrome;
+ if (mTokenizer.hasMoreTokens()) {
+ mError = true;
+ }
+ mState = eComplete;
+ } else {
+ if (isFile) {
+ mSchemeType = eFile;
+ }
+ mState = eExpectingEmptyToken1;
+ }
+
+ return;
+ }
+
+ QM_WARNING("'%s' is not a valid scheme!", nsCString(aToken).get());
+
+ mError = true;
+}
+
+void OriginParser::HandlePathnameComponent(
+ const nsDependentCSubstring& aToken) {
+ MOZ_ASSERT(!aToken.IsEmpty());
+ MOZ_ASSERT(mState == eExpectingEmptyTokenOrDriveLetterOrPathnameComponent ||
+ mState == eExpectingEmptyTokenOrPathnameComponent);
+ MOZ_ASSERT(mSchemeType == eFile);
+
+ mPathnameComponents.AppendElement(aToken);
+
+ mState = mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent
+ : eComplete;
+}
+
+void OriginParser::HandleToken(const nsDependentCSubstring& aToken) {
+ switch (mState) {
+ case eExpectingAppIdOrScheme: {
+ if (aToken.IsEmpty()) {
+ QM_WARNING("Expected an app id or scheme (not an empty string)!");
+
+ mError = true;
+ return;
+ }
+
+ if (IsAsciiDigit(aToken.First())) {
+ // nsDependentCSubstring doesn't provice ToInteger()
+ nsCString token(aToken);
+
+ nsresult rv;
+ Unused << token.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ mState = eExpectingInMozBrowser;
+ return;
+ }
+ }
+
+ HandleScheme(aToken);
+
+ return;
+ }
+
+ case eExpectingInMozBrowser: {
+ if (aToken.Length() != 1) {
+ QM_WARNING("'%zu' is not a valid length for the inMozBrowser flag!",
+ aToken.Length());
+
+ mError = true;
+ return;
+ }
+
+ if (aToken.First() == 't') {
+ mInIsolatedMozBrowser = true;
+ } else if (aToken.First() == 'f') {
+ mInIsolatedMozBrowser = false;
+ } else {
+ QM_WARNING("'%s' is not a valid value for the inMozBrowser flag!",
+ nsCString(aToken).get());
+
+ mError = true;
+ return;
+ }
+
+ mState = eExpectingScheme;
+
+ return;
+ }
+
+ case eExpectingScheme: {
+ if (aToken.IsEmpty()) {
+ QM_WARNING("Expected a scheme (not an empty string)!");
+
+ mError = true;
+ return;
+ }
+
+ HandleScheme(aToken);
+
+ return;
+ }
+
+ case eExpectingEmptyToken1: {
+ if (!aToken.IsEmpty()) {
+ QM_WARNING("Expected the first empty token!");
+
+ mError = true;
+ return;
+ }
+
+ mState = eExpectingEmptyToken2;
+
+ return;
+ }
+
+ case eExpectingEmptyToken2: {
+ if (!aToken.IsEmpty()) {
+ QM_WARNING("Expected the second empty token!");
+
+ mError = true;
+ return;
+ }
+
+ if (mSchemeType == eFile) {
+ mState = eExpectingEmptyTokenOrUniversalFileOrigin;
+ } else {
+ if (mSchemeType == eAbout) {
+ mMaybeObsolete = true;
+ }
+ mState = eExpectingHost;
+ }
+
+ return;
+ }
+
+ case eExpectingEmptyTokenOrUniversalFileOrigin: {
+ MOZ_ASSERT(mSchemeType == eFile);
+
+ if (aToken.IsEmpty()) {
+ mState = mTokenizer.hasMoreTokens()
+ ? eExpectingEmptyTokenOrDriveLetterOrPathnameComponent
+ : eComplete;
+
+ return;
+ }
+
+ if (aToken.EqualsLiteral("UNIVERSAL_FILE_URI_ORIGIN")) {
+ mUniversalFileOrigin = true;
+
+ mPathnameComponents.AppendElement(aToken);
+
+ mState = eComplete;
+
+ return;
+ }
+
+ QM_WARNING(
+ "Expected the third empty token or "
+ "UNIVERSAL_FILE_URI_ORIGIN!");
+
+ mError = true;
+ return;
+ }
+
+ case eExpectingHost: {
+ if (aToken.IsEmpty()) {
+ QM_WARNING("Expected a host (not an empty string)!");
+
+ mError = true;
+ return;
+ }
+
+ mHost = aToken;
+
+ if (aToken.First() == '[') {
+ MOZ_ASSERT(mIPGroup == 0);
+
+ ++mIPGroup;
+ mState = eExpectingIPV6Token;
+
+ MOZ_ASSERT(mTokenizer.hasMoreTokens());
+ return;
+ }
+
+ if (mTokenizer.hasMoreTokens()) {
+ if (mSchemeType == eAbout) {
+ QM_WARNING("Expected an empty string after host!");
+
+ mError = true;
+ return;
+ }
+
+ mState = eExpectingPort;
+
+ return;
+ }
+
+ mState = eComplete;
+
+ return;
+ }
+
+ case eExpectingPort: {
+ MOZ_ASSERT(mSchemeType == eNone);
+
+ if (aToken.IsEmpty()) {
+ QM_WARNING("Expected a port (not an empty string)!");
+
+ mError = true;
+ return;
+ }
+
+ // nsDependentCSubstring doesn't provice ToInteger()
+ nsCString token(aToken);
+
+ nsresult rv;
+ uint32_t port = token.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ mPort.SetValue() = port;
+ } else {
+ QM_WARNING("'%s' is not a valid port number!", token.get());
+
+ mError = true;
+ return;
+ }
+
+ mState = eComplete;
+
+ return;
+ }
+
+ case eExpectingEmptyTokenOrDriveLetterOrPathnameComponent: {
+ MOZ_ASSERT(mSchemeType == eFile);
+
+ if (aToken.IsEmpty()) {
+ mPathnameComponents.AppendElement(""_ns);
+
+ mState = mTokenizer.hasMoreTokens()
+ ? eExpectingEmptyTokenOrPathnameComponent
+ : eComplete;
+
+ return;
+ }
+
+ if (aToken.Length() == 1 && IsAsciiAlpha(aToken.First())) {
+ mMaybeDriveLetter = true;
+
+ mPathnameComponents.AppendElement(aToken);
+
+ mState = mTokenizer.hasMoreTokens()
+ ? eExpectingEmptyTokenOrPathnameComponent
+ : eComplete;
+
+ return;
+ }
+
+ HandlePathnameComponent(aToken);
+
+ return;
+ }
+
+ case eExpectingEmptyTokenOrPathnameComponent: {
+ MOZ_ASSERT(mSchemeType == eFile);
+
+ if (aToken.IsEmpty()) {
+ if (mMaybeDriveLetter) {
+ MOZ_ASSERT(mPathnameComponents.Length() == 1);
+
+ nsCString& pathnameComponent = mPathnameComponents[0];
+ pathnameComponent.Append(':');
+
+ mMaybeDriveLetter = false;
+ } else {
+ mPathnameComponents.AppendElement(""_ns);
+ }
+
+ mState = mTokenizer.hasMoreTokens()
+ ? eExpectingEmptyTokenOrPathnameComponent
+ : eComplete;
+
+ return;
+ }
+
+ HandlePathnameComponent(aToken);
+
+ return;
+ }
+
+ case eExpectingEmptyToken1OrHost: {
+ MOZ_ASSERT(mSchemeType == eAbout &&
+ mScheme.EqualsLiteral("moz-safe-about"));
+
+ if (aToken.IsEmpty()) {
+ mState = eExpectingEmptyToken2;
+ } else {
+ mHost = aToken;
+ mState = mTokenizer.hasMoreTokens() ? eExpectingPort : eComplete;
+ }
+
+ return;
+ }
+
+ case eExpectingIPV6Token: {
+ // A safe check for preventing infinity recursion.
+ if (++mIPGroup > 8) {
+ mError = true;
+ return;
+ }
+
+ mHost.AppendLiteral(":");
+ mHost.Append(aToken);
+ if (!aToken.IsEmpty() && aToken.Last() == ']') {
+ mState = mTokenizer.hasMoreTokens() ? eExpectingPort : eComplete;
+ }
+
+ return;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+}
+
+void OriginParser::HandleTrailingSeparator() {
+ MOZ_ASSERT(mState == eComplete);
+ MOZ_ASSERT(mSchemeType == eFile);
+
+ mPathnameComponents.AppendElement(""_ns);
+
+ mState = eHandledTrailingSeparator;
+}
+
+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(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(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(CreateDirectoryMetadata(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ aOriginProps.mOriginMetadata)));
+ }
+
+ QM_TRY(MOZ_TO_RESULT(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(CreateDirectoryMetadata(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ aOriginProps.mOriginMetadata)));
+ }
+
+ if (aOriginProps.mNeedsRestore2) {
+ QM_TRY(MOZ_TO_RESULT(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(CreateDirectoryMetadata(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ aOriginProps.mOriginMetadata)));
+ }
+
+ if (aOriginProps.mNeedsRestore2) {
+ QM_TRY(MOZ_TO_RESULT(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(CreateDirectoryMetadata(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ aOriginProps.mOriginMetadata)));
+ }
+
+ if (aOriginProps.mNeedsRestore2) {
+ QM_TRY(MOZ_TO_RESULT(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(CreateDirectoryMetadata2(
+ *aOriginProps.mDirectory, aOriginProps.mTimestamp,
+ /* aPersisted */ false, aOriginProps.mOriginMetadata)));
+
+ return NS_OK;
+}
+
+} // namespace mozilla::dom::quota