[go: nahoru, domu]

blob: d2fc7d79b161fed5fa440e054d2ccf9bc47563f2 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "storage/browser/quota/client_usage_tracker.h"
#include <stdint.h>
#include <iterator>
#include "base/barrier_closure.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
namespace storage {
namespace {
void RecordSkippedOriginHistogram(const InvalidOriginReason reason) {
base::UmaHistogramEnumeration("Quota.SkippedInvalidOriginUsage", reason);
}
} // namespace
struct ClientUsageTracker::AccumulateInfo {
int64_t limited_usage = 0;
int64_t unlimited_usage = 0;
};
ClientUsageTracker::ClientUsageTracker(
UsageTracker* tracker,
mojom::QuotaClient* client,
blink::mojom::StorageType type,
scoped_refptr<SpecialStoragePolicy> special_storage_policy)
: client_(client),
type_(type),
special_storage_policy_(std::move(special_storage_policy)) {
DCHECK(client_);
if (special_storage_policy_.get())
special_storage_policy_->AddObserver(this);
}
ClientUsageTracker::~ClientUsageTracker() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (special_storage_policy_.get())
special_storage_policy_->RemoveObserver(this);
}
void ClientUsageTracker::GetBucketsUsage(const std::set<BucketLocator>& buckets,
UsageCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GT(buckets.size(), 0u);
auto info = std::make_unique<AccumulateInfo>();
auto* info_ptr = info.get();
base::RepeatingClosure barrier = base::BarrierClosure(
buckets.size(),
base::BindOnce(&ClientUsageTracker::FinallySendBucketsUsage,
weak_factory_.GetWeakPtr(), std::move(callback),
std::move(info)));
for (const auto& bucket : buckets) {
// TODO(https://crbug.com/941480): `storage_key` should not be opaque or
// have an empty url, but sometimes it is.
if (bucket.storage_key.origin().opaque()) {
DVLOG(1) << "GetBucketsUsage for opaque storage_key!";
RecordSkippedOriginHistogram(InvalidOriginReason::kIsOpaque);
barrier.Run();
continue;
}
if (bucket.storage_key.origin().GetURL().is_empty()) {
DVLOG(1) << "GetBucketsUsage for storage_key with empty url!";
RecordSkippedOriginHistogram(InvalidOriginReason::kIsEmpty);
barrier.Run();
continue;
}
// Use a cached usage value, if we have one.
int64_t cached_usage = GetCachedBucketUsage(bucket);
if (cached_usage != -1) {
AccumulateBucketsUsage(barrier, bucket, info_ptr, cached_usage);
continue;
}
client_->GetBucketUsage(
bucket,
// base::Unretained usage is safe here because barrier holds the
// std::unque_ptr that keeps AccumulateInfo alive, and the barrier
// will outlive all the AccumulateClientGlobalUsage closures.
base::BindOnce(&ClientUsageTracker::AccumulateBucketsUsage,
weak_factory_.GetWeakPtr(), barrier, bucket,
base::Unretained(info_ptr)));
}
}
void ClientUsageTracker::UpdateBucketUsageCache(const BucketLocator& bucket,
absl::optional<int64_t> delta) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!IsUsageCacheEnabledForStorageKey(bucket.storage_key)) {
return;
}
auto bucket_it = cached_bucket_usage_.find(bucket);
if (bucket_it == cached_bucket_usage_.end()) {
return;
}
if (delta.has_value()) {
// Constrain `delta` to avoid negative usage values.
// TODO(crbug.com/463729): At least one storage API sends deltas that
// result in negative total usage. The line below works around this bug.
// Fix the bug, and remove the workaround.
bucket_it->second += std::max(*delta, -bucket_it->second);
} else {
cached_bucket_usage_.erase(bucket_it);
}
}
void ClientUsageTracker::DeleteBucketCache(const BucketLocator& bucket) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
cached_bucket_usage_.erase(bucket);
}
int64_t ClientUsageTracker::GetCachedUsage() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
int64_t usage = 0;
for (const auto& bucket_and_usage : cached_bucket_usage_)
usage += bucket_and_usage.second;
return usage;
}
const ClientUsageTracker::BucketUsageMap&
ClientUsageTracker::GetCachedBucketsUsage() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return cached_bucket_usage_;
}
void ClientUsageTracker::SetUsageCacheEnabled(
const blink::StorageKey& storage_key,
bool enabled) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (enabled) {
non_cached_limited_storage_keys_.erase(storage_key);
non_cached_unlimited_storage_keys_.erase(storage_key);
return;
}
// Find all buckets for `storage_key` in `cached_bucket_usage_`
// and remove them from the cache. Erases cached bucket usage while iterating.
for (auto it = cached_bucket_usage_.cbegin();
it != cached_bucket_usage_.cend();) {
if (it->first.storage_key == storage_key) {
// Calling erase() while iterating is safe because (1) std::map::erase()
// only invalidates the iterator pointing to the erased element, and (2)
// `it` is advanced off of the erased element before erase() is called.
cached_bucket_usage_.erase(it++);
} else {
++it;
}
}
// Add to `non_cached_*_storage_keys_` to exclude `storage_key` from quota
// restrictions.
if (IsStorageUnlimited(storage_key)) {
non_cached_unlimited_storage_keys_.insert(storage_key);
} else {
non_cached_limited_storage_keys_.insert(storage_key);
}
}
bool ClientUsageTracker::IsUsageCacheEnabledForStorageKey(
const blink::StorageKey& storage_key) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return !base::Contains(non_cached_limited_storage_keys_, storage_key) &&
!base::Contains(non_cached_unlimited_storage_keys_, storage_key);
}
void ClientUsageTracker::AccumulateBucketsUsage(
base::OnceClosure barrier_callback,
const BucketLocator& bucket,
AccumulateInfo* info,
int64_t usage) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Defend against confusing inputs from clients.
// TODO(crbug.com/1292210): Remove this check after fixing QuotaClients.
if (usage < 0)
usage = 0;
if (IsStorageUnlimited(bucket.storage_key)) {
info->unlimited_usage += usage;
} else {
info->limited_usage += usage;
}
if (IsUsageCacheEnabledForStorageKey(bucket.storage_key))
CacheBucketUsage(bucket, usage);
std::move(barrier_callback).Run();
}
void ClientUsageTracker::FinallySendBucketsUsage(
UsageCallback callback,
std::unique_ptr<AccumulateInfo> info) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GE(info->limited_usage, 0);
DCHECK_GE(info->unlimited_usage, 0);
std::move(callback).Run(info->limited_usage + info->unlimited_usage,
info->unlimited_usage);
}
void ClientUsageTracker::CacheBucketUsage(const BucketLocator& bucket,
int64_t new_usage) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsUsageCacheEnabledForStorageKey(bucket.storage_key));
cached_bucket_usage_[bucket] = new_usage;
}
int64_t ClientUsageTracker::GetCachedBucketUsage(
const BucketLocator& bucket) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto bucket_it = cached_bucket_usage_.find(bucket);
if (bucket_it == cached_bucket_usage_.end())
return -1;
return bucket_it->second;
}
void ClientUsageTracker::GetBucketUsage(const BucketLocator& bucket,
UsageCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
client_->GetBucketUsage(
bucket,
base::BindOnce(&ClientUsageTracker::DidGetBucketUsage,
weak_factory_.GetWeakPtr(), bucket, std::move(callback)));
return;
}
void ClientUsageTracker::DidGetBucketUsage(const BucketLocator& bucket,
UsageCallback callback,
int64_t usage) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (IsUsageCacheEnabledForStorageKey(bucket.storage_key))
CacheBucketUsage(bucket, usage);
int64_t unlimited_usage = IsStorageUnlimited(bucket.storage_key) ? usage : 0;
std::move(callback).Run(usage, unlimited_usage);
}
void ClientUsageTracker::OnGranted(const url::Origin& origin_url,
int change_flags) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(crbug.com/1215208): Remove this conversion once the storage policy
// APIs are converted to use StorageKey instead of Origin.
const blink::StorageKey storage_key =
blink::StorageKey::CreateFirstParty(origin_url);
if (change_flags & SpecialStoragePolicy::STORAGE_UNLIMITED) {
if (non_cached_limited_storage_keys_.erase(storage_key))
non_cached_unlimited_storage_keys_.insert(storage_key);
}
}
void ClientUsageTracker::OnRevoked(const url::Origin& origin_url,
int change_flags) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(crbug.com/1215208): Remove this conversion once the storage policy
// APIs are converted to use StorageKey instead of Origin.
const blink::StorageKey storage_key =
blink::StorageKey::CreateFirstParty(origin_url);
if (change_flags & SpecialStoragePolicy::STORAGE_UNLIMITED) {
if (non_cached_unlimited_storage_keys_.erase(storage_key))
non_cached_limited_storage_keys_.insert(storage_key);
}
}
void ClientUsageTracker::OnCleared() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
non_cached_limited_storage_keys_.insert(
std::make_move_iterator(non_cached_unlimited_storage_keys_.begin()),
std::make_move_iterator(non_cached_unlimited_storage_keys_.end()));
non_cached_unlimited_storage_keys_.clear();
}
bool ClientUsageTracker::IsStorageUnlimited(
const blink::StorageKey& storage_key) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (type_ == blink::mojom::StorageType::kSyncable)
return false;
return special_storage_policy_.get() &&
special_storage_policy_->IsStorageUnlimited(
storage_key.origin().GetURL());
}
} // namespace storage