blob: d3315014be23c4c25fa391147279e66487adb4b6 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "storage/browser/quota/quota_manager.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <functional>
#include <limits>
#include <memory>
#include <utility>
#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/sequenced_task_runner.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/sys_info.h"
#include "base/task_runner_util.h"
#include "base/task_scheduler/post_task.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "net/base/url_util.h"
#include "storage/browser/quota/client_usage_tracker.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/browser/quota/quota_temporary_storage_evictor.h"
#include "storage/browser/quota/storage_monitor.h"
#include "storage/browser/quota/usage_tracker.h"
using blink::mojom::StorageType;
namespace storage {
namespace {
const int64_t kMBytes = 1024 * 1024;
const int kMinutesInMilliSeconds = 60 * 1000;
const int64_t kReportHistogramInterval = 60 * 60 * 1000; // 1 hour
#define UMA_HISTOGRAM_MBYTES(name, sample) \
UMA_HISTOGRAM_CUSTOM_COUNTS((name), static_cast<int>((sample) / kMBytes), 1, \
10 * 1024 * 1024 /* 10TB */, 100)
} // namespace
const int64_t QuotaManager::kNoLimit = INT64_MAX;
// Cap size for per-host persistent quota determined by the histogram.
// This is a bit lax value because the histogram says nothing about per-host
// persistent storage usage and we determined by global persistent storage
// usage that is less than 10GB for almost all users.
const int64_t QuotaManager::kPerHostPersistentQuotaLimit = 10 * 1024 * kMBytes;
// Heuristics: assuming average cloud server allows a few Gigs storage
// on the server side and the storage needs to be shared for user data
// and by multiple apps.
int64_t QuotaManager::kSyncableStorageDefaultHostQuota = 500 * kMBytes;
const char QuotaManager::kDatabaseName[] = "QuotaManager";
const int QuotaManager::kThresholdOfErrorsToBeBlacklisted = 3;
const int QuotaManager::kEvictionIntervalInMilliSeconds =
30 * kMinutesInMilliSeconds;
const char QuotaManager::kDaysBetweenRepeatedOriginEvictionsHistogram[] =
"Quota.DaysBetweenRepeatedOriginEvictions";
const char QuotaManager::kEvictedOriginAccessedCountHistogram[] =
"Quota.EvictedOriginAccessCount";
const char QuotaManager::kEvictedOriginDaysSinceAccessHistogram[] =
"Quota.EvictedOriginDaysSinceAccess";
namespace {
bool IsSupportedType(StorageType type) {
return type == StorageType::kTemporary || type == StorageType::kPersistent ||
type == StorageType::kSyncable;
}
bool IsSupportedIncognitoType(StorageType type) {
return type == StorageType::kTemporary || type == StorageType::kPersistent;
}
void CountOriginType(const std::set<GURL>& origins,
SpecialStoragePolicy* policy,
size_t* protected_origins,
size_t* unlimited_origins) {
DCHECK(protected_origins);
DCHECK(unlimited_origins);
*protected_origins = 0;
*unlimited_origins = 0;
if (!policy)
return;
for (const auto& origin : origins) {
if (policy->IsStorageProtected(origin))
++*protected_origins;
if (policy->IsStorageUnlimited(origin))
++*unlimited_origins;
}
}
bool GetPersistentHostQuotaOnDBThread(const std::string& host,
int64_t* quota,
QuotaDatabase* database) {
DCHECK(database);
database->GetHostQuota(host, StorageType::kPersistent, quota);
return true;
}
bool SetPersistentHostQuotaOnDBThread(const std::string& host,
int64_t* new_quota,
QuotaDatabase* database) {
DCHECK(database);
if (database->SetHostQuota(host, StorageType::kPersistent, *new_quota))
return true;
*new_quota = 0;
return false;
}
bool GetLRUOriginOnDBThread(StorageType type,
const std::set<GURL>& exceptions,
SpecialStoragePolicy* policy,
GURL* url,
QuotaDatabase* database) {
DCHECK(database);
database->GetLRUOrigin(type, exceptions, policy, url);
return true;
}
bool DeleteOriginInfoOnDBThread(const GURL& origin,
StorageType type,
bool is_eviction,
QuotaDatabase* database) {
DCHECK(database);
base::Time now = base::Time::Now();
if (is_eviction) {
QuotaDatabase::OriginInfoTableEntry entry;
database->GetOriginInfo(origin, type, &entry);
UMA_HISTOGRAM_COUNTS_1M(QuotaManager::kEvictedOriginAccessedCountHistogram,
entry.used_count);
UMA_HISTOGRAM_COUNTS_1000(
QuotaManager::kEvictedOriginDaysSinceAccessHistogram,
(now - entry.last_access_time).InDays());
}
if (!database->DeleteOriginInfo(origin, type))
return false;
// If the deletion is not due to an eviction, delete the entry in the eviction
// table as well due to privacy concerns.
if (!is_eviction)
return database->DeleteOriginLastEvictionTime(origin, type);
base::Time last_eviction_time;
database->GetOriginLastEvictionTime(origin, type, &last_eviction_time);
if (last_eviction_time != base::Time()) {
UMA_HISTOGRAM_COUNTS_1000(
QuotaManager::kDaysBetweenRepeatedOriginEvictionsHistogram,
(now - last_eviction_time).InDays());
}
return database->SetOriginLastEvictionTime(origin, type, now);
}
bool BootstrapDatabaseOnDBThread(const std::set<GURL>* origins,
QuotaDatabase* database) {
DCHECK(database);
if (database->IsOriginDatabaseBootstrapped())
return true;
// Register existing origins with 0 last time access.
if (database->RegisterInitialOriginInfo(*origins, StorageType::kTemporary)) {
database->SetOriginDatabaseBootstrapped(true);
return true;
}
return false;
}
bool UpdateAccessTimeOnDBThread(const GURL& origin,
StorageType type,
base::Time accessed_time,
QuotaDatabase* database) {
DCHECK(database);
return database->SetOriginLastAccessTime(origin, type, accessed_time);
}
bool UpdateModifiedTimeOnDBThread(const GURL& origin,
StorageType type,
base::Time modified_time,
QuotaDatabase* database) {
DCHECK(database);
return database->SetOriginLastModifiedTime(origin, type, modified_time);
}
void DidGetUsageAndQuotaForWebApps(
QuotaManager::UsageAndQuotaCallback callback,
blink::mojom::QuotaStatusCode status,
int64_t usage,
int64_t quota,
base::flat_map<QuotaClient::ID, int64_t> usage_breakdown) {
std::move(callback).Run(status, usage, quota);
}
} // namespace
class QuotaManager::UsageAndQuotaHelper : public QuotaTask {
public:
UsageAndQuotaHelper(QuotaManager* manager,
const GURL& origin,
StorageType type,
bool is_unlimited,
bool is_session_only,
bool is_incognito,
UsageAndQuotaWithBreakdownCallback callback)
: QuotaTask(manager),
origin_(origin),
callback_(std::move(callback)),
type_(type),
is_unlimited_(is_unlimited),
is_session_only_(is_session_only),
is_incognito_(is_incognito),
weak_factory_(this) {}
protected:
void Run() override {
// Start the async process of gathering the info we need.
// Gather 4 pieces of info before computing an answer:
// settings, device_storage_capacity, host_usage, and host_quota.
base::Closure barrier = base::BarrierClosure(
4, base::BindOnce(&UsageAndQuotaHelper::OnBarrierComplete,
weak_factory_.GetWeakPtr()));
std::string host = net::GetHostOrSpecFromURL(origin_);
manager()->GetQuotaSettings(
base::BindOnce(&UsageAndQuotaHelper::OnGotSettings,
weak_factory_.GetWeakPtr(), barrier));
manager()->GetStorageCapacity(
base::BindOnce(&UsageAndQuotaHelper::OnGotCapacity,
weak_factory_.GetWeakPtr(), barrier));
manager()->GetHostUsageWithBreakdown(
host, type_,
base::BindOnce(&UsageAndQuotaHelper::OnGotHostUsage,
weak_factory_.GetWeakPtr(), barrier));
// Determine host_quota differently depending on type.
if (is_unlimited_) {
SetDesiredHostQuota(barrier, blink::mojom::QuotaStatusCode::kOk,
kNoLimit);
} else if (type_ == StorageType::kSyncable) {
SetDesiredHostQuota(barrier, blink::mojom::QuotaStatusCode::kOk,
kSyncableStorageDefaultHostQuota);
} else if (type_ == StorageType::kPersistent) {
manager()->GetPersistentHostQuota(
host, base::BindOnce(&UsageAndQuotaHelper::SetDesiredHostQuota,
weak_factory_.GetWeakPtr(), barrier));
} else {
DCHECK_EQ(StorageType::kTemporary, type_);
// For temporary storage, OnGotSettings will set the host quota.
}
}
void Aborted() override {
weak_factory_.InvalidateWeakPtrs();
std::move(callback_).Run(blink::mojom::QuotaStatusCode::kErrorAbort, 0, 0,
base::flat_map<QuotaClient::ID, int64_t>());
DeleteSoon();
}
void Completed() override {
weak_factory_.InvalidateWeakPtrs();
// Constrain the desired |host_quota| to something that fits.
// If available space is too low, cap usage at current levels.
// If it's close to being too low, cap growth to avoid it getting too low.
int64_t host_quota =
std::min(desired_host_quota_,
host_usage_ +
std::max(INT64_C(0), available_space_ -
settings_.must_remain_available));
std::move(callback_).Run(blink::mojom::QuotaStatusCode::kOk, host_usage_,
host_quota, std::move(host_usage_breakdown_));
if (type_ == StorageType::kTemporary && !is_incognito_ && !is_unlimited_) {
UMA_HISTOGRAM_MBYTES("Quota.QuotaForOrigin", host_quota);
if (host_quota > 0) {
UMA_HISTOGRAM_PERCENTAGE("Quota.PercentUsedByOrigin",
std::min(100, static_cast<int>((host_usage_ * 100) / host_quota)));
}
}
DeleteSoon();
}
private:
QuotaManager* manager() const {
return static_cast<QuotaManager*>(observer());
}
void OnGotSettings(const base::Closure& barrier_closure,
const QuotaSettings& settings) {
settings_ = settings;
barrier_closure.Run();
if (type_ == StorageType::kTemporary && !is_unlimited_) {
int64_t host_quota = is_session_only_
? settings.session_only_per_host_quota
: settings.per_host_quota;
SetDesiredHostQuota(barrier_closure, blink::mojom::QuotaStatusCode::kOk,
host_quota);
}
}
void OnGotCapacity(const base::Closure& barrier_closure,
int64_t total_space,
int64_t available_space) {
total_space_ = total_space;
available_space_ = available_space;
barrier_closure.Run();
}
void OnGotHostUsage(
const base::Closure& barrier_closure,
int64_t usage,
base::flat_map<QuotaClient::ID, int64_t> usage_breakdown) {
host_usage_ = usage;
host_usage_breakdown_ = std::move(usage_breakdown);
barrier_closure.Run();
}
void SetDesiredHostQuota(const base::Closure& barrier_closure,
blink::mojom::QuotaStatusCode status,
int64_t quota) {
desired_host_quota_ = quota;
barrier_closure.Run();
}
void OnBarrierComplete() { CallCompleted(); }
GURL origin_;
QuotaManager::UsageAndQuotaWithBreakdownCallback callback_;
StorageType type_;
bool is_unlimited_;
bool is_session_only_;
bool is_incognito_;
int64_t available_space_ = 0;
int64_t total_space_ = 0;
int64_t desired_host_quota_ = 0;
int64_t host_usage_ = 0;
base::flat_map<QuotaClient::ID, int64_t> host_usage_breakdown_;
QuotaSettings settings_;
base::WeakPtrFactory<UsageAndQuotaHelper> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(UsageAndQuotaHelper);
};
// Helper to asynchronously gather information needed at the start of an
// eviction round.
class QuotaManager::EvictionRoundInfoHelper : public QuotaTask {
public:
EvictionRoundInfoHelper(QuotaManager* manager,
EvictionRoundInfoCallback callback)
: QuotaTask(manager),
callback_(std::move(callback)),
weak_factory_(this) {}
protected:
void Run() override {
// Gather 2 pieces of info before deciding if we need to get GlobalUsage:
// settings and device_storage_capacity.
base::RepeatingClosure barrier = base::BarrierClosure(
2, base::BindOnce(&EvictionRoundInfoHelper::OnBarrierComplete,
weak_factory_.GetWeakPtr()));
manager()->GetQuotaSettings(
base::BindOnce(&EvictionRoundInfoHelper::OnGotSettings,
weak_factory_.GetWeakPtr(), barrier));
manager()->GetStorageCapacity(
base::BindOnce(&EvictionRoundInfoHelper::OnGotCapacity,
weak_factory_.GetWeakPtr(), barrier));
}
void Aborted() override {
weak_factory_.InvalidateWeakPtrs();
std::move(callback_).Run(blink::mojom::QuotaStatusCode::kErrorAbort,
QuotaSettings(), 0, 0, 0, false);
DeleteSoon();
}
void Completed() override {
weak_factory_.InvalidateWeakPtrs();
std::move(callback_).Run(blink::mojom::QuotaStatusCode::kOk, settings_,
available_space_, total_space_, global_usage_,
global_usage_is_complete_);
DeleteSoon();
}
private:
QuotaManager* manager() const {
return static_cast<QuotaManager*>(observer());
}
void OnGotSettings(const base::Closure& barrier_closure,
const QuotaSettings& settings) {
settings_ = settings;
barrier_closure.Run();
}
void OnGotCapacity(const base::Closure& barrier_closure,
int64_t total_space,
int64_t available_space) {
total_space_ = total_space;
available_space_ = available_space;
barrier_closure.Run();
}
void OnBarrierComplete() {
// Avoid computing the full current_usage when there's no pressure.
int64_t consumed_space = total_space_ - available_space_;
if (consumed_space < settings_.pool_size &&
available_space_ > settings_.should_remain_available) {
DCHECK(!global_usage_is_complete_);
global_usage_ =
manager()->GetUsageTracker(StorageType::kTemporary)->GetCachedUsage();
CallCompleted();
return;
}
manager()->GetGlobalUsage(
StorageType::kTemporary,
base::BindOnce(&EvictionRoundInfoHelper::OnGotGlobalUsage,
weak_factory_.GetWeakPtr()));
}
void OnGotGlobalUsage(int64_t usage, int64_t unlimited_usage) {
global_usage_ = std::max(INT64_C(0), usage - unlimited_usage);
global_usage_is_complete_ = true;
if (total_space_ > 0) {
UMA_HISTOGRAM_PERCENTAGE("Quota.PercentUsedForTemporaryStorage",
std::min(100,
static_cast<int>((global_usage_ * 100) / total_space_)));
}
CallCompleted();
}
EvictionRoundInfoCallback callback_;
QuotaSettings settings_;
int64_t available_space_ = 0;
int64_t total_space_ = 0;
int64_t global_usage_ = 0;
bool global_usage_is_complete_ = false;
base::WeakPtrFactory<EvictionRoundInfoHelper> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(EvictionRoundInfoHelper);
};
class QuotaManager::GetUsageInfoTask : public QuotaTask {
public:
GetUsageInfoTask(QuotaManager* manager, GetUsageInfoCallback callback)
: QuotaTask(manager),
callback_(std::move(callback)),
weak_factory_(this) {}
protected:
void Run() override {
// crbug.com/349708
TRACE_EVENT0("io", "QuotaManager::GetUsageInfoTask::Run");
remaining_trackers_ = 3;
// This will populate cached hosts and usage info.
manager()
->GetUsageTracker(StorageType::kTemporary)
->GetGlobalUsage(base::BindOnce(&GetUsageInfoTask::DidGetGlobalUsage,
weak_factory_.GetWeakPtr(),
StorageType::kTemporary));
manager()
->GetUsageTracker(StorageType::kPersistent)
->GetGlobalUsage(base::BindOnce(&GetUsageInfoTask::DidGetGlobalUsage,
weak_factory_.GetWeakPtr(),
StorageType::kPersistent));
manager()
->GetUsageTracker(StorageType::kSyncable)
->GetGlobalUsage(base::BindOnce(&GetUsageInfoTask::DidGetGlobalUsage,
weak_factory_.GetWeakPtr(),
StorageType::kSyncable));
}
void Completed() override {
// crbug.com/349708
TRACE_EVENT0("io", "QuotaManager::GetUsageInfoTask::Completed");
std::move(callback_).Run(entries_);
DeleteSoon();
}
void Aborted() override {
std::move(callback_).Run(UsageInfoEntries());
DeleteSoon();
}
private:
void AddEntries(StorageType type, UsageTracker* tracker) {
std::map<std::string, int64_t> host_usage;
tracker->GetCachedHostsUsage(&host_usage);
for (const auto& host_usage_pair : host_usage) {
entries_.push_back(
UsageInfo(host_usage_pair.first, type, host_usage_pair.second));
}
if (--remaining_trackers_ == 0)
CallCompleted();
}
void DidGetGlobalUsage(StorageType type, int64_t, int64_t) {
DCHECK(manager()->GetUsageTracker(type));
AddEntries(type, manager()->GetUsageTracker(type));
}
QuotaManager* manager() const {
return static_cast<QuotaManager*>(observer());
}
GetUsageInfoCallback callback_;
UsageInfoEntries entries_;
int remaining_trackers_;
base::WeakPtrFactory<GetUsageInfoTask> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(GetUsageInfoTask);
};
class QuotaManager::OriginDataDeleter : public QuotaTask {
public:
OriginDataDeleter(QuotaManager* manager,
const GURL& origin,
StorageType type,
int quota_client_mask,
bool is_eviction,
StatusCallback callback)
: QuotaTask(manager),
origin_(origin),
type_(type),
quota_client_mask_(quota_client_mask),
error_count_(0),
remaining_clients_(-1),
skipped_clients_(0),
is_eviction_(is_eviction),
callback_(std::move(callback)),
weak_factory_(this) {}
protected:
void Run() override {
error_count_ = 0;
remaining_clients_ = manager()->clients_.size();
for (auto* client : manager()->clients_) {
if (quota_client_mask_ & client->id()) {
client->DeleteOriginData(
url::Origin::Create(origin_), type_,
base::BindOnce(&OriginDataDeleter::DidDeleteOriginData,
weak_factory_.GetWeakPtr()));
} else {
++skipped_clients_;
if (--remaining_clients_ == 0)
CallCompleted();
}
}
}
void Completed() override {
if (error_count_ == 0) {
// crbug.com/349708
TRACE_EVENT0("io", "QuotaManager::OriginDataDeleter::Completed Ok");
// Only remove the entire origin if we didn't skip any client types.
if (skipped_clients_ == 0)
manager()->DeleteOriginFromDatabase(origin_, type_, is_eviction_);
std::move(callback_).Run(blink::mojom::QuotaStatusCode::kOk);
} else {
// crbug.com/349708
TRACE_EVENT0("io", "QuotaManager::OriginDataDeleter::Completed Error");
std::move(callback_).Run(
blink::mojom::QuotaStatusCode::kErrorInvalidModification);
}
DeleteSoon();
}
void Aborted() override {
std::move(callback_).Run(blink::mojom::QuotaStatusCode::kErrorAbort);
DeleteSoon();
}
private:
void DidDeleteOriginData(blink::mojom::QuotaStatusCode status) {
DCHECK_GT(remaining_clients_, 0);
if (status != blink::mojom::QuotaStatusCode::kOk)
++error_count_;
if (--remaining_clients_ == 0)
CallCompleted();
}
QuotaManager* manager() const {
return static_cast<QuotaManager*>(observer());
}
GURL origin_;
StorageType type_;
int quota_client_mask_;
int error_count_;
int remaining_clients_;
int skipped_clients_;
bool is_eviction_;
StatusCallback callback_;
base::WeakPtrFactory<OriginDataDeleter> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(OriginDataDeleter);
};
class QuotaManager::HostDataDeleter : public QuotaTask {
public:
HostDataDeleter(QuotaManager* manager,
const std::string& host,
StorageType type,
int quota_client_mask,
StatusCallback callback)
: QuotaTask(manager),
host_(host),
type_(type),
quota_client_mask_(quota_client_mask),
error_count_(0),
remaining_clients_(-1),
remaining_deleters_(-1),
callback_(std::move(callback)),
weak_factory_(this) {}
protected:
void Run() override {
error_count_ = 0;
remaining_clients_ = manager()->clients_.size();
for (auto* client : manager()->clients_) {
client->GetOriginsForHost(
type_, host_,
base::BindOnce(&HostDataDeleter::DidGetOriginsForHost,
weak_factory_.GetWeakPtr()));
}
}
void Completed() override {
if (error_count_ == 0) {
// crbug.com/349708
TRACE_EVENT0("io", "QuotaManager::HostDataDeleter::Completed Ok");
std::move(callback_).Run(blink::mojom::QuotaStatusCode::kOk);
} else {
// crbug.com/349708
TRACE_EVENT0("io", "QuotaManager::HostDataDeleter::Completed Error");
std::move(callback_).Run(
blink::mojom::QuotaStatusCode::kErrorInvalidModification);
}
DeleteSoon();
}
void Aborted() override {
std::move(callback_).Run(blink::mojom::QuotaStatusCode::kErrorAbort);
DeleteSoon();
}
private:
void DidGetOriginsForHost(const std::set<url::Origin>& origins) {
DCHECK_GT(remaining_clients_, 0);
for (const auto& origin : origins)
origins_.insert(origin.GetURL());
if (--remaining_clients_ == 0) {
if (!origins_.empty())
ScheduleOriginsDeletion();
else
CallCompleted();
}
}
void ScheduleOriginsDeletion() {
remaining_deleters_ = origins_.size();
for (const auto& origin : origins_) {
OriginDataDeleter* deleter = new OriginDataDeleter(
manager(), origin, type_, quota_client_mask_, false,
base::BindOnce(&HostDataDeleter::DidDeleteOriginData,
weak_factory_.GetWeakPtr()));
deleter->Start();
}
}
void DidDeleteOriginData(blink::mojom::QuotaStatusCode status) {
DCHECK_GT(remaining_deleters_, 0);
if (status != blink::mojom::QuotaStatusCode::kOk)
++error_count_;
if (--remaining_deleters_ == 0)
CallCompleted();
}
QuotaManager* manager() const {
return static_cast<QuotaManager*>(observer());
}
std::string host_;
StorageType type_;
int quota_client_mask_;
std::set<GURL> origins_;
int error_count_;
int remaining_clients_;
int remaining_deleters_;
StatusCallback callback_;
base::WeakPtrFactory<HostDataDeleter> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(HostDataDeleter);
};
class QuotaManager::GetModifiedSinceHelper {
public:
bool GetModifiedSinceOnDBThread(StorageType type,
base::Time modified_since,
QuotaDatabase* database) {
DCHECK(database);
return database->GetOriginsModifiedSince(type, &origins_, modified_since);
}
void DidGetModifiedSince(const base::WeakPtr<QuotaManager>& manager,
GetOriginsCallback callback,
StorageType type,
bool success) {
if (!manager) {
// The operation was aborted.
std::move(callback).Run(std::set<GURL>(), type);
return;
}
manager->DidDatabaseWork(success);
std::move(callback).Run(origins_, type);
}
private:
std::set<GURL> origins_;
};
class QuotaManager::DumpQuotaTableHelper {
public:
bool DumpQuotaTableOnDBThread(QuotaDatabase* database) {
DCHECK(database);
return database->DumpQuotaTable(base::BindRepeating(
&DumpQuotaTableHelper::AppendEntry, base::Unretained(this)));
}
void DidDumpQuotaTable(const base::WeakPtr<QuotaManager>& manager,
DumpQuotaTableCallback callback,
bool success) {
if (!manager) {
// The operation was aborted.
std::move(callback).Run(QuotaTableEntries());
return;
}
manager->DidDatabaseWork(success);
std::move(callback).Run(entries_);
}
private:
bool AppendEntry(const QuotaTableEntry& entry) {
entries_.push_back(entry);
return true;
}
QuotaTableEntries entries_;
};
class QuotaManager::DumpOriginInfoTableHelper {
public:
bool DumpOriginInfoTableOnDBThread(QuotaDatabase* database) {
DCHECK(database);
return database->DumpOriginInfoTable(base::BindRepeating(
&DumpOriginInfoTableHelper::AppendEntry, base::Unretained(this)));
}
void DidDumpOriginInfoTable(const base::WeakPtr<QuotaManager>& manager,
DumpOriginInfoTableCallback callback,
bool success) {
if (!manager) {
// The operation was aborted.
std::move(callback).Run(OriginInfoTableEntries());
return;
}
manager->DidDatabaseWork(success);
std::move(callback).Run(entries_);
}
private:
bool AppendEntry(const OriginInfoTableEntry& entry) {
entries_.push_back(entry);
return true;
}
OriginInfoTableEntries entries_;
};
// QuotaManager ---------------------------------------------------------------
QuotaManager::QuotaManager(
bool is_incognito,
const base::FilePath& profile_path,
const scoped_refptr<base::SingleThreadTaskRunner>& io_thread,
const scoped_refptr<SpecialStoragePolicy>& special_storage_policy,
const GetQuotaSettingsFunc& get_settings_function)
: is_incognito_(is_incognito),
profile_path_(profile_path),
proxy_(new QuotaManagerProxy(this, io_thread)),
db_disabled_(false),
eviction_disabled_(false),
io_thread_(io_thread),
db_runner_(base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN})),
get_settings_function_(get_settings_function),
is_getting_eviction_origin_(false),
special_storage_policy_(special_storage_policy),
get_volume_info_fn_(&QuotaManager::GetVolumeInfo),
storage_monitor_(new StorageMonitor(this)),
weak_factory_(this) {
DCHECK_EQ(settings_.refresh_interval, base::TimeDelta::Max());
if (!get_settings_function.is_null()) {
// Reset the interval to ensure we use the get_settings_function
// the first times settings_ is needed.
settings_.refresh_interval = base::TimeDelta();
get_settings_task_runner_ = base::ThreadTaskRunnerHandle::Get();
}
}
void QuotaManager::SetQuotaSettings(const QuotaSettings& settings) {
settings_ = settings;
settings_timestamp_ = base::TimeTicks::Now();
}
void QuotaManager::GetUsageInfo(GetUsageInfoCallback callback) {
LazyInitialize();
GetUsageInfoTask* get_usage_info =
new GetUsageInfoTask(this, std::move(callback));
get_usage_info->Start();
}
void QuotaManager::GetUsageAndQuotaForWebApps(const GURL& origin,
StorageType type,
UsageAndQuotaCallback callback) {
GetUsageAndQuotaWithBreakdown(
origin, type,
base::BindOnce(&DidGetUsageAndQuotaForWebApps, std::move(callback)));
}
void QuotaManager::GetUsageAndQuotaWithBreakdown(
const GURL& origin,
StorageType type,
UsageAndQuotaWithBreakdownCallback callback) {
DCHECK(origin == origin.GetOrigin());
if (!IsSupportedType(type) ||
(is_incognito_ && !IsSupportedIncognitoType(type))) {
std::move(callback).Run(blink::mojom::QuotaStatusCode::kErrorNotSupported,
0, 0, base::flat_map<QuotaClient::ID, int64_t>());
return;
}
LazyInitialize();
bool is_session_only = type == StorageType::kTemporary &&
special_storage_policy_ &&
special_storage_policy_->IsStorageSessionOnly(origin);
UsageAndQuotaHelper* helper = new UsageAndQuotaHelper(
this, origin, type, IsStorageUnlimited(origin, type), is_session_only,
is_incognito_, std::move(callback));
helper->Start();
}
void QuotaManager::GetUsageAndQuota(const GURL& origin,
StorageType type,
UsageAndQuotaCallback callback) {
DCHECK(origin == origin.GetOrigin());
if (IsStorageUnlimited(origin, type)) {
// TODO(michaeln): This seems like a non-obvious odd behavior, probably for
// apps/extensions, but it would be good to eliminate this special case.
std::move(callback).Run(blink::mojom::QuotaStatusCode::kOk, 0, kNoLimit);
return;
}
GetUsageAndQuotaForWebApps(origin, type, std::move(callback));
}
void QuotaManager::NotifyStorageAccessed(
QuotaClient::ID client_id,
const GURL& origin, StorageType type) {
DCHECK(origin == origin.GetOrigin());
NotifyStorageAccessedInternal(client_id, origin, type, base::Time::Now());
}
void QuotaManager::NotifyStorageModified(QuotaClient::ID client_id,
const GURL& origin,
StorageType type,
int64_t delta) {
DCHECK(origin == origin.GetOrigin());
NotifyStorageModifiedInternal(client_id, origin, type, delta,
base::Time::Now());
}
void QuotaManager::NotifyOriginInUse(const GURL& origin) {
DCHECK(io_thread_->BelongsToCurrentThread());
origins_in_use_[origin]++;
}
void QuotaManager::NotifyOriginNoLongerInUse(const GURL& origin) {
DCHECK(io_thread_->BelongsToCurrentThread());
DCHECK(IsOriginInUse(origin));
int& count = origins_in_use_[origin];
if (--count == 0)
origins_in_use_.erase(origin);
}
void QuotaManager::SetUsageCacheEnabled(QuotaClient::ID client_id,
const GURL& origin,
StorageType type,
bool enabled) {
LazyInitialize();
DCHECK(GetUsageTracker(type));
GetUsageTracker(type)->SetUsageCacheEnabled(client_id, origin, enabled);
}
void QuotaManager::DeleteOriginData(const GURL& origin,
StorageType type,
int quota_client_mask,
StatusCallback callback) {
DeleteOriginDataInternal(origin, type, quota_client_mask, false,
std::move(callback));
}
void QuotaManager::DeleteHostData(const std::string& host,
StorageType type,
int quota_client_mask,
StatusCallback callback) {
LazyInitialize();
if (host.empty() || clients_.empty()) {
std::move(callback).Run(blink::mojom::QuotaStatusCode::kOk);
return;
}
HostDataDeleter* deleter = new HostDataDeleter(
this, host, type, quota_client_mask, std::move(callback));
deleter->Start();
}
void QuotaManager::GetPersistentHostQuota(const std::string& host,
QuotaCallback callback) {
LazyInitialize();
if (host.empty()) {
// This could happen if we are called on file:///.
// TODO(kinuko) We may want to respect --allow-file-access-from-files
// command line switch.
std::move(callback).Run(blink::mojom::QuotaStatusCode::kOk, 0);
return;
}
if (!persistent_host_quota_callbacks_.Add(host, std::move(callback)))
return;
int64_t* quota_ptr = new int64_t(0);
PostTaskAndReplyWithResultForDBThread(
FROM_HERE,
base::BindOnce(&GetPersistentHostQuotaOnDBThread, host,
base::Unretained(quota_ptr)),
base::BindOnce(&QuotaManager::DidGetPersistentHostQuota,
weak_factory_.GetWeakPtr(), host, base::Owned(quota_ptr)));
}
void QuotaManager::SetPersistentHostQuota(const std::string& host,
int64_t new_quota,
QuotaCallback callback) {
LazyInitialize();
if (host.empty()) {
// This could happen if we are called on file:///.
std::move(callback).Run(blink::mojom::QuotaStatusCode::kErrorNotSupported,
0);
return;
}
if (new_quota < 0) {
std::move(callback).Run(
blink::mojom::QuotaStatusCode::kErrorInvalidModification, -1);
return;
}
// Cap the requested size at the per-host quota limit.
new_quota = std::min(new_quota, kPerHostPersistentQuotaLimit);
if (db_disabled_) {
std::move(callback).Run(blink::mojom::QuotaStatusCode::kErrorInvalidAccess,
-1);
return;
}
int64_t* new_quota_ptr = new int64_t(new_quota);
PostTaskAndReplyWithResultForDBThread(
FROM_HERE,
base::BindOnce(&SetPersistentHostQuotaOnDBThread, host,
base::Unretained(new_quota_ptr)),
base::BindOnce(&QuotaManager::DidSetPersistentHostQuota,
weak_factory_.GetWeakPtr(), host, std::move(callback),
base::Owned(new_quota_ptr)));
}
void QuotaManager::GetGlobalUsage(StorageType type,
GlobalUsageCallback callback) {
LazyInitialize();
DCHECK(GetUsageTracker(type));
GetUsageTracker(type)->GetGlobalUsage(std::move(callback));
}
void QuotaManager::GetHostUsage(const std::string& host,
StorageType type,
UsageCallback callback) {
LazyInitialize();
DCHECK(GetUsageTracker(type));
GetUsageTracker(type)->GetHostUsage(host, std::move(callback));
}
void QuotaManager::GetHostUsage(const std::string& host,
StorageType type,
QuotaClient::ID client_id,
UsageCallback callback) {
LazyInitialize();
DCHECK(GetUsageTracker(type));
ClientUsageTracker* tracker =
GetUsageTracker(type)->GetClientTracker(client_id);
if (!tracker) {
std::move(callback).Run(0);
return;
}
tracker->GetHostUsage(host, std::move(callback));
}
void QuotaManager::GetHostUsageWithBreakdown(
const std::string& host,
StorageType type,
UsageWithBreakdownCallback callback) {
LazyInitialize();
DCHECK(GetUsageTracker(type));
GetUsageTracker(type)->GetHostUsageWithBreakdown(host, std::move(callback));
}
bool QuotaManager::IsTrackingHostUsage(StorageType type,
QuotaClient::ID client_id) const {
UsageTracker* tracker = GetUsageTracker(type);
return tracker && tracker->GetClientTracker(client_id);
}
void QuotaManager::GetStatistics(
std::map<std::string, std::string>* statistics) {
DCHECK(statistics);
if (temporary_storage_evictor_) {
std::map<std::string, int64_t> stats;
temporary_storage_evictor_->GetStatistics(&stats);
for (const auto& origin_usage_pair : stats) {
(*statistics)[origin_usage_pair.first] =
base::Int64ToString(origin_usage_pair.second);
}
}
}
bool QuotaManager::IsStorageUnlimited(const GURL& origin,
StorageType type) const {
// For syncable storage we should always enforce quota (since the
// quota must be capped by the server limit).
if (type == StorageType::kSyncable)
return false;
if (type == StorageType::kQuotaNotManaged)
return true;
return special_storage_policy_.get() &&
special_storage_policy_->IsStorageUnlimited(origin);
}
void QuotaManager::GetOriginsModifiedSince(StorageType type,
base::Time modified_since,
GetOriginsCallback callback) {
LazyInitialize();
GetModifiedSinceHelper* helper = new GetModifiedSinceHelper;
PostTaskAndReplyWithResultForDBThread(
FROM_HERE,
base::BindOnce(&GetModifiedSinceHelper::GetModifiedSinceOnDBThread,
base::Unretained(helper), type, modified_since),
base::BindOnce(&GetModifiedSinceHelper::DidGetModifiedSince,
base::Owned(helper), weak_factory_.GetWeakPtr(),
std::move(callback), type));
}
bool QuotaManager::ResetUsageTracker(StorageType type) {
DCHECK(GetUsageTracker(type));
if (GetUsageTracker(type)->IsWorking())
return false;
switch (type) {
case StorageType::kTemporary:
temporary_usage_tracker_.reset(new UsageTracker(
clients_, StorageType::kTemporary, special_storage_policy_.get(),
storage_monitor_.get()));
return true;
case StorageType::kPersistent:
persistent_usage_tracker_.reset(new UsageTracker(
clients_, StorageType::kPersistent, special_storage_policy_.get(),
storage_monitor_.get()));
return true;
case StorageType::kSyncable:
syncable_usage_tracker_.reset(new UsageTracker(
clients_, StorageType::kSyncable, special_storage_policy_.get(),
storage_monitor_.get()));
return true;
default:
NOTREACHED();
}
return true;
}
void QuotaManager::AddStorageObserver(
StorageObserver* observer, const StorageObserver::MonitorParams& params) {
DCHECK(observer);
storage_monitor_->AddObserver(observer, params);
}
void QuotaManager::RemoveStorageObserver(StorageObserver* observer) {
DCHECK(observer);
storage_monitor_->RemoveObserver(observer);
}
QuotaManager::~QuotaManager() {
proxy_->manager_ = NULL;
for (auto* client : clients_)
client->OnQuotaManagerDestroyed();
if (database_)
db_runner_->DeleteSoon(FROM_HERE, database_.release());
}
QuotaManager::EvictionContext::EvictionContext()
: evicted_type(StorageType::kUnknown) {}
QuotaManager::EvictionContext::~EvictionContext() = default;
void QuotaManager::LazyInitialize() {
DCHECK(io_thread_->BelongsToCurrentThread());
if (database_) {
// Already initialized.
return;
}
// Use an empty path to open an in-memory only databse for incognito.
database_.reset(new QuotaDatabase(is_incognito_ ? base::FilePath() :
profile_path_.AppendASCII(kDatabaseName)));
temporary_usage_tracker_.reset(
new UsageTracker(clients_, StorageType::kTemporary,
special_storage_policy_.get(), storage_monitor_.get()));
persistent_usage_tracker_.reset(
new UsageTracker(clients_, StorageType::kPersistent,
special_storage_policy_.get(), storage_monitor_.get()));
syncable_usage_tracker_.reset(
new UsageTracker(clients_, StorageType::kSyncable,
special_storage_policy_.get(), storage_monitor_.get()));
if (!is_incognito_) {
histogram_timer_.Start(
FROM_HERE, base::TimeDelta::FromMilliseconds(kReportHistogramInterval),
this, &QuotaManager::ReportHistogram);
}
base::PostTaskAndReplyWithResult(
db_runner_.get(), FROM_HERE,
base::BindOnce(&QuotaDatabase::IsOriginDatabaseBootstrapped,
base::Unretained(database_.get())),
base::BindOnce(&QuotaManager::FinishLazyInitialize,
weak_factory_.GetWeakPtr()));
}
void QuotaManager::FinishLazyInitialize(bool is_database_bootstrapped) {
is_database_bootstrapped_ = is_database_bootstrapped;
StartEviction();
}
void QuotaManager::BootstrapDatabaseForEviction(
GetOriginCallback did_get_origin_callback,
int64_t usage,
int64_t unlimited_usage) {
// The usage cache should be fully populated now so we can
// seed the database with origins we know about.
std::set<GURL>* origins = new std::set<GURL>;
temporary_usage_tracker_->GetCachedOrigins(origins);
PostTaskAndReplyWithResultForDBThread(
FROM_HERE,
base::BindOnce(&BootstrapDatabaseOnDBThread, base::Owned(origins)),
base::BindOnce(&QuotaManager::DidBootstrapDatabase,
weak_factory_.GetWeakPtr(),
std::move(did_get_origin_callback)));
}
void QuotaManager::DidBootstrapDatabase(
GetOriginCallback did_get_origin_callback,
bool success) {
is_database_bootstrapped_ = success;
DidDatabaseWork(success);
GetLRUOrigin(StorageType::kTemporary, std::move(did_get_origin_callback));
}
void QuotaManager::RegisterClient(QuotaClient* client) {
DCHECK(!database_.get());
clients_.push_back(client);
}
UsageTracker* QuotaManager::GetUsageTracker(StorageType type) const {
switch (type) {
case StorageType::kTemporary:
return temporary_usage_tracker_.get();
case StorageType::kPersistent:
return persistent_usage_tracker_.get();
case StorageType::kSyncable:
return syncable_usage_tracker_.get();
case StorageType::kQuotaNotManaged:
return NULL;
case StorageType::kUnknown:
NOTREACHED();
}
return NULL;
}
void QuotaManager::GetCachedOrigins(
StorageType type, std::set<GURL>* origins) {
DCHECK(origins);
LazyInitialize();
DCHECK(GetUsageTracker(type));
GetUsageTracker(type)->GetCachedOrigins(origins);
}
void QuotaManager::NotifyStorageAccessedInternal(
QuotaClient::ID client_id,
const GURL& origin, StorageType type,
base::Time accessed_time) {
LazyInitialize();
if (type == StorageType::kTemporary && is_getting_eviction_origin_) {
// Record the accessed origins while GetLRUOrigin task is runing
// to filter out them from eviction.
access_notified_origins_.insert(origin);
}
if (db_disabled_)
return;
PostTaskAndReplyWithResultForDBThread(
FROM_HERE,
base::BindOnce(&UpdateAccessTimeOnDBThread, origin, type, accessed_time),
base::BindOnce(&QuotaManager::DidDatabaseWork,
weak_factory_.GetWeakPtr()));
}
void QuotaManager::NotifyStorageModifiedInternal(QuotaClient::ID client_id,
const GURL& origin,
StorageType type,
int64_t delta,
base::Time modified_time) {
LazyInitialize();
DCHECK(GetUsageTracker(type));
GetUsageTracker(type)->UpdateUsageCache(client_id, origin, delta);
PostTaskAndReplyWithResultForDBThread(
FROM_HERE,
base::BindOnce(&UpdateModifiedTimeOnDBThread, origin, type,
modified_time),
base::BindOnce(&QuotaManager::DidDatabaseWork,
weak_factory_.GetWeakPtr()));
}
void QuotaManager::DumpQuotaTable(DumpQuotaTableCallback callback) {
DumpQuotaTableHelper* helper = new DumpQuotaTableHelper;
PostTaskAndReplyWithResultForDBThread(
FROM_HERE,
base::BindOnce(&DumpQuotaTableHelper::DumpQuotaTableOnDBThread,
base::Unretained(helper)),
base::BindOnce(&DumpQuotaTableHelper::DidDumpQuotaTable,
base::Owned(helper), weak_factory_.GetWeakPtr(),
std::move(callback)));
}
void QuotaManager::DumpOriginInfoTable(DumpOriginInfoTableCallback callback) {
DumpOriginInfoTableHelper* helper = new DumpOriginInfoTableHelper;
PostTaskAndReplyWithResultForDBThread(
FROM_HERE,
base::BindOnce(&DumpOriginInfoTableHelper::DumpOriginInfoTableOnDBThread,
base::Unretained(helper)),
base::BindOnce(&DumpOriginInfoTableHelper::DidDumpOriginInfoTable,
base::Owned(helper), weak_factory_.GetWeakPtr(),
std::move(callback)));
}
void QuotaManager::StartEviction() {
DCHECK(!temporary_storage_evictor_.get());
if (eviction_disabled_)
return;
temporary_storage_evictor_.reset(new QuotaTemporaryStorageEvictor(
this, kEvictionIntervalInMilliSeconds));
temporary_storage_evictor_->Start();
}
void QuotaManager::DeleteOriginFromDatabase(const GURL& origin,
StorageType type,
bool is_eviction) {
LazyInitialize();
if (db_disabled_)
return;
PostTaskAndReplyWithResultForDBThread(
FROM_HERE,
base::BindOnce(&DeleteOriginInfoOnDBThread, origin, type, is_eviction),
base::BindOnce(&QuotaManager::DidDatabaseWork,
weak_factory_.GetWeakPtr()));
}
void QuotaManager::DidOriginDataEvicted(blink::mojom::QuotaStatusCode status) {
DCHECK(io_thread_->BelongsToCurrentThread());
// We only try evict origins that are not in use, so basically
// deletion attempt for eviction should not fail. Let's record
// the origin if we get error and exclude it from future eviction
// if the error happens consistently (> kThresholdOfErrorsToBeBlacklisted).
if (status != blink::mojom::QuotaStatusCode::kOk)
origins_in_error_[eviction_context_.evicted_origin]++;
std::move(eviction_context_.evict_origin_data_callback).Run(status);
}
void QuotaManager::DeleteOriginDataInternal(const GURL& origin,
StorageType type,
int quota_client_mask,
bool is_eviction,
StatusCallback callback) {
LazyInitialize();
if (origin.is_empty() || clients_.empty()) {
std::move(callback).Run(blink::mojom::QuotaStatusCode::kOk);
return;
}
DCHECK(origin == origin.GetOrigin());
OriginDataDeleter* deleter = new OriginDataDeleter(
this, origin, type, quota_client_mask, is_eviction, std::move(callback));
deleter->Start();
}
void QuotaManager::ReportHistogram() {
DCHECK(!is_incognito_);
GetGlobalUsage(
StorageType::kTemporary,
base::BindOnce(&QuotaManager::DidGetTemporaryGlobalUsageForHistogram,
weak_factory_.GetWeakPtr()));
}
void QuotaManager::DidGetTemporaryGlobalUsageForHistogram(
int64_t usage,
int64_t unlimited_usage) {
UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfTemporaryStorage", usage);
std::set<GURL> origins;
GetCachedOrigins(StorageType::kTemporary, &origins);
size_t num_origins = origins.size();
size_t protected_origins = 0;
size_t unlimited_origins = 0;
CountOriginType(origins,
special_storage_policy_.get(),
&protected_origins,
&unlimited_origins);
UMA_HISTOGRAM_COUNTS_1M("Quota.NumberOfTemporaryStorageOrigins", num_origins);
UMA_HISTOGRAM_COUNTS_1M("Quota.NumberOfProtectedTemporaryStorageOrigins",
protected_origins);
UMA_HISTOGRAM_COUNTS_1M("Quota.NumberOfUnlimitedTemporaryStorageOrigins",
unlimited_origins);
GetGlobalUsage(
StorageType::kPersistent,
base::BindOnce(&QuotaManager::DidGetPersistentGlobalUsageForHistogram,
weak_factory_.GetWeakPtr()));
}
void QuotaManager::DidGetPersistentGlobalUsageForHistogram(
int64_t usage,
int64_t unlimited_usage) {
UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfPersistentStorage", usage);
std::set<GURL> origins;
GetCachedOrigins(StorageType::kPersistent, &origins);
size_t num_origins = origins.size();
size_t protected_origins = 0;
size_t unlimited_origins = 0;
CountOriginType(origins,
special_storage_policy_.get(),
&protected_origins,
&unlimited_origins);
UMA_HISTOGRAM_COUNTS_1M("Quota.NumberOfPersistentStorageOrigins",
num_origins);
UMA_HISTOGRAM_COUNTS_1M("Quota.NumberOfProtectedPersistentStorageOrigins",
protected_origins);
UMA_HISTOGRAM_COUNTS_1M("Quota.NumberOfUnlimitedPersistentStorageOrigins",
unlimited_origins);
// We DumpOriginInfoTable last to ensure the trackers caches are loaded.
DumpOriginInfoTable(
base::BindOnce(&QuotaManager::DidDumpOriginInfoTableForHistogram,
weak_factory_.GetWeakPtr()));
}
void QuotaManager::DidDumpOriginInfoTableForHistogram(
const OriginInfoTableEntries& entries) {
using UsageMap = std::map<GURL, int64_t>;
UsageMap usage_map;
GetUsageTracker(StorageType::kTemporary)->GetCachedOriginsUsage(&usage_map);
base::Time now = base::Time::Now();
for (const auto& info : entries) {
if (info.type != StorageType::kTemporary)
continue;
// Ignore stale database entries. If there is no map entry, the origin's
// data has been deleted.
UsageMap::const_iterator found = usage_map.find(info.origin);
if (found == usage_map.end() || found->second == 0)
continue;
base::TimeDelta age = now - std::max(info.last_access_time,
info.last_modified_time);
UMA_HISTOGRAM_COUNTS_1000("Quota.AgeOfOriginInDays", age.InDays());
int64_t kilobytes = std::max(found->second / INT64_C(1024), INT64_C(1));
base::Histogram::FactoryGet(
"Quota.AgeOfDataInDays", 1, 1000, 50,
base::HistogramBase::kUmaTargetedHistogramFlag)->
AddCount(age.InDays(),
base::saturated_cast<int>(kilobytes));
}
}
std::set<GURL> QuotaManager::GetEvictionOriginExceptions(
const std::set<GURL>& extra_exceptions) {
std::set<GURL> exceptions = extra_exceptions;
for (const auto& p : origins_in_use_) {
if (p.second > 0)
exceptions.insert(p.first);
}
for (const auto& p : origins_in_error_) {
if (p.second > QuotaManager::kThresholdOfErrorsToBeBlacklisted)
exceptions.insert(p.first);
}
return exceptions;
}
void QuotaManager::DidGetEvictionOrigin(GetOriginCallback callback,
const GURL& origin) {
// Make sure the returned origin is (still) not in the origin_in_use_ set
// and has not been accessed since we posted the task.
if (base::ContainsKey(origins_in_use_, origin) ||
base::ContainsKey(access_notified_origins_, origin)) {
std::move(callback).Run(GURL());
} else {
std::move(callback).Run(origin);
}
access_notified_origins_.clear();
is_getting_eviction_origin_ = false;
}
void QuotaManager::GetEvictionOrigin(StorageType type,
const std::set<GURL>& extra_exceptions,
int64_t global_quota,
GetOriginCallback callback) {
LazyInitialize();
// This must not be called while there's an in-flight task.
DCHECK(!is_getting_eviction_origin_);
is_getting_eviction_origin_ = true;
GetOriginCallback did_get_origin_callback =
base::BindOnce(&QuotaManager::DidGetEvictionOrigin,
weak_factory_.GetWeakPtr(), std::move(callback));
if (!is_database_bootstrapped_ && !eviction_disabled_) {
// Once bootstrapped, GetLRUOrigin will be called.
GetGlobalUsage(StorageType::kTemporary,
base::BindOnce(&QuotaManager::BootstrapDatabaseForEviction,
weak_factory_.GetWeakPtr(),
std::move(did_get_origin_callback)));
return;
}
GetLRUOrigin(type, std::move(did_get_origin_callback));
}
void QuotaManager::EvictOriginData(const GURL& origin,
StorageType type,
StatusCallback callback) {
DCHECK(io_thread_->BelongsToCurrentThread());
DCHECK_EQ(type, StorageType::kTemporary);
eviction_context_.evicted_origin = origin;
eviction_context_.evicted_type = type;
eviction_context_.evict_origin_data_callback = std::move(callback);
DeleteOriginDataInternal(origin, type, QuotaClient::kAllClientsMask, true,
base::BindOnce(&QuotaManager::DidOriginDataEvicted,
weak_factory_.GetWeakPtr()));
}
void QuotaManager::GetEvictionRoundInfo(EvictionRoundInfoCallback callback) {
DCHECK(io_thread_->BelongsToCurrentThread());
LazyInitialize();
EvictionRoundInfoHelper* helper =
new EvictionRoundInfoHelper(this, std::move(callback));
helper->Start();
}
void QuotaManager::GetLRUOrigin(StorageType type, GetOriginCallback callback) {
LazyInitialize();
// This must not be called while there's an in-flight task.
DCHECK(lru_origin_callback_.is_null());
lru_origin_callback_ = std::move(callback);
if (db_disabled_) {
std::move(lru_origin_callback_).Run(GURL());
return;
}
GURL* url = new GURL;
PostTaskAndReplyWithResultForDBThread(
FROM_HERE,
base::BindOnce(&GetLRUOriginOnDBThread, type,
GetEvictionOriginExceptions(std::set<GURL>()),
base::RetainedRef(special_storage_policy_),
base::Unretained(url)),
base::BindOnce(&QuotaManager::DidGetLRUOrigin, weak_factory_.GetWeakPtr(),
base::Owned(url)));
}
void QuotaManager::DidGetPersistentHostQuota(const std::string& host,
const int64_t* quota,
bool success) {
DidDatabaseWork(success);
persistent_host_quota_callbacks_.Run(
host, blink::mojom::QuotaStatusCode::kOk,
std::min(*quota, kPerHostPersistentQuotaLimit));
}
void QuotaManager::DidSetPersistentHostQuota(const std::string& host,
QuotaCallback callback,
const int64_t* new_quota,
bool success) {
DidDatabaseWork(success);
std::move(callback).Run(
success ? blink::mojom::QuotaStatusCode::kOk
: blink::mojom::QuotaStatusCode::kErrorInvalidAccess,
*new_quota);
}
void QuotaManager::DidGetLRUOrigin(const GURL* origin,
bool success) {
DidDatabaseWork(success);
std::move(lru_origin_callback_).Run(*origin);
}
namespace {
void DidGetSettingsThreadAdapter(base::TaskRunner* task_runner,
OptionalQuotaSettingsCallback callback,
base::Optional<QuotaSettings> settings) {
task_runner->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(settings)));
}
} // namespace
void QuotaManager::GetQuotaSettings(QuotaSettingsCallback callback) {
if (base::TimeTicks::Now() - settings_timestamp_ <
settings_.refresh_interval) {
std::move(callback).Run(settings_);
return;
}
if (!settings_callbacks_.Add(std::move(callback)))
return;
// We invoke our clients GetQuotaSettingsFunc on the
// UI thread and plumb the resulting value back to this thread.
get_settings_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
get_settings_function_,
base::BindOnce(&DidGetSettingsThreadAdapter,
base::RetainedRef(base::ThreadTaskRunnerHandle::Get()),
base::BindOnce(&QuotaManager::DidGetSettings,
weak_factory_.GetWeakPtr(),
base::TimeTicks::Now()))));
}
void QuotaManager::DidGetSettings(base::TimeTicks start_ticks,
base::Optional<QuotaSettings> settings) {
if (!settings) {
settings = settings_;
settings->refresh_interval = base::TimeDelta::FromMinutes(1);
}
SetQuotaSettings(*settings);
settings_callbacks_.Run(*settings);
UMA_HISTOGRAM_MBYTES("Quota.GlobalTemporaryPoolSize", settings->pool_size);
UMA_HISTOGRAM_LONG_TIMES("Quota.TimeToGetSettings",
base::TimeTicks::Now() - start_ticks);
LOG_IF(WARNING, settings->pool_size == 0)
<< "No storage quota provided in QuotaSettings.";
}
void QuotaManager::GetStorageCapacity(StorageCapacityCallback callback) {
if (!storage_capacity_callbacks_.Add(std::move(callback)))
return;
if (is_incognito_) {
GetQuotaSettings(
base::BindOnce(&QuotaManager::ContinueIncognitoGetStorageCapacity,
weak_factory_.GetWeakPtr()));
return;
}
base::PostTaskAndReplyWithResult(
db_runner_.get(), FROM_HERE,
base::BindOnce(&QuotaManager::CallGetVolumeInfo, get_volume_info_fn_,
profile_path_),
base::BindOnce(&QuotaManager::DidGetStorageCapacity,
weak_factory_.GetWeakPtr()));
}
void QuotaManager::ContinueIncognitoGetStorageCapacity(
const QuotaSettings& settings) {
int64_t current_usage =
GetUsageTracker(StorageType::kTemporary)->GetCachedUsage();
current_usage += GetUsageTracker(StorageType::kPersistent)->GetCachedUsage();
int64_t available_space =
std::max(INT64_C(0), settings.pool_size - current_usage);
DidGetStorageCapacity(std::make_tuple(settings.pool_size, available_space));
}
void QuotaManager::DidGetStorageCapacity(
const std::tuple<int64_t, int64_t>& total_and_available) {
storage_capacity_callbacks_.Run(std::get<0>(total_and_available),
std::get<1>(total_and_available));
}
void QuotaManager::DidDatabaseWork(bool success) {
db_disabled_ = !success;
}
void QuotaManager::DeleteOnCorrectThread() const {
if (!io_thread_->BelongsToCurrentThread() &&
io_thread_->DeleteSoon(FROM_HERE, this)) {
return;
}
delete this;
}
void QuotaManager::PostTaskAndReplyWithResultForDBThread(
const base::Location& from_here,
base::OnceCallback<bool(QuotaDatabase*)> task,
base::OnceCallback<void(bool)> reply) {
// Deleting manager will post another task to DB sequence to delete
// |database_|, therefore we can be sure that database_ is alive when this
// task runs.
base::PostTaskAndReplyWithResult(
db_runner_.get(), from_here,
base::BindOnce(std::move(task), base::Unretained(database_.get())),
std::move(reply));
}
// static
std::tuple<int64_t, int64_t> QuotaManager::CallGetVolumeInfo(
GetVolumeInfoFn get_volume_info_fn,
const base::FilePath& path) {
// crbug.com/349708
TRACE_EVENT0("io", "CallGetVolumeInfo");
if (!base::CreateDirectory(path)) {
LOG(WARNING) << "Create directory failed for path" << path.value();
return std::make_tuple<int64_t, int64_t>(0, 0);
}
int64_t total;
int64_t available;
std::tie(total, available) = get_volume_info_fn(path);
if (total < 0 || available < 0) {
LOG(WARNING) << "Unable to get volume info: " << path.value();
return std::make_tuple<int64_t, int64_t>(0, 0);
}
UMA_HISTOGRAM_MBYTES("Quota.TotalDiskSpace", total);
UMA_HISTOGRAM_MBYTES("Quota.AvailableDiskSpace", available);
if (total > 0) {
UMA_HISTOGRAM_PERCENTAGE("Quota.PercentDiskAvailable",
std::min(100, static_cast<int>((available * 100) / total)));
}
return std::make_tuple(total, available);
}
// static
std::tuple<int64_t, int64_t> QuotaManager::GetVolumeInfo(
const base::FilePath& path) {
return std::make_tuple(base::SysInfo::AmountOfTotalDiskSpace(path),
base::SysInfo::AmountOfFreeDiskSpace(path));
}
} // namespace storage