#include "chrome/browser/android/oom_intervention/oom_intervention_decider.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "components/metrics/metrics_service.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/scoped_user_pref_update.h"
namespace {
const char kOomInterventionDecider[] = "oom_intervention.decider";
const char kBlacklist[] = "oom_intervention.blacklist";
const char kBlocklist[] = "oom_intervention.blocklist";
const char kDeclinedHostList[] = "oom_intervention.declined_host_list";
const char kOomDetectedHostList[] = "oom_intervention.oom_detected_host_list";
class DelegateImpl : public OomInterventionDecider::Delegate {
public:
bool WasLastShutdownClean() override {
if (!g_browser_process || !g_browser_process->metrics_service())
return true;
return g_browser_process->metrics_service()->WasLastShutdownClean();
}
};
}
const size_t OomInterventionDecider::kMaxListSize = 10;
const size_t OomInterventionDecider::kMaxBlocklistSize = 6;
void OomInterventionDecider::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterListPref(kBlocklist);
registry->RegisterListPref(kDeclinedHostList);
registry->RegisterListPref(kOomDetectedHostList);
registry->RegisterListPref(kBlacklist);
}
OomInterventionDecider* OomInterventionDecider::GetForBrowserContext(
content::BrowserContext* context) {
if (context->IsOffTheRecord())
return nullptr;
if (!context->GetUserData(kOomInterventionDecider)) {
PrefService* prefs = Profile::FromBrowserContext(context)->GetPrefs();
context->SetUserData(kOomInterventionDecider,
base::WrapUnique(new OomInterventionDecider(
std::make_unique<DelegateImpl>(), prefs)));
}
return static_cast<OomInterventionDecider*>(
context->GetUserData(kOomInterventionDecider));
}
OomInterventionDecider::OomInterventionDecider(
std::unique_ptr<Delegate> delegate,
PrefService* prefs)
: delegate_(std::move(delegate)), prefs_(prefs) {
DCHECK(delegate_);
PrefService::PrefInitializationStatus pref_status =
prefs_->GetInitializationStatus();
if (pref_status == PrefService::INITIALIZATION_STATUS_WAITING) {
prefs_->AddPrefInitObserver(base::BindOnce(
&OomInterventionDecider::OnPrefInitialized, base::Unretained(this)));
} else {
OnPrefInitialized(pref_status ==
PrefService::INITIALIZATION_STATUS_SUCCESS);
}
}
OomInterventionDecider::~OomInterventionDecider() = default;
bool OomInterventionDecider::CanTriggerIntervention(
const std::string& host) const {
if (IsOptedOut(host))
return false;
if (IsInList(kOomDetectedHostList, host))
return true;
if (IsInList(kDeclinedHostList, host))
return false;
return true;
}
void OomInterventionDecider::OnInterventionDeclined(const std::string& host) {
if (IsOptedOut(host))
return;
if (IsInList(kDeclinedHostList, host)) {
AddToList(kBlocklist, host);
} else {
AddToList(kDeclinedHostList, host);
}
}
void OomInterventionDecider::OnOomDetected(const std::string& host) {
if (IsOptedOut(host))
return;
AddToList(kOomDetectedHostList, host);
}
void OomInterventionDecider::ClearData() {
prefs_->ClearPref(kBlocklist);
prefs_->ClearPref(kDeclinedHostList);
prefs_->ClearPref(kOomDetectedHostList);
}
void OomInterventionDecider::OnPrefInitialized(bool success) {
if (!success)
return;
const base::Value::List& old_pref_value = prefs_->GetList(kBlacklist);
if (!old_pref_value.empty()) {
prefs_->SetList(kBlocklist, old_pref_value.Clone());
prefs_->SetList(kBlacklist, base::Value::List());
}
if (delegate_->WasLastShutdownClean())
return;
const base::Value::List& declined_list = prefs_->GetList(kDeclinedHostList);
if (!declined_list.empty()) {
const std::string& last_declined = declined_list.back().GetString();
if (!IsInList(kBlocklist, last_declined))
AddToList(kOomDetectedHostList, last_declined);
}
}
bool OomInterventionDecider::IsOptedOut(const std::string& host) const {
if (prefs_->GetList(kBlocklist).size() >= kMaxBlocklistSize)
return true;
return IsInList(kBlocklist, host);
}
bool OomInterventionDecider::IsInList(const char* list_name,
const std::string& host) const {
for (const auto& value : prefs_->GetList(list_name)) {
if (value.GetString() == host)
return true;
}
return false;
}
void OomInterventionDecider::AddToList(const char* list_name,
const std::string& host) {
if (IsInList(list_name, host))
return;
ScopedListPrefUpdate update(prefs_, list_name);
base::Value::List& update_list = update.Get();
update_list.Append(host);
if (update_list.size() > kMaxListSize)
update_list.erase(update_list.begin());
prefs_->CommitPendingWrite();
}