blob: 87fd0d5690ca70b6c71f14748a9f5301a5a56c70 [file] [log] [blame]
// Copyright 2014 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/client_usage_tracker.h"
#include <stdint.h>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/containers/contains.h"
#include "base/metrics/histogram_macros.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
namespace storage {
namespace {
using StorageKeySetByHost = ClientUsageTracker::StorageKeySetByHost;
void DidGetHostUsage(UsageCallback callback,
int64_t limited_usage,
int64_t unlimited_usage) {
DCHECK_GE(limited_usage, 0);
DCHECK_GE(unlimited_usage, 0);
std::move(callback).Run(limited_usage + unlimited_usage);
}
bool EraseStorageKeyFromStorageKeySet(StorageKeySetByHost* storage_keys_by_host,
const std::string& host,
const blink::StorageKey& storage_key) {
auto it = storage_keys_by_host->find(host);
if (it == storage_keys_by_host->end())
return false;
if (!it->second.erase(storage_key))
return false;
if (it->second.empty())
storage_keys_by_host->erase(host);
return true;
}
bool StorageKeySetContainsStorageKey(const StorageKeySetByHost& storage_keys,
const std::string& host,
const blink::StorageKey& storage_key) {
auto itr = storage_keys.find(host);
return itr != storage_keys.end() && base::Contains(itr->second, storage_key);
}
void RecordSkippedOriginHistogram(const InvalidOriginReason reason) {
UMA_HISTOGRAM_ENUMERATION("Quota.SkippedInvalidOriginUsage", reason);
}
} // namespace
struct ClientUsageTracker::AccumulateInfo {
AccumulateInfo() = default;
~AccumulateInfo() = default;
AccumulateInfo(const AccumulateInfo&) = delete;
AccumulateInfo& operator=(const AccumulateInfo&) = delete;
size_t pending_jobs = 0;
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),
global_limited_usage_(0),
global_unlimited_usage_(0),
global_usage_retrieved_(false),
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::GetGlobalUsage(GlobalUsageCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (global_usage_retrieved_ &&
non_cached_limited_storage_keys_by_host_.empty() &&
non_cached_unlimited_storage_keys_by_host_.empty()) {
std::move(callback).Run(global_limited_usage_ + global_unlimited_usage_,
global_unlimited_usage_);
return;
}
client_->GetStorageKeysForType(
type_,
base::BindOnce(&ClientUsageTracker::DidGetStorageKeysForGlobalUsage,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void ClientUsageTracker::GetHostUsage(const std::string& host,
UsageCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (base::Contains(cached_hosts_, host) &&
!base::Contains(non_cached_limited_storage_keys_by_host_, host) &&
!base::Contains(non_cached_unlimited_storage_keys_by_host_, host)) {
// TODO(kinuko): Drop host_usage_map_ cache periodically.
std::move(callback).Run(GetCachedHostUsage(host));
return;
}
if (!host_usage_accumulators_.Add(
host, base::BindOnce(&DidGetHostUsage, std::move(callback))))
return;
client_->GetStorageKeysForHost(
type_, host,
base::BindOnce(&ClientUsageTracker::DidGetStorageKeysForHostUsage,
weak_factory_.GetWeakPtr(), host));
}
void ClientUsageTracker::UpdateUsageCache(const blink::StorageKey& storage_key,
int64_t delta) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const std::string& host = storage_key.origin().host();
if (base::Contains(cached_hosts_, host)) {
if (!IsUsageCacheEnabledForStorageKey(storage_key))
return;
// Constrain |delta| to avoid negative usage values.
// TODO(michaeln): crbug/463729
delta = std::max(delta, -cached_usage_by_host_[host][storage_key]);
cached_usage_by_host_[host][storage_key] += delta;
UpdateGlobalUsageValue(IsStorageUnlimited(storage_key)
? &global_unlimited_usage_
: &global_limited_usage_,
delta);
return;
}
// We call GetHostUsage() so that the cache still updates, but we don't need
// to do anything else with the usage so we do not pass a callback.
GetHostUsage(host, base::DoNothing());
}
int64_t ClientUsageTracker::GetCachedUsage() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
int64_t usage = 0;
for (const auto& host_and_usage_map : cached_usage_by_host_) {
for (const auto& storage_key_and_usage : host_and_usage_map.second)
usage += storage_key_and_usage.second;
}
return usage;
}
std::map<std::string, int64_t> ClientUsageTracker::GetCachedHostsUsage() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::map<std::string, int64_t> host_usage;
for (const auto& host_and_usage_map : cached_usage_by_host_) {
const std::string& host = host_and_usage_map.first;
host_usage[host] += GetCachedHostUsage(host);
}
return host_usage;
}
std::map<blink::StorageKey, int64_t>
ClientUsageTracker::GetCachedStorageKeysUsage() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::map<blink::StorageKey, int64_t> storage_key_usage;
for (const auto& host_and_usage_map : cached_usage_by_host_) {
for (const auto& storage_key_and_usage : host_and_usage_map.second)
storage_key_usage[storage_key_and_usage.first] +=
storage_key_and_usage.second;
}
return storage_key_usage;
}
std::set<blink::StorageKey> ClientUsageTracker::GetCachedStorageKeys() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::set<blink::StorageKey> storage_keys;
for (const auto& host_and_usage_map : cached_usage_by_host_) {
for (const auto& storage_key_and_usage : host_and_usage_map.second)
storage_keys.insert(storage_key_and_usage.first);
}
return storage_keys;
}
void ClientUsageTracker::SetUsageCacheEnabled(
const blink::StorageKey& storage_key,
bool enabled) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const std::string& host = storage_key.origin().host();
if (!enabled) {
// Erase `storage_key` from cache and subtract its usage.
auto host_it = cached_usage_by_host_.find(host);
if (host_it != cached_usage_by_host_.end()) {
UsageMap& cached_usage_for_host = host_it->second;
auto storage_key_it = cached_usage_for_host.find(storage_key);
if (storage_key_it != cached_usage_for_host.end()) {
int64_t usage = storage_key_it->second;
UpdateUsageCache(storage_key, -usage);
cached_usage_for_host.erase(storage_key_it);
if (cached_usage_for_host.empty()) {
cached_usage_by_host_.erase(host_it);
cached_hosts_.erase(host);
}
}
}
if (IsStorageUnlimited(storage_key))
non_cached_unlimited_storage_keys_by_host_[host].insert(storage_key);
else
non_cached_limited_storage_keys_by_host_[host].insert(storage_key);
} else {
// Erase `storage_key` from `non_cached_storage_keys_` and invalidate the
// usage cache for the host.
if (EraseStorageKeyFromStorageKeySet(
&non_cached_limited_storage_keys_by_host_, host, storage_key) ||
EraseStorageKeyFromStorageKeySet(
&non_cached_unlimited_storage_keys_by_host_, host, storage_key)) {
cached_hosts_.erase(host);
global_usage_retrieved_ = false;
}
}
}
void ClientUsageTracker::DidGetStorageKeysForGlobalUsage(
GlobalUsageCallback callback,
const std::vector<blink::StorageKey>& storage_keys) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::map<std::string, std::vector<blink::StorageKey>> storage_keys_by_host;
for (const auto& storage_key : storage_keys)
storage_keys_by_host[storage_key.origin().host()].push_back(storage_key);
AccumulateInfo* info = new AccumulateInfo;
// Getting host usage may synchronously return the result if the usage is
// cached, which may in turn dispatch the completion callback before we finish
// looping over all hosts (because info->pending_jobs may reach 0 during the
// loop). To avoid this, we add one more pending host as a sentinel and
// fire the sentinel callback at the end.
info->pending_jobs = storage_keys_by_host.size() + 1;
auto accumulator = base::BindRepeating(
&ClientUsageTracker::AccumulateHostUsage, weak_factory_.GetWeakPtr(),
base::Owned(info),
// The `accumulator` is called multiple times, but the `callback` inside
// of it will only be called a single time, so we give ownership to the
// `accumulator` itself.
base::OwnedRef(std::move(callback)));
for (const auto& host_and_storage_keys : storage_keys_by_host) {
const std::string& host = host_and_storage_keys.first;
const std::vector<blink::StorageKey>& storage_keys_for_host =
host_and_storage_keys.second;
if (host_usage_accumulators_.Add(host, accumulator))
GetUsageForStorageKeys(host, storage_keys_for_host);
}
// Fire the sentinel as we've now called GetUsageForStorageKeys for all
// clients.
std::move(accumulator).Run(0, 0);
}
void ClientUsageTracker::AccumulateHostUsage(AccumulateInfo* info,
GlobalUsageCallback& callback,
int64_t limited_usage,
int64_t unlimited_usage) {
DCHECK_GT(info->pending_jobs, 0U);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
info->limited_usage += limited_usage;
info->unlimited_usage += unlimited_usage;
if (--info->pending_jobs)
return;
DCHECK_GE(info->limited_usage, 0);
DCHECK_GE(info->unlimited_usage, 0);
global_usage_retrieved_ = true;
std::move(callback).Run(info->limited_usage + info->unlimited_usage,
info->unlimited_usage);
}
void ClientUsageTracker::DidGetStorageKeysForHostUsage(
const std::string& host,
const std::vector<blink::StorageKey>& storage_keys) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
GetUsageForStorageKeys(host, storage_keys);
}
void ClientUsageTracker::GetUsageForStorageKeys(
const std::string& host,
const std::vector<blink::StorageKey>& storage_keys) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AccumulateInfo* info = new AccumulateInfo;
// Getting storage_key usage may synchronously return the result if the usage
// is cached, which may in turn dispatch the completion callback before we
// finish looping over all storage_keys (because info->pending_jobs may reach
// 0 during the loop). To avoid this, we add one more pending storage_key as
// a sentinel and fire the sentinel callback at the end.
info->pending_jobs = storage_keys.size() + 1;
auto accumulator =
base::BindRepeating(&ClientUsageTracker::AccumulateStorageKeyUsage,
weak_factory_.GetWeakPtr(), base::Owned(info), host);
for (const auto& storage_key : storage_keys) {
DCHECK_EQ(host, storage_key.origin().host());
int64_t storage_key_usage = 0;
if (GetCachedStorageKeyUsage(storage_key, &storage_key_usage)) {
accumulator.Run(storage_key, storage_key_usage);
} else {
client_->GetStorageKeyUsage(storage_key, type_,
base::BindOnce(accumulator, storage_key));
}
}
// Fire the sentinel as we've now called GetStorageKeyUsage for all clients.
accumulator.Run(absl::nullopt, 0);
}
void ClientUsageTracker::AccumulateStorageKeyUsage(
AccumulateInfo* info,
const std::string& host,
const absl::optional<blink::StorageKey>& storage_key,
int64_t usage) {
DCHECK_GT(info->pending_jobs, 0U);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (storage_key.has_value()) {
// TODO(https://crbug.com/941480): `storage_key` should not be opaque or
// have an empty url, but sometimes it is.
if (storage_key->origin().opaque()) {
DVLOG(1) << "AccumulateStorageKeyUsage for opaque storage_key!";
RecordSkippedOriginHistogram(InvalidOriginReason::kIsOpaque);
} else if (storage_key->origin().GetURL().is_empty()) {
DVLOG(1) << "AccumulateStorageKeyUsage for storage_key with empty url!";
RecordSkippedOriginHistogram(InvalidOriginReason::kIsEmpty);
} else {
if (usage < 0)
usage = 0;
if (IsStorageUnlimited(*storage_key))
info->unlimited_usage += usage;
else
info->limited_usage += usage;
if (IsUsageCacheEnabledForStorageKey(*storage_key))
AddCachedStorageKey(*storage_key, usage);
}
}
if (--info->pending_jobs)
return;
AddCachedHost(host);
host_usage_accumulators_.Run(
host, info->limited_usage, info->unlimited_usage);
}
void ClientUsageTracker::AddCachedStorageKey(
const blink::StorageKey& storage_key,
int64_t new_usage) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsUsageCacheEnabledForStorageKey(storage_key));
const std::string& host = storage_key.origin().host();
int64_t* usage = &cached_usage_by_host_[host][storage_key];
int64_t delta = new_usage - *usage;
*usage = new_usage;
if (delta) {
UpdateGlobalUsageValue(IsStorageUnlimited(storage_key)
? &global_unlimited_usage_
: &global_limited_usage_,
delta);
}
}
void ClientUsageTracker::AddCachedHost(const std::string& host) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
cached_hosts_.insert(host);
}
int64_t ClientUsageTracker::GetCachedHostUsage(const std::string& host) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = cached_usage_by_host_.find(host);
if (it == cached_usage_by_host_.end())
return 0;
int64_t usage = 0;
const UsageMap& usage_map = it->second;
for (const auto& storage_key_and_usage : usage_map)
usage += storage_key_and_usage.second;
return usage;
}
bool ClientUsageTracker::GetCachedStorageKeyUsage(
const blink::StorageKey& storage_key,
int64_t* usage) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const std::string& host = storage_key.origin().host();
auto host_it = cached_usage_by_host_.find(host);
if (host_it == cached_usage_by_host_.end())
return false;
auto storage_key_it = host_it->second.find(storage_key);
if (storage_key_it == host_it->second.end())
return false;
DCHECK(IsUsageCacheEnabledForStorageKey(storage_key));
*usage = storage_key_it->second;
return true;
}
bool ClientUsageTracker::IsUsageCacheEnabledForStorageKey(
const blink::StorageKey& storage_key) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const std::string& host = storage_key.origin().host();
return !StorageKeySetContainsStorageKey(
non_cached_limited_storage_keys_by_host_, host, storage_key) &&
!StorageKeySetContainsStorageKey(
non_cached_unlimited_storage_keys_by_host_, host, storage_key);
}
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(origin_url);
if (change_flags & SpecialStoragePolicy::STORAGE_UNLIMITED) {
int64_t usage = 0;
if (GetCachedStorageKeyUsage(storage_key, &usage)) {
global_unlimited_usage_ += usage;
global_limited_usage_ -= usage;
}
const std::string& host = storage_key.origin().host();
if (EraseStorageKeyFromStorageKeySet(
&non_cached_limited_storage_keys_by_host_, host, storage_key))
non_cached_unlimited_storage_keys_by_host_[host].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(origin_url);
if (change_flags & SpecialStoragePolicy::STORAGE_UNLIMITED) {
int64_t usage = 0;
if (GetCachedStorageKeyUsage(storage_key, &usage)) {
global_unlimited_usage_ -= usage;
global_limited_usage_ += usage;
}
const std::string& host = storage_key.origin().host();
if (EraseStorageKeyFromStorageKeySet(
&non_cached_unlimited_storage_keys_by_host_, host, storage_key))
non_cached_limited_storage_keys_by_host_[host].insert(storage_key);
}
}
void ClientUsageTracker::OnCleared() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
global_limited_usage_ += global_unlimited_usage_;
global_unlimited_usage_ = 0;
for (const auto& host_and_storage_keys :
non_cached_unlimited_storage_keys_by_host_) {
const auto& host = host_and_storage_keys.first;
for (const auto& storage_key : host_and_storage_keys.second)
non_cached_limited_storage_keys_by_host_[host].insert(storage_key);
}
non_cached_unlimited_storage_keys_by_host_.clear();
}
void ClientUsageTracker::UpdateGlobalUsageValue(int64_t* usage_value,
int64_t delta) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
*usage_value += delta;
if (*usage_value >= 0)
return;
// If we have a negative global usage value, recalculate them.
// TODO(michaeln): There are book keeping bugs, crbug/463729
global_limited_usage_ = 0;
global_unlimited_usage_ = 0;
for (const auto& host_and_usage_map : cached_usage_by_host_) {
for (const auto& storage_key_and_usage : host_and_usage_map.second) {
if (IsStorageUnlimited(storage_key_and_usage.first))
global_unlimited_usage_ += storage_key_and_usage.second;
else
global_limited_usage_ += storage_key_and_usage.second;
}
}
}
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