Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ActorsParent.h"
#include "mozIStorageConnection.h"
#include "mozIStorageService.h"
#include "nsIBinaryInputStream.h"
#include "nsIObjectOutputStream.h"
#include "nsIFile.h"
#include "nsIFileStreams.h"
#include "nsIObserverService.h"
#include "nsIPlatformInfo.h"
#include "nsIPrincipal.h"
#include "nsIRunnable.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsISupportsPrimitives.h"
#include "nsITimer.h"
#include "nsIURI.h"
#include "nsPIDOMWindow.h"
#include <algorithm>
#include <type_traits>
#include "GeckoProfiler.h"
#include "mozilla/Atomics.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/CondVar.h"
#include "mozilla/Telemetry.h"
#include "mozilla/dom/PContent.h"
#include "mozilla/dom/cache/QuotaClient.h"
#include "mozilla/dom/indexedDB/ActorsParent.h"
#include "mozilla/dom/localstorage/ActorsParent.h"
#include "mozilla/dom/quota/PQuotaParent.h"
#include "mozilla/dom/quota/PQuotaRequestParent.h"
#include "mozilla/dom/quota/PQuotaUsageRequestParent.h"
#include "mozilla/dom/simpledb/ActorsParent.h"
#include "mozilla/dom/StorageActivityService.h"
#include "mozilla/dom/StorageDBUpdater.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/net/MozURL.h"
#include "mozilla/Mutex.h"
#include "mozilla/Preferences.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/TextUtils.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Unused.h"
#include "mozStorageCID.h"
#include "mozStorageHelper.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsComponentManagerUtils.h"
#include "nsAboutProtocolUtils.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsContentUtils.h"
#include "nsCRTGlue.h"
#include "nsDirectoryServiceUtils.h"
#include "nsEscape.h"
#include "nsNetUtil.h"
#include "nsPrintfCString.h"
#include "nsScriptSecurityManager.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "prio.h"
#include "xpcpublic.h"
#include "OriginScope.h"
#include "QuotaManager.h"
#include "QuotaManagerService.h"
#include "QuotaObject.h"
#include "UsageInfo.h"
#define DISABLE_ASSERTS_FOR_FUZZING 0
#if DISABLE_ASSERTS_FOR_FUZZING
# define ASSERT_UNLESS_FUZZING(...) \
do { \
} while (0)
#else
# define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
#endif
#define QM_LOG_TEST() MOZ_LOG_TEST(GetQuotaManagerLogger(), LogLevel::Info)
#define QM_LOG(_args) MOZ_LOG(GetQuotaManagerLogger(), LogLevel::Info, _args)
// 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
// The amount of time, in milliseconds, that we will wait for active storage
// transactions on shutdown before aborting them.
#define DEFAULT_SHUTDOWN_TIMER_MS 30000
// 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 {
namespace dom {
namespace quota {
using namespace mozilla::ipc;
using mozilla::net::MozURL;
// 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 "/:*?\"<>|\\";
namespace {
template <typename T>
void AssertNoOverflow(uint64_t aDest, T aArg);
/*******************************************************************************
* Constants
******************************************************************************/
const uint32_t kSQLitePageSizeOverride = 512;
const uint32_t kHackyDowngradeMajorStorageVersion = 2;
const uint32_t kHackyDowngradeMinorStorageVersion = 1;
// 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);
static_assert(static_cast<uint32_t>(StorageType::Persistent) ==
static_cast<uint32_t>(PERSISTENCE_TYPE_PERSISTENT),
"Enum values should match.");
static_assert(static_cast<uint32_t>(StorageType::Temporary) ==
static_cast<uint32_t>(PERSISTENCE_TYPE_TEMPORARY),
"Enum values should match.");
static_assert(static_cast<uint32_t>(StorageType::Default) ==
static_cast<uint32_t>(PERSISTENCE_TYPE_DEFAULT),
"Enum values should match.");
const char kChromeOrigin[] = "chrome";
const char kAboutHomeOriginPrefix[] = "moz-safe-about:home";
const char kIndexedDBOriginPrefix[] = "indexeddb://";
const char kResourceOriginPrefix[] = "resource://";
constexpr auto kPersistentOriginTelemetryKey = "PersistentOrigin"_ns;
constexpr auto kTemporaryOriginTelemetryKey = "TemporaryOrigin"_ns;
constexpr auto kStorageName = u"storage"_ns;
constexpr auto kSQLiteSuffix = u".sqlite"_ns;
#define INDEXEDDB_DIRECTORY_NAME u"indexedDB"
#define PERSISTENT_DIRECTORY_NAME u"persistent"
#define PERMANENT_DIRECTORY_NAME u"permanent"
#define TEMPORARY_DIRECTORY_NAME u"temporary"
#define DEFAULT_DIRECTORY_NAME u"default"
// 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 uint32_t kLocalStorageArchiveVersion = 4;
const char kProfileDoChangeTopic[] = "profile-do-change";
const int32_t kCacheVersion = 1;
const int64_t kBypassDirectoryLockIdTableId = -1;
/******************************************************************************
* 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`
nsresult rv = aConnection->ExecuteSimpleSQL(
nsLiteralCString("CREATE TABLE database"
"( cache_version INTEGER NOT NULL DEFAULT 0"
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
#ifdef DEBUG
{
int32_t storageVersion;
rv = aConnection->GetSchemaVersion(&storageVersion);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(storageVersion == 0);
}
#endif
rv = aConnection->SetSchemaVersion(kStorageVersion);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
nsresult LoadCacheVersion(mozIStorageConnection* aConnection,
int32_t& aVersion) {
AssertIsOnIOThread();
MOZ_ASSERT(aConnection);
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = aConnection->CreateStatement(
"SELECT cache_version FROM database"_ns, getter_AddRefs(stmt));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
bool hasResult;
rv = stmt->ExecuteStep(&hasResult);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (NS_WARN_IF(!hasResult)) {
return NS_ERROR_FILE_CORRUPTED;
}
int32_t version;
rv = stmt->GetInt32(0, &version);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
aVersion = version;
return NS_OK;
}
nsresult SaveCacheVersion(mozIStorageConnection* aConnection,
int32_t aVersion) {
AssertIsOnIOThread();
MOZ_ASSERT(aConnection);
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = aConnection->CreateStatement(
"UPDATE database SET cache_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;
}
nsresult CreateCacheTables(mozIStorageConnection* aConnection) {
AssertIsOnIOThread();
MOZ_ASSERT(aConnection);
// Table `cache`
nsresult rv = aConnection->ExecuteSimpleSQL(
nsLiteralCString("CREATE TABLE cache"
"( valid INTEGER NOT NULL DEFAULT 0"
", build_id TEXT NOT NULL DEFAULT ''"
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Table `repository`
rv =
aConnection->ExecuteSimpleSQL(nsLiteralCString("CREATE TABLE repository"
"( id INTEGER PRIMARY KEY"
", name TEXT NOT NULL"
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Table `origin`
rv = aConnection->ExecuteSimpleSQL(
nsLiteralCString("CREATE TABLE origin"
"( repository_id INTEGER NOT NULL"
", origin TEXT NOT NULL"
", group_ 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) "
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
#ifdef DEBUG
{
int32_t cacheVersion;
rv = LoadCacheVersion(aConnection, cacheVersion);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(cacheVersion == 0);
}
#endif
rv = SaveCacheVersion(aConnection, kCacheVersion);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
/*
nsresult UpgradeCacheFrom1To2(mozIStorageConnection* aConnection) {
AssertIsOnIOThread();
MOZ_ASSERT(aConnection);
nsresult rv;
#ifdef DEBUG
{
int32_t cacheVersion;
rv = LoadCacheVersion(aConnection, cacheVersion);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(cacheVersion == 1);
}
#endif
rv = SaveCacheVersion(aConnection, 2);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
*/
nsresult InvalidateCache(mozIStorageConnection* aConnection) {
AssertIsOnIOThread();
MOZ_ASSERT(aConnection);
mozStorageTransaction transaction(
aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
nsresult rv = aConnection->ExecuteSimpleSQL("DELETE FROM origin;"_ns);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL("UPDATE cache SET valid = 0"_ns);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = transaction.Commit();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
nsresult CreateWebAppsStoreConnection(nsIFile* aWebAppsStoreFile,
mozIStorageService* aStorageService,
mozIStorageConnection** aConnection) {
AssertIsOnIOThread();
MOZ_ASSERT(aWebAppsStoreFile);
MOZ_ASSERT(aStorageService);
MOZ_ASSERT(aConnection);
// Check if the old database exists at all.
bool exists;
nsresult rv = aWebAppsStoreFile->Exists(&exists);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (!exists) {
// webappsstore.sqlite doesn't exist, return a null connection.
*aConnection = nullptr;
return NS_OK;
}
bool isDirectory;
rv = aWebAppsStoreFile->IsDirectory(&isDirectory);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (isDirectory) {
QM_WARNING("webappsstore.sqlite is not a file!");
*aConnection = nullptr;
return NS_OK;
}
nsCOMPtr<mozIStorageConnection> connection;
rv = aStorageService->OpenUnsharedDatabase(aWebAppsStoreFile,
getter_AddRefs(connection));
if (rv == NS_ERROR_FILE_CORRUPTED) {
// Don't throw an error, leave a corrupted webappsstore database as it is.
*aConnection = nullptr;
return NS_OK;
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = StorageDBUpdater::Update(connection);
if (NS_FAILED(rv)) {
// Don't throw an error, leave a non-updateable webappsstore database as
// it is.
*aConnection = nullptr;
return NS_OK;
}
connection.forget(aConnection);
return NS_OK;
}
nsresult GetLocalStorageArchiveFile(const nsAString& aDirectoryPath,
nsIFile** aLsArchiveFile) {
AssertIsOnIOThread();
MOZ_ASSERT(!aDirectoryPath.IsEmpty());
MOZ_ASSERT(aLsArchiveFile);
auto lsArchiveFileOrErr = QM_NewLocalFile(aDirectoryPath);
if (NS_WARN_IF(lsArchiveFileOrErr.isErr())) {
return lsArchiveFileOrErr.unwrapErr();
}
nsCOMPtr<nsIFile> lsArchiveFile = lsArchiveFileOrErr.unwrap();
nsresult rv = lsArchiveFile->Append(nsLiteralString(LS_ARCHIVE_FILE_NAME));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
lsArchiveFile.forget(aLsArchiveFile);
return NS_OK;
}
nsresult GetLocalStorageArchiveTmpFile(const nsAString& aDirectoryPath,
nsIFile** aLsArchiveTmpFile) {
AssertIsOnIOThread();
MOZ_ASSERT(!aDirectoryPath.IsEmpty());
MOZ_ASSERT(aLsArchiveTmpFile);
auto lsArchiveTempFileOrErr = QM_NewLocalFile(aDirectoryPath);
if (NS_WARN_IF(lsArchiveTempFileOrErr.isErr())) {
return lsArchiveTempFileOrErr.unwrapErr();
}
nsCOMPtr<nsIFile> lsArchiveTmpFile = lsArchiveTempFileOrErr.unwrap();
nsresult rv =
lsArchiveTmpFile->Append(nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
lsArchiveTmpFile.forget(aLsArchiveTmpFile);
return NS_OK;
}
nsresult InitializeLocalStorageArchive(mozIStorageConnection* aConnection,
uint32_t aVersion) {
AssertIsOnIOThread();
MOZ_ASSERT(aConnection);
nsresult rv = aConnection->ExecuteSimpleSQL(nsLiteralCString(
"CREATE TABLE database(version INTEGER NOT NULL DEFAULT 0);"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<mozIStorageStatement> stmt;
rv = aConnection->CreateStatement(
"INSERT INTO database (version) VALUES (: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;
}
nsresult IsLocalStorageArchiveInitialized(mozIStorageConnection* aConnection,
bool& aInitialized) {
AssertIsOnIOThread();
MOZ_ASSERT(aConnection);
bool exists;
nsresult rv = aConnection->TableExists("database"_ns, &exists);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
aInitialized = exists;
return NS_OK;
}
nsresult LoadLocalStorageArchiveVersion(mozIStorageConnection* aConnection,
uint32_t& aVersion) {
AssertIsOnIOThread();
MOZ_ASSERT(aConnection);
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = aConnection->CreateStatement("SELECT version FROM database"_ns,
getter_AddRefs(stmt));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
bool hasResult;
rv = stmt->ExecuteStep(&hasResult);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (NS_WARN_IF(!hasResult)) {
return NS_ERROR_FILE_CORRUPTED;
}
int32_t version;
rv = stmt->GetInt32(0, &version);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
aVersion = version;
return NS_OK;
}
/*
nsresult SaveLocalStorageArchiveVersion(mozIStorageConnection* aConnection,
uint32_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;
}
*/
/******************************************************************************
* Quota manager class declarations
******************************************************************************/
} // namespace
class DirectoryLockImpl final : public DirectoryLock {
const NotNull<RefPtr<QuotaManager>> mQuotaManager;
const Nullable<PersistenceType> mPersistenceType;
const nsCString mGroup;
const OriginScope mOriginScope;
const Nullable<Client::Type> mClientType;
LazyInitializedOnceEarlyDestructible<
const NotNull<RefPtr<OpenDirectoryListener>>>
mOpenListener;
nsTArray<NotNull<DirectoryLockImpl*>> mBlocking;
nsTArray<NotNull<DirectoryLockImpl*>> mBlockedOn;
const int64_t mId;
const bool mExclusive;
// Internal quota manager operations use this flag to prevent directory lock
// registraction/unregistration from updating origin access time, etc.
const bool mInternal;
bool mRegistered;
FlippedOnce<false> mInvalidated;
public:
DirectoryLockImpl(MovingNotNull<RefPtr<QuotaManager>> aQuotaManager,
const int64_t aId,
const Nullable<PersistenceType>& aPersistenceType,
const nsACString& aGroup, const OriginScope& aOriginScope,
const Nullable<Client::Type>& aClientType, bool aExclusive,
bool aInternal,
RefPtr<OpenDirectoryListener> aOpenListener);
void AssertIsOnOwningThread() const
#ifdef DEBUG
;
#else
{
}
#endif
const Nullable<PersistenceType>& NullablePersistenceType() const {
return mPersistenceType;
}
const OriginScope& GetOriginScope() const { return mOriginScope; }
const Nullable<Client::Type>& NullableClientType() const {
return mClientType;
}
bool IsInternal() const { return mInternal; }
void SetRegistered(bool aRegistered) { mRegistered = aRegistered; }
// Ideally, we would have just one table (instead of these two:
// QuotaManager::mDirectoryLocks and QuotaManager::mDirectoryLockIdTable) for
// all registered locks. However, some directory locks need to be accessed off
// the PBackground thread, so the access must be protected by the quota mutex.
// The problem is that directory locks for eviction must be currently created
// while the mutex lock is already acquired. So we decided to have two tables
// for now and to not register directory locks for eviction in
// QuotaMnaager::mDirectoryLockIdTable. This can be improved in future after
// some refactoring of the mutex locking.
bool ShouldUpdateLockIdTable() const {
return mId != kBypassDirectoryLockIdTableId;
}
bool ShouldUpdateLockTable() {
return !mInternal &&
mPersistenceType.Value() != PERSISTENCE_TYPE_PERSISTENT;
}
bool Overlaps(const DirectoryLockImpl& aLock) const;
// Test whether this DirectoryLock needs to wait for the given lock.
bool MustWaitFor(const DirectoryLockImpl& aLock) const;
void AddBlockingLock(DirectoryLockImpl& aLock) {
AssertIsOnOwningThread();
mBlocking.AppendElement(WrapNotNull(&aLock));
}
const nsTArray<NotNull<DirectoryLockImpl*>>& GetBlockedOnLocks() {
return mBlockedOn;
}
void AddBlockedOnLock(DirectoryLockImpl& aLock) {
AssertIsOnOwningThread();
mBlockedOn.AppendElement(WrapNotNull(&aLock));
}
void MaybeUnblock(DirectoryLockImpl& aLock) {
AssertIsOnOwningThread();
mBlockedOn.RemoveElement(&aLock);
if (mBlockedOn.IsEmpty()) {
NotifyOpenListener();
}
}
void NotifyOpenListener();
void Invalidate() {
AssertIsOnOwningThread();
mInvalidated.EnsureFlipped();
}
NS_INLINE_DECL_REFCOUNTING(DirectoryLockImpl, override)
int64_t Id() const { return mId; }
PersistenceType GetPersistenceType() const {
MOZ_DIAGNOSTIC_ASSERT(!mPersistenceType.IsNull());
return mPersistenceType.Value();
}
const nsACString& Group() const {
MOZ_DIAGNOSTIC_ASSERT(!mGroup.IsEmpty());
return mGroup;
}
const nsACString& Origin() const {
MOZ_DIAGNOSTIC_ASSERT(mOriginScope.IsOrigin());
MOZ_DIAGNOSTIC_ASSERT(!mOriginScope.GetOrigin().IsEmpty());
return mOriginScope.GetOrigin();
}
Client::Type ClientType() const {
MOZ_DIAGNOSTIC_ASSERT(!mClientType.IsNull());
MOZ_DIAGNOSTIC_ASSERT(mClientType.Value() < Client::TypeMax());
return mClientType.Value();
}
already_AddRefed<DirectoryLock> Specialize(PersistenceType aPersistenceType,
const nsACString& aGroup,
const nsACString& aOrigin,
Client::Type aClientType) const;
void Log() const;
private:
~DirectoryLockImpl();
};
const DirectoryLockImpl* GetDirectoryLockImpl(
const DirectoryLock* aDirectoryLock) {
MOZ_ASSERT(aDirectoryLock);
return static_cast<const DirectoryLockImpl*>(aDirectoryLock);
}
class QuotaManager::Observer final : public nsIObserver {
static Observer* sInstance;
bool mPendingProfileChange;
bool mShutdownComplete;
public:
static nsresult Initialize();
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
// XXX Change this not to derive from AutoTArray.
class ClientUsageArray final
: public AutoTArray<Maybe<uint64_t>, Client::TYPE_MAX> {
public:
ClientUsageArray() { SetLength(Client::TypeMax()); }
void Serialize(nsACString& aText) const;
nsresult Deserialize(const nsACString& aText);
ClientUsageArray Clone() const {
ClientUsageArray res;
res.Assign(*this);
return res;
}
};
class OriginInfo final {
friend class GroupInfo;
friend class QuotaManager;
friend class QuotaObject;
public:
OriginInfo(GroupInfo* aGroupInfo, const nsACString& aOrigin,
const ClientUsageArray& aClientUsages, uint64_t aUsage,
int64_t aAccessTime, bool aPersisted, bool aDirectoryExists);
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OriginInfo)
GroupInfo* GetGroupInfo() const { return mGroupInfo; }
const nsCString& Origin() const { return mOrigin; }
int64_t LockedUsage() const {
AssertCurrentThreadOwnsQuotaMutex();
#ifdef DEBUG
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
uint64_t usage = 0;
for (Client::Type type : quotaManager->AllClientTypes()) {
AssertNoOverflow(usage, mClientUsages[type].valueOr(0));
usage += mClientUsages[type].valueOr(0);
}
MOZ_ASSERT(mUsage == usage);
#endif
return mUsage;
}
int64_t LockedAccessTime() const {
AssertCurrentThreadOwnsQuotaMutex();
return mAccessTime;
}
bool LockedPersisted() const {
AssertCurrentThreadOwnsQuotaMutex();
return mPersisted;
}
nsresult LockedBindToStatement(mozIStorageStatement* aStatement) const;
private:
// Private destructor, to discourage deletion outside of Release():
~OriginInfo() {
MOZ_COUNT_DTOR(OriginInfo);
MOZ_ASSERT(!mQuotaObjects.Count());
}
void LockedDecreaseUsage(Client::Type aClientType, int64_t aSize);
void LockedResetUsageForClient(Client::Type aClientType);
bool LockedGetUsageForClient(Client::Type aClientType, uint64_t& aUsage);
void LockedUpdateAccessTime(int64_t aAccessTime) {
AssertCurrentThreadOwnsQuotaMutex();
mAccessTime = aAccessTime;
if (!mAccessed) {
mAccessed = true;
}
}
void LockedPersist();
nsDataHashtable<nsStringHashKey, QuotaObject*> mQuotaObjects;
ClientUsageArray mClientUsages;
GroupInfo* mGroupInfo;
const nsCString mOrigin;
uint64_t mUsage;
int64_t mAccessTime;
bool mAccessed;
bool mPersisted;
/**
* In some special cases like the LocalStorage client where it's possible to
* create a Quota-using representation but not actually write any data, we
* want to be able to track quota for an origin without creating its origin
* directory or the per-client files until they are actually needed to store
* data. In those cases, the OriginInfo will be created by
* EnsureQuotaForOrigin and the resulting mDirectoryExists will be false until
* the origin actually needs to be created. It is possible for mUsage to be
* greater than zero while mDirectoryExists is false, representing a state
* where a client like LocalStorage has reserved quota for disk writes, but
* has not yet flushed the data to disk.
*/
bool mDirectoryExists;
};
class OriginInfoLRUComparator {
public:
bool Equals(const OriginInfo* a, const OriginInfo* b) const {
return a && b ? a->LockedAccessTime() == b->LockedAccessTime()
: !a && !b ? true : false;
}
bool LessThan(const OriginInfo* a, const OriginInfo* b) const {
return a && b ? a->LockedAccessTime() < b->LockedAccessTime()
: b ? true : false;
}
};
class GroupInfo final {
friend class GroupInfoPair;
friend class OriginInfo;
friend class QuotaManager;
friend class QuotaObject;
public:
GroupInfo(GroupInfoPair* aGroupInfoPair, PersistenceType aPersistenceType,
const nsACString& aGroup)
: mGroupInfoPair(aGroupInfoPair),
mPersistenceType(aPersistenceType),
mGroup(aGroup),
mUsage(0) {
MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
MOZ_COUNT_CTOR(GroupInfo);
}
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GroupInfo)
PersistenceType GetPersistenceType() const { return mPersistenceType; }
private:
// Private destructor, to discourage deletion outside of Release():
MOZ_COUNTED_DTOR(GroupInfo)
already_AddRefed<OriginInfo> LockedGetOriginInfo(const nsACString& aOrigin);
void LockedAddOriginInfo(OriginInfo* aOriginInfo);
void LockedRemoveOriginInfo(const nsACString& aOrigin);
void LockedRemoveOriginInfos();
bool LockedHasOriginInfos() {
AssertCurrentThreadOwnsQuotaMutex();
return !mOriginInfos.IsEmpty();
}
nsTArray<RefPtr<OriginInfo>> mOriginInfos;
GroupInfoPair* mGroupInfoPair;
PersistenceType mPersistenceType;
nsCString mGroup;
uint64_t mUsage;
};
class GroupInfoPair {
friend class QuotaManager;
friend class QuotaObject;
public:
MOZ_COUNTED_DEFAULT_CTOR(GroupInfoPair)
MOZ_COUNTED_DTOR(GroupInfoPair)
private:
already_AddRefed<GroupInfo> LockedGetGroupInfo(
PersistenceType aPersistenceType) {
AssertCurrentThreadOwnsQuotaMutex();
MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
RefPtr<GroupInfo> groupInfo =
GetGroupInfoForPersistenceType(aPersistenceType);
return groupInfo.forget();
}
void LockedSetGroupInfo(PersistenceType aPersistenceType,
GroupInfo* aGroupInfo) {
AssertCurrentThreadOwnsQuotaMutex();
MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
RefPtr<GroupInfo>& groupInfo =
GetGroupInfoForPersistenceType(aPersistenceType);
groupInfo = aGroupInfo;
}
void LockedClearGroupInfo(PersistenceType aPersistenceType) {
AssertCurrentThreadOwnsQuotaMutex();
MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
RefPtr<GroupInfo>& groupInfo =
GetGroupInfoForPersistenceType(aPersistenceType);
groupInfo = nullptr;
}
bool LockedHasGroupInfos() {
AssertCurrentThreadOwnsQuotaMutex();
return mTemporaryStorageGroupInfo || mDefaultStorageGroupInfo;
}
RefPtr<GroupInfo>& GetGroupInfoForPersistenceType(
PersistenceType aPersistenceType);
RefPtr<GroupInfo> mTemporaryStorageGroupInfo;
RefPtr<GroupInfo> mDefaultStorageGroupInfo;
};
namespace {
class CollectOriginsHelper final : public Runnable {
uint64_t mMinSizeToBeFreed;
Mutex& mMutex;
CondVar mCondVar;
// The members below are protected by mMutex.
nsTArray<RefPtr<DirectoryLockImpl>> 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<DirectoryLockImpl>>& 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 quota manager initialization on the owning thread.
State_CreatingQuotaManager,
// 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 mNeedsQuotaManagerInit;
bool mNeedsStorageInit;
public:
void NoteActorDestroyed() {
AssertIsOnOwningThread();
mActorDestroyed = true;
}
bool IsActorDestroyed() const {
AssertIsOnOwningThread();
return mActorDestroyed;
}
protected:
explicit OriginOperationBase(
nsIEventTarget* aOwningThread = GetCurrentEventTarget())
: BackgroundThreadObject(aOwningThread),
Runnable("dom::quota::OriginOperationBase"),
mResultCode(NS_OK),
mState(State_Initial),
mActorDestroyed(false),
mNeedsQuotaManagerInit(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_CreatingQuotaManager;
return;
case State_CreatingQuotaManager:
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 void Open() = 0;
nsresult DirectoryOpen();
virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) = 0;
void Finish(nsresult aResult);
virtual void UnblockOpen() = 0;
private:
nsresult Init();
nsresult FinishInit();
nsresult QuotaManagerOpen();
nsresult DirectoryWork();
};
class FinalizeOriginEvictionOp : public OriginOperationBase {
nsTArray<RefPtr<DirectoryLockImpl>> mLocks;
public:
FinalizeOriginEvictionOp(nsIEventTarget* aBackgroundThread,
nsTArray<RefPtr<DirectoryLockImpl>>&& aLocks)
: OriginOperationBase(aBackgroundThread), 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>> {
RefPtr<DirectoryLock> mDirectoryLock;
protected:
Nullable<PersistenceType> mPersistenceType;
OriginScope mOriginScope;
Nullable<Client::Type> mClientType;
mozilla::Atomic<bool> mCanceled;
const bool mExclusive;
bool mNeedsDirectoryLocking;
public:
void RunImmediately() {
MOZ_ASSERT(GetState() == State_Initial);
MOZ_ALWAYS_SUCCEEDS(this->Run());
}
protected:
NormalOriginOperationBase(const Nullable<PersistenceType>& aPersistenceType,
const OriginScope& aOriginScope, bool aExclusive)
: mPersistenceType(aPersistenceType),
mOriginScope(aOriginScope),
mExclusive(aExclusive),
mNeedsDirectoryLocking(true) {
AssertIsOnOwningThread();
}
~NormalOriginOperationBase() = default;
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 {
int64_t mTimestamp;
public:
SaveOriginAccessTimeOp(PersistenceType aPersistenceType,
const nsACString& aOrigin, int64_t aTimestamp)
: NormalOriginOperationBase(Nullable<PersistenceType>(aPersistenceType),
OriginScope::FromOrigin(aOrigin),
/* aExclusive */ false),
mTimestamp(aTimestamp) {
AssertIsOnOwningThread();
}
private:
~SaveOriginAccessTimeOp() = default;
virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
virtual 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();
void StartIdleMaintenance();
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()
: NormalOriginOperationBase(Nullable<PersistenceType>(),
OriginScope::FromNull(),
/* aExclusive */ false) {}
mozilla::Result<UsageInfo, nsresult> GetUsageForOrigin(
QuotaManager& aQuotaManager, PersistenceType aPersistenceType,
const nsACString& aGroup, const nsACString& aOrigin);
// 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 nsACString& aGroup, const nsACString& aOrigin,
nsIDirectoryEnumerator& aEntries, 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 bool IsCanceled() = 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;
nsDataHashtable<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;
bool IsCanceled() override;
nsresult ProcessOrigin(QuotaManager& aQuotaManager, nsIFile& aOriginDir,
const bool aPersistent,
const PersistenceType aPersistenceType) override;
void GetResponse(UsageRequestResponse& aResponse) override;
};
class GetOriginUsageOp final : public QuotaUsageRequestBase {
nsCString mSuffix;
nsCString mGroup;
uint64_t mUsage;
uint64_t mFileUsage;
bool mFromMemory;
public:
explicit GetOriginUsageOp(const UsageRequestParams& aParams);
private:
~GetOriginUsageOp() = default;
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(bool aExclusive)
: NormalOriginOperationBase(Nullable<PersistenceType>(),
OriginScope::FromNull(), 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;
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();
};
class StorageInitializedOp final : public InitializedRequestBase {
private:
~StorageInitializedOp() = default;
nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
void GetResponse(RequestResponse& aResponse) override;
};
class TemporaryStorageInitializedOp final : public InitializedRequestBase {
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 InitStorageAndOriginOp final : public QuotaRequestBase {
nsCString mSuffix;
nsCString mGroup;
bool mCreated;
public:
explicit InitStorageAndOriginOp(const RequestParams& aParams);
void Init(Quota& aQuota) override;
private:
~InitStorageAndOriginOp() = default;
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 ClearRequestBase : public QuotaRequestBase {
protected:
explicit ClearRequestBase(bool aExclusive) : QuotaRequestBase(aExclusive) {
AssertIsOnOwningThread();
}
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;
public:
void Init(Quota& aQuota) override;
protected:
explicit PersistRequestBase(const PrincipalInfo& aPrincipalInfo);
};
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 {
nsCString mGroup;
uint64_t mUsage;
uint64_t mLimit;
public:
explicit EstimateOp(const RequestParams& aParams);
private:
~EstimateOp() = default;
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;
bool IsCanceled() 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
};
/*******************************************************************************
* Helper classes
******************************************************************************/
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
class PrincipalVerifier final : public Runnable {
nsTArray<PrincipalInfo> mPrincipalInfos;
public:
static already_AddRefed<PrincipalVerifier> CreateAndDispatch(
nsTArray<PrincipalInfo>&& aPrincipalInfos);
private:
explicit PrincipalVerifier(nsTArray<PrincipalInfo>&& aPrincipalInfos)
: Runnable("dom::quota::PrincipalVerifier"),
mPrincipalInfos(std::move(aPrincipalInfos)) {
AssertIsOnIOThread();
}
virtual ~PrincipalVerifier() = default;
bool IsPrincipalInfoValid(const PrincipalInfo& aPrincipalInfo);
NS_DECL_NSIRUNNABLE
};
#endif
/*******************************************************************************
* Helper Functions
******************************************************************************/
template <typename T, bool = std::is_unsigned_v<T>>
struct IntChecker {
static void Assert(T aInt) {
static_assert(std::is_integral_v<T>, "Not an integer!");
MOZ_ASSERT(aInt >= 0);
}
};
template <typename T>
struct IntChecker<T, true> {
static void Assert(T aInt) {
static_assert(std::is_integral_v<T>, "Not an integer!");
}
};
template <typename T>
void AssertNoOverflow(uint64_t aDest, T aArg) {
IntChecker<T>::Assert(aDest);
IntChecker<T>::Assert(aArg);
MOZ_ASSERT(UINT64_MAX - aDest >= uint64_t(aArg));
}
template <typename T, typename U>
void AssertNoUnderflow(T aDest, U aArg) {
IntChecker<T>::Assert(aDest);
IntChecker<T>::Assert(aArg);
MOZ_ASSERT(uint64_t(aDest) >= uint64_t(aArg));
}
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);
}
nsresult MaybeUpdateGroupForOrigin(const nsACString& aOrigin,
nsACString& aGroup, bool& aUpdated) {
MOZ_ASSERT(!NS_IsMainThread());
aUpdated = false;
if (aOrigin.EqualsLiteral(kChromeOrigin)) {
if (!aGroup.EqualsLiteral(kChromeOrigin)) {
aGroup.AssignLiteral(kChromeOrigin);
aUpdated = true;
}
} else {
OriginAttributes originAttributes;
nsCString originNoSuffix;
if (NS_WARN_IF(
!originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix))) {
return NS_ERROR_FAILURE;
}
nsCString suffix;
originAttributes.CreateSuffix(suffix);
RefPtr<MozURL> url;
nsresult rv = MozURL::Init(getter_AddRefs(url), originNoSuffix);
if (NS_WARN_IF(NS_FAILED(rv))) {
QM_WARNING("A URL %s is not recognized by MozURL", originNoSuffix.get());
return rv;
}
nsCString baseDomain;
rv = url->BaseDomain(baseDomain);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCString upToDateGroup = baseDomain + suffix;
if (aGroup != upToDateGroup) {
aGroup = upToDateGroup;
aUpdated = true;
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
ContentPrincipalInfo contentPrincipalInfo;
contentPrincipalInfo.attrs() = originAttributes;
contentPrincipalInfo.originNoSuffix() = originNoSuffix;
contentPrincipalInfo.spec() = originNoSuffix;
contentPrincipalInfo.baseDomain() = baseDomain;
PrincipalInfo principalInfo(contentPrincipalInfo);
nsTArray<PrincipalInfo> principalInfos;
principalInfos.AppendElement(principalInfo);
RefPtr<PrincipalVerifier> principalVerifier =
PrincipalVerifier::CreateAndDispatch(std::move(principalInfos));
#endif
}
}
return NS_OK;
}
} // namespace
BackgroundThreadObject::BackgroundThreadObject()
: mOwningThread(GetCurrentEventTarget()) {
AssertIsOnOwningThread();
}
BackgroundThreadObject::BackgroundThreadObject(nsIEventTarget* 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
nsIEventTarget* 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 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::