blob: 1cca7e6b5832b53933c04a4271462c1cfdef0b67 [file] [log] [blame]
// Copyright 2019 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 "chrome/browser/optimization_guide/optimization_guide_top_host_provider.h"
#include <algorithm>
#include "base/metrics/histogram_macros.h"
#include "base/time/default_clock.h"
#include "base/values.h"
#include "chrome/browser/engagement/site_engagement_details.mojom.h"
#include "chrome/browser/engagement/site_engagement_score.h"
#include "chrome/browser/engagement/site_engagement_service.h"
#include "chrome/browser/optimization_guide/optimization_guide_permissions_util.h"
#include "chrome/browser/profiles/profile.h"
#include "components/optimization_guide/hints_processing_util.h"
#include "components/optimization_guide/optimization_guide_features.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
namespace {
// Returns true if hints can't be fetched for |host|.
bool IsHostBlacklisted(const base::DictionaryValue* top_host_blacklist,
const std::string& host) {
if (!top_host_blacklist)
return false;
return top_host_blacklist->FindKey(
optimization_guide::HashHostForDictionary(host));
}
} // namespace
// static
std::unique_ptr<OptimizationGuideTopHostProvider>
OptimizationGuideTopHostProvider::CreateIfAllowed(
content::BrowserContext* browser_context) {
if (IsUserPermittedToFetchHints(
Profile::FromBrowserContext(browser_context))) {
return std::make_unique<OptimizationGuideTopHostProvider>(
browser_context, base::DefaultClock::GetInstance());
}
return nullptr;
}
OptimizationGuideTopHostProvider::OptimizationGuideTopHostProvider(
content::BrowserContext* browser_context,
base::Clock* time_clock)
: browser_context_(browser_context),
time_clock_(time_clock),
pref_service_(Profile::FromBrowserContext(browser_context_)->GetPrefs()) {
}
OptimizationGuideTopHostProvider::~OptimizationGuideTopHostProvider() {}
void OptimizationGuideTopHostProvider::
InitializeHintsFetcherTopHostBlacklist() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(browser_context_);
DCHECK_EQ(GetCurrentBlacklistState(),
optimization_guide::prefs::HintsFetcherTopHostBlacklistState::
kNotInitialized);
DCHECK(
pref_service_
->GetDictionary(
optimization_guide::prefs::kHintsFetcherDataSaverTopHostBlacklist)
->empty());
Profile* profile = Profile::FromBrowserContext(browser_context_);
SiteEngagementService* engagement_service =
SiteEngagementService::Get(profile);
std::unique_ptr<base::DictionaryValue> top_host_blacklist =
std::make_unique<base::DictionaryValue>();
std::vector<mojom::SiteEngagementDetails> engagement_details =
engagement_service->GetAllDetails();
std::sort(engagement_details.begin(), engagement_details.end(),
[](const mojom::SiteEngagementDetails& lhs,
const mojom::SiteEngagementDetails& rhs) {
return lhs.total_score > rhs.total_score;
});
pref_service_->SetDouble(
optimization_guide::prefs::kTimeBlacklistLastInitialized,
time_clock_->Now().ToDeltaSinceWindowsEpoch().InSecondsF());
// Set the minimum engagement score to -1.0f. This ensures that in the default
// case (where the blacklist size is enough to accommodate all hosts from the
// site engagement service), a threshold on the minimum site engagement score
// does not disqualify |this| from requesting hints for any host.
pref_service_->SetDouble(
optimization_guide::prefs::
kHintsFetcherDataSaverTopHostBlacklistMinimumEngagementScore,
-1.0f);
for (const auto& detail : engagement_details) {
if (!detail.origin.SchemeIsHTTPOrHTTPS())
continue;
if (top_host_blacklist->size() >=
optimization_guide::features::MaxHintsFetcherTopHostBlacklistSize()) {
// Set the minimum engagement score to the score of the host that
// could not be added to |top_host_blacklist|. Add a small epsilon value
// to the threshold so that any host with score equal to or less than
// the threshold is not included in the hints fetcher request.
pref_service_->SetDouble(
optimization_guide::prefs::
kHintsFetcherDataSaverTopHostBlacklistMinimumEngagementScore,
std::min(detail.total_score + 0.001f,
optimization_guide::features::
MinTopHostEngagementScoreThreshold()));
break;
}
top_host_blacklist->SetBoolKey(
optimization_guide::HashHostForDictionary(detail.origin.host()), true);
}
UMA_HISTOGRAM_COUNTS_1000(
"OptimizationGuide.HintsFetcher.TopHostProvider.BlacklistSize."
"OnInitialize",
top_host_blacklist->size());
pref_service_->Set(
optimization_guide::prefs::kHintsFetcherDataSaverTopHostBlacklist,
*top_host_blacklist);
UpdateCurrentBlacklistState(
optimization_guide::prefs::HintsFetcherTopHostBlacklistState::
kInitialized);
}
// static
void OptimizationGuideTopHostProvider::MaybeUpdateTopHostBlacklist(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->GetURL().SchemeIsHTTPOrHTTPS())
return;
PrefService* pref_service =
Profile::FromBrowserContext(
navigation_handle->GetWebContents()->GetBrowserContext())
->GetPrefs();
if (pref_service->GetInteger(
optimization_guide::prefs::
kHintsFetcherDataSaverTopHostBlacklistState) !=
static_cast<int>(optimization_guide::prefs::
HintsFetcherTopHostBlacklistState::kInitialized)) {
return;
}
DictionaryPrefUpdate blacklist_pref(
pref_service,
optimization_guide::prefs::kHintsFetcherDataSaverTopHostBlacklist);
if (!blacklist_pref->FindKey(optimization_guide::HashHostForDictionary(
navigation_handle->GetURL().host()))) {
return;
}
blacklist_pref->RemovePath(optimization_guide::HashHostForDictionary(
navigation_handle->GetURL().host()));
if (blacklist_pref->empty()) {
blacklist_pref->Clear();
pref_service->SetInteger(
optimization_guide::prefs::kHintsFetcherDataSaverTopHostBlacklistState,
static_cast<int>(optimization_guide::prefs::
HintsFetcherTopHostBlacklistState::kEmpty));
}
}
optimization_guide::prefs::HintsFetcherTopHostBlacklistState
OptimizationGuideTopHostProvider::GetCurrentBlacklistState() const {
return static_cast<
optimization_guide::prefs::HintsFetcherTopHostBlacklistState>(
pref_service_->GetInteger(
optimization_guide::prefs::
kHintsFetcherDataSaverTopHostBlacklistState));
}
void OptimizationGuideTopHostProvider::UpdateCurrentBlacklistState(
optimization_guide::prefs::HintsFetcherTopHostBlacklistState new_state) {
optimization_guide::prefs::HintsFetcherTopHostBlacklistState current_state =
GetCurrentBlacklistState();
// TODO(mcrouse): Change to DCHECK_NE.
DCHECK_EQ(
new_state == optimization_guide::prefs::
HintsFetcherTopHostBlacklistState::kInitialized,
current_state == optimization_guide::prefs::
HintsFetcherTopHostBlacklistState::kNotInitialized &&
new_state == optimization_guide::prefs::
HintsFetcherTopHostBlacklistState::kInitialized);
DCHECK_EQ(
new_state ==
optimization_guide::prefs::HintsFetcherTopHostBlacklistState::kEmpty,
current_state == optimization_guide::prefs::
HintsFetcherTopHostBlacklistState::kInitialized &&
new_state == optimization_guide::prefs::
HintsFetcherTopHostBlacklistState::kEmpty);
DCHECK_EQ(new_state == optimization_guide::prefs::
HintsFetcherTopHostBlacklistState::kNotInitialized,
current_state == optimization_guide::prefs::
HintsFetcherTopHostBlacklistState::kEmpty &&
new_state ==
optimization_guide::prefs::
HintsFetcherTopHostBlacklistState::kNotInitialized);
if (current_state == new_state)
return;
// TODO(mcrouse): Add histogram to record the blacklist state change.
pref_service_->SetInteger(
optimization_guide::prefs::kHintsFetcherDataSaverTopHostBlacklistState,
static_cast<int>(new_state));
}
std::vector<std::string> OptimizationGuideTopHostProvider::GetTopHosts(
size_t max_sites) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(browser_context_);
DCHECK(pref_service_);
// It's possible that the blacklist is initialized but
// kTimeBlacklistLastInitialized pref is not populated. This may happen since
// the logic to populate kTimeBlacklistLastInitialized pref was added in a
// later Chrome version. In that case, set kTimeBlacklistLastInitialized to
// the conservative value of current time.
if (pref_service_->GetDouble(
optimization_guide::prefs::kTimeBlacklistLastInitialized) == 0) {
pref_service_->SetDouble(
optimization_guide::prefs::kTimeBlacklistLastInitialized,
time_clock_->Now().ToDeltaSinceWindowsEpoch().InSecondsF());
}
if (GetCurrentBlacklistState() ==
optimization_guide::prefs::HintsFetcherTopHostBlacklistState::
kNotInitialized) {
InitializeHintsFetcherTopHostBlacklist();
return std::vector<std::string>();
}
// Create SiteEngagementService to request site engagement scores.
Profile* profile = Profile::FromBrowserContext(browser_context_);
SiteEngagementService* engagement_service =
SiteEngagementService::Get(profile);
const base::DictionaryValue* top_host_blacklist = nullptr;
if (GetCurrentBlacklistState() !=
optimization_guide::prefs::HintsFetcherTopHostBlacklistState::kEmpty) {
top_host_blacklist = pref_service_->GetDictionary(
optimization_guide::prefs::kHintsFetcherDataSaverTopHostBlacklist);
UMA_HISTOGRAM_COUNTS_1000(
"OptimizationGuide.HintsFetcher.TopHostProvider.BlacklistSize."
"OnRequest",
top_host_blacklist->size());
// This check likely should not be needed as the process of removing hosts
// from the blacklist should check and update the pref state.
if (top_host_blacklist->size() == 0) {
UpdateCurrentBlacklistState(
optimization_guide::prefs::HintsFetcherTopHostBlacklistState::kEmpty);
top_host_blacklist = nullptr;
}
}
std::vector<std::string> top_hosts;
top_hosts.reserve(max_sites);
// Create a vector of the top hosts by engagement score up to |max_sites|
// size. Currently utilizes just the first |max_sites| entries. Only HTTPS
// schemed hosts are included. Hosts are filtered by the blacklist that is
// populated when DataSaver is first enabled.
std::vector<mojom::SiteEngagementDetails> engagement_details =
engagement_service->GetAllDetails();
std::sort(engagement_details.begin(), engagement_details.end(),
[](const mojom::SiteEngagementDetails& lhs,
const mojom::SiteEngagementDetails& rhs) {
return lhs.total_score > rhs.total_score;
});
base::Time blacklist_initialized_time =
base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromSecondsD(pref_service_->GetDouble(
optimization_guide::prefs::kTimeBlacklistLastInitialized)));
base::TimeDelta duration_since_blacklist_initialized =
(time_clock_->Now() - blacklist_initialized_time);
for (const auto& detail : engagement_details) {
if (top_hosts.size() >= max_sites)
return top_hosts;
// TODO(b/968542): Skip origins that are local hosts (e.g., IP addresses,
// localhost:8080 etc.).
if (!detail.origin.SchemeIs(url::kHttpsScheme))
continue;
// Once the engagement score is less than the initial engagement score for a
// newly navigated host, return the current set of top hosts. This threshold
// prevents hosts that have not been engaged recently from having hints
// requested for them. The engagement_details are sorted above in descending
// order by engagement score.
// This filtering is applied only if the the blacklist was initialized
// recently. If the blacklist was initialized too far back in time, hosts
// that could not make it to blacklist should have either been navigated to
// or would have fallen off the blacklist.
if (duration_since_blacklist_initialized <=
optimization_guide::features::
DurationApplyLowEngagementScoreThreshold() &&
detail.total_score <
pref_service_->GetDouble(
optimization_guide::prefs::
kHintsFetcherDataSaverTopHostBlacklistMinimumEngagementScore)) {
return top_hosts;
}
if (!IsHostBlacklisted(top_host_blacklist, detail.origin.host())) {
top_hosts.push_back(detail.origin.host());
}
}
return top_hosts;
}