blob: be23ab3861f79f714a9c0c6e73f57f657cf00afb [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "android_webview/browser/aw_http_cache_manager.h"
#include <algorithm>
#include "android_webview/browser/aw_browser_context.h"
#include "android_webview/browser/network_service/net_helpers.h"
#include "android_webview/common/aw_features.h"
#include "base/android/scoped_java_ref.h"
#include "base/byte_size.h"
#include "base/check.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/checked_math.h"
#include "components/prefs/pref_name_set.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "services/network/public/mojom/network_context.mojom.h"
// Has to come after all the FromJniType() / ToJniType() headers.
#include "android_webview/browser_jni_headers/AwHttpCacheManager_jni.h"
namespace android_webview {
namespace {
// Value used in per-profile prefs to indicate that the default HTTP Cache
// quota should be used.
const int64_t kUseDefaultHttpCacheQuota = -1;
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// LINT.IfChange(HttpCacheQuotaChangeHistory)
enum class HttpCacheQuotaChangeHistory {
kFirstTime = 0,
kRepeatedCallWithoutChange = 1,
kRepeatedCallWithChange = 2,
kMaxValue = kRepeatedCallWithChange,
};
// LINT.ThenChange(/tools/metrics/histograms/metadata/android/enums.xml:HttpCacheQuotaChangeHistory)
} // namespace
namespace prefs {
const char kHttpCacheQuota[] = "aw_http_cache_quota";
} // namespace prefs
AwHttpCacheManager::AwHttpCacheManager(AwBrowserContext* browser_context)
: browser_context_(raw_ref<AwBrowserContext>::from_ptr(browser_context)) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (base::FeatureList::IsEnabled(features::kWebViewHttpCacheQuotaApi)) {
// 1 is a hard minimum because the backends use 0 to mean a default (that
// WebView doesn't actually ever use).
CHECK_GE(features::kWebViewHttpCacheQuotaApiMinimum.Get(), 1);
CHECK_LE(features::kWebViewHttpCacheQuotaApiMinimum.Get(),
features::kWebViewHttpCacheQuotaApiMaximum.Get());
}
}
AwHttpCacheManager::~AwHttpCacheManager() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
base::android::ScopedJavaLocalRef<jobject>
AwHttpCacheManager::GetJavaHttpCacheManager() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!java_obj_) {
JNIEnv* env = base::android::AttachCurrentThread();
java_obj_ =
Java_AwHttpCacheManager_create(env, reinterpret_cast<intptr_t>(this));
}
return base::android::ScopedJavaLocalRef<jobject>(java_obj_);
}
// static
void AwHttpCacheManager::RegisterProfilePrefs(PrefRegistrySimple* registry) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// We don't know if we're dealing with a default or non-default profile here,
// so we may register the pref even if the API is disabled for Default.
if (!base::FeatureList::IsEnabled(features::kWebViewHttpCacheQuotaApi)) {
return;
}
registry->RegisterInt64Pref(prefs::kHttpCacheQuota,
kUseDefaultHttpCacheQuota);
}
void AwHttpCacheManager::InsertPersistentPrefs(PrefNameSet* persistent_prefs) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!IsQuotaApiEnabled()) {
return;
}
persistent_prefs->insert(prefs::kHttpCacheQuota);
}
void AwHttpCacheManager::RecordInitialQuotaHistogram() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!IsQuotaApiEnabled()) {
return;
}
PrefService* user_pref_service = browser_context_->GetPrefService();
base::UmaHistogramBoolean(
browser_context_->IsDefaultBrowserContext()
? "Android.WebView.HttpCacheQuotaApi.RestoredFromPrefs.Default"
: "Android.WebView.HttpCacheQuotaApi.RestoredFromPrefs.NonDefault",
user_pref_service->HasPrefPath(prefs::kHttpCacheQuota) &&
user_pref_service->GetInt64(prefs::kHttpCacheQuota) !=
kUseDefaultHttpCacheQuota);
}
int64_t AwHttpCacheManager::GetDefaultQuotaBytes(JNIEnv* env) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!IsQuotaApiEnabled()) {
return GetDefaultHttpCacheSize();
}
return std::clamp(GetDefaultHttpCacheSize(),
features::kWebViewHttpCacheQuotaApiMinimum.Get(),
features::kWebViewHttpCacheQuotaApiMaximum.Get());
}
void AwHttpCacheManager::UseDefaultQuota(JNIEnv* env) {
SetQuotaBytesInternal(kUseDefaultHttpCacheQuota);
}
bool AwHttpCacheManager::IsUsingDefaultQuota(JNIEnv* env) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!IsQuotaApiEnabled()) {
return true;
}
return browser_context_->GetPrefService()->GetInt64(prefs::kHttpCacheQuota) ==
kUseDefaultHttpCacheQuota;
}
int64_t AwHttpCacheManager::GetQuotaBytes(JNIEnv* env) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!IsQuotaApiEnabled()) {
return GetDefaultHttpCacheSize();
}
int64_t quota_in_bytes =
browser_context_->GetPrefService()->GetInt64(prefs::kHttpCacheQuota);
if (quota_in_bytes == kUseDefaultHttpCacheQuota) {
quota_in_bytes = GetDefaultHttpCacheSize();
} else {
if (!features::kWebViewHttpCacheQuotaApiAllowShrinking.Get()) {
quota_in_bytes =
std::max(quota_in_bytes, int64_t{GetDefaultHttpCacheSize()});
}
}
return std::clamp(quota_in_bytes,
int64_t{features::kWebViewHttpCacheQuotaApiMinimum.Get()},
int64_t{features::kWebViewHttpCacheQuotaApiMaximum.Get()});
}
void AwHttpCacheManager::SetQuotaBytes(JNIEnv* env, int64_t quota_in_bytes) {
CHECK_NE(quota_in_bytes, kUseDefaultHttpCacheQuota);
CHECK_GE(quota_in_bytes, 0);
SetQuotaBytesInternal(quota_in_bytes);
}
void AwHttpCacheManager::SetQuotaBytesInternal(int64_t quota_in_bytes) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!IsQuotaApiEnabled()) {
return;
}
RecordQuotaSetterHistograms(quota_in_bytes);
// Note that we store the exact value supplied to the API without adjustment.
// Adjustments are only performed when reading the value back out, allowing
// adjustment-related feature parameters to always take effect.
browser_context_->GetPrefService()->SetInt64(prefs::kHttpCacheQuota,
quota_in_bytes);
if (features::kWebViewHttpCacheQuotaApiRuntimeUpdate.Get()) {
content::StoragePartition* partition =
browser_context_->GetDefaultStoragePartition();
if (partition && partition->GetNetworkContext()) {
// Use GetQuotaBytes instead of quota_in_bytes, because it applies checks
// and adjustments.
partition->GetNetworkContext()->SetHttpCacheMaxSize(
base::ByteSize(
base::checked_cast<uint64_t>(GetQuotaBytes(/*env=*/nullptr))),
features::kWebViewHttpCacheQuotaApiForceBackendInit.Get());
}
}
}
void AwHttpCacheManager::RecordQuotaSetterHistograms(int64_t quota_in_bytes) {
const bool is_default_profile = browser_context_->IsDefaultBrowserContext();
// Change history.
HttpCacheQuotaChangeHistory change_history;
if (!last_quota_set_for_metrics_.has_value()) {
change_history = HttpCacheQuotaChangeHistory::kFirstTime;
} else if (*last_quota_set_for_metrics_ == quota_in_bytes) {
change_history = HttpCacheQuotaChangeHistory::kRepeatedCallWithoutChange;
} else {
change_history = HttpCacheQuotaChangeHistory::kRepeatedCallWithChange;
}
base::UmaHistogramEnumeration(
is_default_profile
? "Android.WebView.HttpCacheQuotaApi.ChangeHistory.Default"
: "Android.WebView.HttpCacheQuotaApi.ChangeHistory.NonDefault",
change_history);
last_quota_set_for_metrics_ = quota_in_bytes;
if (quota_in_bytes == kUseDefaultHttpCacheQuota) {
base::UmaHistogramBoolean(
is_default_profile
? "Android.WebView.HttpCacheQuotaApi.UseDefaultQuota.Default"
: "Android.WebView.HttpCacheQuotaApi.UseDefaultQuota.NonDefault",
true);
return;
}
// Absolute value in KiB.
base::UmaHistogramCounts1M(
is_default_profile
? "Android.WebView.HttpCacheQuotaApi.AbsoluteValue.Default"
: "Android.WebView.HttpCacheQuotaApi.AbsoluteValue.NonDefault",
base::saturated_cast<int>(quota_in_bytes / 1024));
// Relative value in %.
int64_t default_quota_in_bytes = GetDefaultQuotaBytes(/*env=*/nullptr);
CHECK_GT(default_quota_in_bytes, 0);
base::UmaHistogramCounts10000(
is_default_profile
? "Android.WebView.HttpCacheQuotaApi.RelativeValue.Default"
: "Android.WebView.HttpCacheQuotaApi.RelativeValue.NonDefault",
base::saturated_cast<int>(quota_in_bytes * 100.0f /
default_quota_in_bytes));
}
bool AwHttpCacheManager::IsQuotaApiEnabled() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!base::FeatureList::IsEnabled(features::kWebViewHttpCacheQuotaApi)) {
return false;
}
if (browser_context_->IsDefaultBrowserContext() &&
!features::kWebViewHttpCacheQuotaApiAllowForDefaultProfile.Get()) {
return false;
}
return true;
}
} // namespace android_webview
DEFINE_JNI(AwHttpCacheManager)