gecko-dev/dom/quota/QuotaManagerService.cpp

873 строки
20 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "QuotaManagerService.h"
#include "ActorsChild.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Hal.h"
#include "mozilla/Preferences.h"
#include "mozilla/Unused.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "nsIIdleService.h"
#include "nsIObserverService.h"
#include "nsIScriptSecurityManager.h"
#include "nsXULAppAPI.h"
#include "QuotaManager.h"
#include "QuotaRequests.h"
#define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm"
namespace mozilla {
namespace dom {
namespace quota {
using namespace mozilla::ipc;
namespace {
// Preference that is used to enable testing features.
const char kTestingPref[] = "dom.quotaManager.testing";
const char kIdleServiceContractId[] = "@mozilla.org/widget/idleservice;1";
// The number of seconds we will wait after receiving the idle-daily
// notification before beginning maintenance.
const uint32_t kIdleObserverTimeSec = 1;
mozilla::StaticRefPtr<QuotaManagerService> gQuotaManagerService;
mozilla::Atomic<bool> gInitialized(false);
mozilla::Atomic<bool> gClosed(false);
mozilla::Atomic<bool> gTestingMode(false);
void
TestingPrefChangedCallback(const char* aPrefName,
void* aClosure)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!strcmp(aPrefName, kTestingPref));
MOZ_ASSERT(!aClosure);
gTestingMode = Preferences::GetBool(aPrefName);
}
nsresult
CheckedPrincipalToPrincipalInfo(nsIPrincipal* aPrincipal,
PrincipalInfo& aPrincipalInfo)
{
MOZ_ASSERT(aPrincipal);
nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &aPrincipalInfo);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (aPrincipalInfo.type() != PrincipalInfo::TContentPrincipalInfo &&
aPrincipalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
class AbortOperationsRunnable final
: public Runnable
{
ContentParentId mContentParentId;
public:
explicit AbortOperationsRunnable(ContentParentId aContentParentId)
: Runnable("dom::quota::AbortOperationsRunnable")
, mContentParentId(aContentParentId)
{ }
private:
NS_DECL_NSIRUNNABLE
};
} // namespace
class QuotaManagerService::PendingRequestInfo
{
protected:
RefPtr<RequestBase> mRequest;
public:
explicit PendingRequestInfo(RequestBase* aRequest)
: mRequest(aRequest)
{ }
virtual ~PendingRequestInfo()
{ }
RequestBase*
GetRequest() const
{
return mRequest;
}
virtual nsresult
InitiateRequest(QuotaChild* aActor) = 0;
};
class QuotaManagerService::UsageRequestInfo
: public PendingRequestInfo
{
UsageRequestParams mParams;
public:
UsageRequestInfo(UsageRequest* aRequest,
const UsageRequestParams& aParams)
: PendingRequestInfo(aRequest)
, mParams(aParams)
{
MOZ_ASSERT(aRequest);
MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None);
}
virtual nsresult
InitiateRequest(QuotaChild* aActor) override;
};
class QuotaManagerService::RequestInfo
: public PendingRequestInfo
{
RequestParams mParams;
public:
RequestInfo(Request* aRequest,
const RequestParams& aParams)
: PendingRequestInfo(aRequest)
, mParams(aParams)
{
MOZ_ASSERT(aRequest);
MOZ_ASSERT(aParams.type() != RequestParams::T__None);
}
virtual nsresult
InitiateRequest(QuotaChild* aActor) override;
};
class QuotaManagerService::IdleMaintenanceInfo
: public PendingRequestInfo
{
const bool mStart;
public:
explicit IdleMaintenanceInfo(bool aStart)
: PendingRequestInfo(nullptr)
, mStart(aStart)
{ }
virtual nsresult
InitiateRequest(QuotaChild* aActor) override;
};
QuotaManagerService::QuotaManagerService()
: mBackgroundActor(nullptr)
, mBackgroundActorFailed(false)
, mIdleObserverRegistered(false)
{
MOZ_ASSERT(NS_IsMainThread());
}
QuotaManagerService::~QuotaManagerService()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mIdleObserverRegistered);
}
// static
QuotaManagerService*
QuotaManagerService::GetOrCreate()
{
MOZ_ASSERT(NS_IsMainThread());
if (gClosed) {
MOZ_ASSERT(false, "Calling GetOrCreate() after shutdown!");
return nullptr;
}
if (!gQuotaManagerService) {
RefPtr<QuotaManagerService> instance(new QuotaManagerService());
nsresult rv = instance->Init();
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
if (gInitialized.exchange(true)) {
MOZ_ASSERT(false, "Initialized more than once?!");
}
gQuotaManagerService = instance;
ClearOnShutdown(&gQuotaManagerService);
}
return gQuotaManagerService;
}
// static
QuotaManagerService*
QuotaManagerService::Get()
{
// Does not return an owning reference.
return gQuotaManagerService;
}
// static
already_AddRefed<QuotaManagerService>
QuotaManagerService::FactoryCreate()
{
RefPtr<QuotaManagerService> quotaManagerService = GetOrCreate();
return quotaManagerService.forget();
}
void
QuotaManagerService::ClearBackgroundActor()
{
MOZ_ASSERT(NS_IsMainThread());
mBackgroundActor = nullptr;
}
void
QuotaManagerService::NoteLiveManager(QuotaManager* aManager)
{
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aManager);
mBackgroundThread = aManager->OwningThread();
}
void
QuotaManagerService::NoteShuttingDownManager()
{
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
mBackgroundThread = nullptr;
}
void
QuotaManagerService::AbortOperationsForProcess(ContentParentId aContentParentId)
{
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
if (!mBackgroundThread) {
return;
}
RefPtr<AbortOperationsRunnable> runnable =
new AbortOperationsRunnable(aContentParentId);
MOZ_ALWAYS_SUCCEEDS(
mBackgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL));
}
nsresult
QuotaManagerService::Init()
{
MOZ_ASSERT(NS_IsMainThread());
if (XRE_IsParentProcess()) {
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (NS_WARN_IF(!observerService)) {
return NS_ERROR_FAILURE;
}
nsresult rv =
observerService->AddObserver(this,
PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID,
false);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
Preferences::RegisterCallbackAndCall(TestingPrefChangedCallback,
kTestingPref);
return NS_OK;
}
void
QuotaManagerService::Destroy()
{
// Setting the closed flag prevents the service from being recreated.
// Don't set it though if there's no real instance created.
if (gInitialized && gClosed.exchange(true)) {
MOZ_ASSERT(false, "Shutdown more than once?!");
}
Preferences::UnregisterCallback(TestingPrefChangedCallback, kTestingPref);
delete this;
}
nsresult
QuotaManagerService::InitiateRequest(nsAutoPtr<PendingRequestInfo>& aInfo)
{
// Nothing can be done here if we have previously failed to create a
// background actor.
if (mBackgroundActorFailed) {
return NS_ERROR_FAILURE;
}
if (!mBackgroundActor) {
PBackgroundChild* backgroundActor =
BackgroundChild::GetOrCreateForCurrentThread();
if (NS_WARN_IF(!backgroundActor)) {
mBackgroundActorFailed = true;
return NS_ERROR_FAILURE;
}
{
QuotaChild* actor = new QuotaChild(this);
mBackgroundActor =
static_cast<QuotaChild*>(backgroundActor->SendPQuotaConstructor(actor));
}
}
if (!mBackgroundActor) {
mBackgroundActorFailed = true;
return NS_ERROR_FAILURE;
}
// If we already have a background actor then we can start this request now.
nsresult rv = aInfo->InitiateRequest(mBackgroundActor);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
void
QuotaManagerService::PerformIdleMaintenance()
{
using namespace mozilla::hal;
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
// If we're running on battery power then skip all idle maintenance since we
// would otherwise be doing lots of disk I/O.
BatteryInformation batteryInfo;
#ifdef MOZ_WIDGET_ANDROID
// Android XPCShell doesn't load the AndroidBridge that is needed to make
// GetCurrentBatteryInformation work...
if (!QuotaManager::IsRunningXPCShellTests())
#endif
{
// In order to give the correct battery level, hal must have registered
// battery observers.
RegisterBatteryObserver(this);
GetCurrentBatteryInformation(&batteryInfo);
UnregisterBatteryObserver(this);
}
// If we're running XPCShell because we always want to be able to test this
// code so pretend that we're always charging.
if (QuotaManager::IsRunningXPCShellTests()) {
batteryInfo.level() = 100;
batteryInfo.charging() = true;
}
if (NS_WARN_IF(!batteryInfo.charging())) {
return;
}
if (QuotaManager::IsRunningXPCShellTests()) {
// We don't want user activity to impact this code if we're running tests.
Unused << Observe(nullptr, OBSERVER_TOPIC_IDLE, nullptr);
} else if (!mIdleObserverRegistered) {
nsCOMPtr<nsIIdleService> idleService =
do_GetService(kIdleServiceContractId);
MOZ_ASSERT(idleService);
MOZ_ALWAYS_SUCCEEDS(
idleService->AddIdleObserver(this, kIdleObserverTimeSec));
mIdleObserverRegistered = true;
}
}
void
QuotaManagerService::RemoveIdleObserver()
{
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
if (mIdleObserverRegistered) {
nsCOMPtr<nsIIdleService> idleService =
do_GetService(kIdleServiceContractId);
MOZ_ASSERT(idleService);
// Ignore the return value of RemoveIdleObserver, it may fail if the
// observer has already been unregistered during shutdown.
Unused <<
idleService->RemoveIdleObserver(this, kIdleObserverTimeSec);
mIdleObserverRegistered = false;
}
}
NS_IMPL_ADDREF(QuotaManagerService)
NS_IMPL_RELEASE_WITH_DESTROY(QuotaManagerService, Destroy())
NS_IMPL_QUERY_INTERFACE(QuotaManagerService,
nsIQuotaManagerService,
nsIObserver)
NS_IMETHODIMP
QuotaManagerService::Init(nsIQuotaRequest** _retval)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(nsContentUtils::IsCallerChrome());
if (NS_WARN_IF(!gTestingMode)) {
return NS_ERROR_UNEXPECTED;
}
RefPtr<Request> request = new Request();
InitParams params;
nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params));
nsresult rv = InitiateRequest(info);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
request.forget(_retval);
return NS_OK;
}
NS_IMETHODIMP
QuotaManagerService::InitStoragesForPrincipal(
nsIPrincipal* aPrincipal,
const nsACString& aPersistenceType,
nsIQuotaRequest** _retval)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(nsContentUtils::IsCallerChrome());
if (NS_WARN_IF(!gTestingMode)) {
return NS_ERROR_UNEXPECTED;
}
RefPtr<Request> request = new Request();
InitOriginParams params;
nsresult rv = CheckedPrincipalToPrincipalInfo(aPrincipal,
params.principalInfo());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
Nullable<PersistenceType> persistenceType;
rv = NullablePersistenceTypeFromText(aPersistenceType, &persistenceType);
if (NS_WARN_IF(NS_FAILED(rv)) || persistenceType.IsNull()) {
return NS_ERROR_INVALID_ARG;
}
params.persistenceType() = persistenceType.Value();
nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params));
rv = InitiateRequest(info);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
request.forget(_retval);
return NS_OK;
}
NS_IMETHODIMP
QuotaManagerService::GetUsage(nsIQuotaUsageCallback* aCallback,
bool aGetAll,
nsIQuotaUsageRequest** _retval)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aCallback);
RefPtr<UsageRequest> request = new UsageRequest(aCallback);
AllUsageParams params;
params.getAll() = aGetAll;
nsAutoPtr<PendingRequestInfo> info(new UsageRequestInfo(request, params));
nsresult rv = InitiateRequest(info);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
request.forget(_retval);
return NS_OK;
}
NS_IMETHODIMP
QuotaManagerService::GetUsageForPrincipal(nsIPrincipal* aPrincipal,
nsIQuotaUsageCallback* aCallback,
bool aGetGroupUsage,
nsIQuotaUsageRequest** _retval)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(aCallback);
RefPtr<UsageRequest> request = new UsageRequest(aPrincipal, aCallback);
OriginUsageParams params;
nsresult rv = CheckedPrincipalToPrincipalInfo(aPrincipal,
params.principalInfo());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
params.getGroupUsage() = aGetGroupUsage;
nsAutoPtr<PendingRequestInfo> info(new UsageRequestInfo(request, params));
rv = InitiateRequest(info);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
request.forget(_retval);
return NS_OK;
}
NS_IMETHODIMP
QuotaManagerService::Clear(nsIQuotaRequest** _retval)
{
MOZ_ASSERT(NS_IsMainThread());
if (NS_WARN_IF(!gTestingMode)) {
return NS_ERROR_UNEXPECTED;
}
RefPtr<Request> request = new Request();
ClearAllParams params;
nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params));
nsresult rv = InitiateRequest(info);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
request.forget(_retval);
return NS_OK;
}
NS_IMETHODIMP
QuotaManagerService::ClearStoragesForPrincipal(nsIPrincipal* aPrincipal,
const nsACString& aPersistenceType,
bool aClearAll,
nsIQuotaRequest** _retval)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aPrincipal);
nsCString suffix;
aPrincipal->OriginAttributesRef().CreateSuffix(suffix);
if (NS_WARN_IF(aClearAll && !suffix.IsEmpty())) {
// The originAttributes should be default originAttributes when the
// aClearAll flag is set.
return NS_ERROR_INVALID_ARG;
}
RefPtr<Request> request = new Request(aPrincipal);
ClearOriginParams params;
nsresult rv = CheckedPrincipalToPrincipalInfo(aPrincipal,
params.principalInfo());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
Nullable<PersistenceType> persistenceType;
rv = NullablePersistenceTypeFromText(aPersistenceType, &persistenceType);
if (NS_WARN_IF(NS_FAILED(rv))) {
return NS_ERROR_INVALID_ARG;
}
if (persistenceType.IsNull()) {
params.persistenceTypeIsExplicit() = false;
} else {
params.persistenceType() = persistenceType.Value();
params.persistenceTypeIsExplicit() = true;
}
params.clearAll() = aClearAll;
nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params));
rv = InitiateRequest(info);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
request.forget(_retval);
return NS_OK;
}
NS_IMETHODIMP
QuotaManagerService::Reset(nsIQuotaRequest** _retval)
{
MOZ_ASSERT(NS_IsMainThread());
if (NS_WARN_IF(!gTestingMode)) {
return NS_ERROR_UNEXPECTED;
}
RefPtr<Request> request = new Request();
ResetAllParams params;
nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params));
nsresult rv = InitiateRequest(info);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
request.forget(_retval);
return NS_OK;
}
NS_IMETHODIMP
QuotaManagerService::Persisted(nsIPrincipal* aPrincipal,
nsIQuotaRequest** _retval)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(_retval);
RefPtr<Request> request = new Request(aPrincipal);
PersistedParams params;
nsresult rv = CheckedPrincipalToPrincipalInfo(aPrincipal,
params.principalInfo());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params));
rv = InitiateRequest(info);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
request.forget(_retval);
return NS_OK;
}
NS_IMETHODIMP
QuotaManagerService::Persist(nsIPrincipal* aPrincipal,
nsIQuotaRequest** _retval)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(_retval);
RefPtr<Request> request = new Request(aPrincipal);
PersistParams params;
nsresult rv = CheckedPrincipalToPrincipalInfo(aPrincipal,
params.principalInfo());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params));
rv = InitiateRequest(info);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
request.forget(_retval);
return NS_OK;
}
NS_IMETHODIMP
QuotaManagerService::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aData)
{
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)) {
RemoveIdleObserver();
return NS_OK;
}
if (!strcmp(aTopic, "clear-origin-attributes-data")) {
RefPtr<Request> request = new Request();
ClearDataParams params;
params.pattern() = nsDependentString(aData);
nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params));
nsresult rv = InitiateRequest(info);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
if (!strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY)) {
PerformIdleMaintenance();
return NS_OK;
}
if (!strcmp(aTopic, OBSERVER_TOPIC_IDLE)) {
nsAutoPtr<PendingRequestInfo> info(
new IdleMaintenanceInfo(/* aStart */ true));
nsresult rv = InitiateRequest(info);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
if (!strcmp(aTopic, OBSERVER_TOPIC_ACTIVE)) {
RemoveIdleObserver();
nsAutoPtr<PendingRequestInfo> info(
new IdleMaintenanceInfo(/* aStart */ false));
nsresult rv = InitiateRequest(info);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
MOZ_ASSERT_UNREACHABLE("Should never get here!");
return NS_OK;
}
void
QuotaManagerService::Notify(const hal::BatteryInformation& aBatteryInfo)
{
// This notification is received when battery data changes. We don't need to
// deal with this notification.
}
NS_IMETHODIMP
AbortOperationsRunnable::Run()
{
AssertIsOnBackgroundThread();
if (QuotaManager::IsShuttingDown()) {
return NS_OK;
}
QuotaManager* quotaManager = QuotaManager::Get();
if (!quotaManager) {
return NS_OK;
}
quotaManager->AbortOperationsForProcess(mContentParentId);
return NS_OK;
}
nsresult
QuotaManagerService::
UsageRequestInfo::InitiateRequest(QuotaChild* aActor)
{
MOZ_ASSERT(aActor);
auto request = static_cast<UsageRequest*>(mRequest.get());
auto actor = new QuotaUsageRequestChild(request);
if (!aActor->SendPQuotaUsageRequestConstructor(actor, mParams)) {
request->SetError(NS_ERROR_FAILURE);
return NS_ERROR_FAILURE;
}
request->SetBackgroundActor(actor);
return NS_OK;
}
nsresult
QuotaManagerService::
RequestInfo::InitiateRequest(QuotaChild* aActor)
{
MOZ_ASSERT(aActor);
auto request = static_cast<Request*>(mRequest.get());
auto actor = new QuotaRequestChild(request);
if (!aActor->SendPQuotaRequestConstructor(actor, mParams)) {
request->SetError(NS_ERROR_FAILURE);
return NS_ERROR_FAILURE;
}
return NS_OK;
}
nsresult
QuotaManagerService::
IdleMaintenanceInfo::InitiateRequest(QuotaChild* aActor)
{
MOZ_ASSERT(aActor);
bool result;
if (mStart) {
result = aActor->SendStartIdleMaintenance();
} else {
result = aActor->SendStopIdleMaintenance();
}
if (!result) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
} // namespace quota
} // namespace dom
} // namespace mozilla