blob: f2fb8573f3e79d6774f8703895581362bfdbb250 [file] [log] [blame]
// Copyright 2017 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/predictors/loading_predictor.h"
#include <algorithm>
#include <vector>
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/predictors/loading_data_collector.h"
#include "chrome/browser/predictors/loading_stats_collector.h"
#include "chrome/browser/predictors/navigation_id.h"
#include "chrome/browser/predictors/predictors_features.h"
#include "chrome/browser/predictors/resource_prefetch_predictor.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/network_isolation_key.h"
#include "url/origin.h"
namespace predictors {
namespace {
const base::TimeDelta kMinDelayBetweenPreresolveRequests =
base::TimeDelta::FromSeconds(60);
const base::TimeDelta kMinDelayBetweenPreconnectRequests =
base::TimeDelta::FromSeconds(10);
// Returns true iff |prediction| is not empty.
bool AddInitialUrlToPreconnectPrediction(const GURL& initial_url,
PreconnectPrediction* prediction) {
url::Origin initial_origin = url::Origin::Create(initial_url);
// Open minimum 2 sockets to the main frame host to speed up the loading if a
// main page has a redirect to the same host. This is because there can be a
// race between reading the server redirect response and sending a new request
// while the connection is still in use.
static const int kMinSockets = 2;
if (!prediction->requests.empty() &&
prediction->requests.front().origin == initial_origin) {
prediction->requests.front().num_sockets =
std::max(prediction->requests.front().num_sockets, kMinSockets);
} else if (!initial_origin.opaque() &&
(initial_origin.scheme() == url::kHttpScheme ||
initial_origin.scheme() == url::kHttpsScheme)) {
prediction->requests.emplace(
prediction->requests.begin(), initial_origin, kMinSockets,
net::NetworkIsolationKey(initial_origin, initial_origin));
}
return !prediction->requests.empty();
}
} // namespace
LoadingPredictor::LoadingPredictor(const LoadingPredictorConfig& config,
Profile* profile)
: config_(config),
profile_(profile),
resource_prefetch_predictor_(
std::make_unique<ResourcePrefetchPredictor>(config, profile)),
stats_collector_(std::make_unique<LoadingStatsCollector>(
resource_prefetch_predictor_.get(),
config)),
loading_data_collector_(std::make_unique<LoadingDataCollector>(
resource_prefetch_predictor_.get(),
stats_collector_.get(),
config)) {}
LoadingPredictor::~LoadingPredictor() {
DCHECK(shutdown_);
}
bool LoadingPredictor::PrepareForPageLoad(
const GURL& url,
HintOrigin origin,
bool preconnectable,
base::Optional<PreconnectPrediction> preconnect_prediction) {
if (shutdown_)
return true;
if (origin == HintOrigin::OMNIBOX) {
// Omnibox hints are lightweight and need a special treatment.
HandleOmniboxHint(url, preconnectable);
return true;
}
PreconnectPrediction prediction;
bool has_local_preconnect_prediction = false;
if (features::ShouldUseLocalPredictions()) {
has_local_preconnect_prediction =
resource_prefetch_predictor_->PredictPreconnectOrigins(url,
&prediction);
}
if (active_hints_.find(url) != active_hints_.end() &&
has_local_preconnect_prediction && !preconnect_prediction) {
// We are currently preconnecting using the local preconnect prediction. Do
// not proceed further.
return true;
}
if (preconnect_prediction) {
// Overwrite the prediction if we were provided with a non-empty one.
prediction = *preconnect_prediction;
} else {
// Try to preconnect to the |url| even if the predictor has no
// prediction.
AddInitialUrlToPreconnectPrediction(url, &prediction);
}
// Return early if we do not have any requests.
if (prediction.requests.empty() && prediction.prefetch_requests.empty())
return false;
++total_hints_activated_;
active_hints_.emplace(url, base::TimeTicks::Now());
if (IsPreconnectAllowed(profile_))
MaybeAddPreconnect(url, std::move(prediction), origin);
return has_local_preconnect_prediction || preconnect_prediction;
}
void LoadingPredictor::CancelPageLoadHint(const GURL& url) {
if (shutdown_)
return;
CancelActiveHint(active_hints_.find(url));
}
void LoadingPredictor::StartInitialization() {
if (shutdown_)
return;
resource_prefetch_predictor_->StartInitialization();
}
LoadingDataCollector* LoadingPredictor::loading_data_collector() {
return loading_data_collector_.get();
}
ResourcePrefetchPredictor* LoadingPredictor::resource_prefetch_predictor() {
return resource_prefetch_predictor_.get();
}
PreconnectManager* LoadingPredictor::preconnect_manager() {
if (shutdown_ || !IsPreconnectFeatureEnabled())
return nullptr;
if (!preconnect_manager_) {
preconnect_manager_ =
std::make_unique<PreconnectManager>(GetWeakPtr(), profile_);
}
return preconnect_manager_.get();
}
PrefetchManager* LoadingPredictor::prefetch_manager() {
if (!base::FeatureList::IsEnabled(features::kLoadingPredictorPrefetch))
return nullptr;
if (shutdown_ || !IsPreconnectFeatureEnabled())
return nullptr;
if (!prefetch_manager_) {
prefetch_manager_ =
std::make_unique<PrefetchManager>(GetWeakPtr(), profile_);
}
return prefetch_manager_.get();
}
void LoadingPredictor::Shutdown() {
DCHECK(!shutdown_);
resource_prefetch_predictor_->Shutdown();
shutdown_ = true;
}
bool LoadingPredictor::OnNavigationStarted(const NavigationID& navigation_id) {
if (shutdown_)
return true;
loading_data_collector()->RecordStartNavigation(navigation_id);
CleanupAbandonedHintsAndNavigations(navigation_id);
active_navigations_.emplace(navigation_id);
active_urls_to_navigations_[navigation_id.main_frame_url].insert(
navigation_id);
return PrepareForPageLoad(navigation_id.main_frame_url,
HintOrigin::NAVIGATION);
}
void LoadingPredictor::OnNavigationFinished(
const NavigationID& old_navigation_id,
const NavigationID& new_navigation_id,
bool is_error_page) {
if (shutdown_)
return;
loading_data_collector()->RecordFinishNavigation(
old_navigation_id, new_navigation_id, is_error_page);
if (active_urls_to_navigations_.find(old_navigation_id.main_frame_url) !=
active_urls_to_navigations_.end()) {
active_urls_to_navigations_[old_navigation_id.main_frame_url].erase(
old_navigation_id);
if (active_urls_to_navigations_[old_navigation_id.main_frame_url].empty()) {
active_urls_to_navigations_.erase(old_navigation_id.main_frame_url);
}
}
active_navigations_.erase(old_navigation_id);
CancelPageLoadHint(old_navigation_id.main_frame_url);
}
std::map<GURL, base::TimeTicks>::iterator LoadingPredictor::CancelActiveHint(
std::map<GURL, base::TimeTicks>::iterator hint_it) {
if (hint_it == active_hints_.end())
return hint_it;
const GURL& url = hint_it->first;
MaybeRemovePreconnect(url);
return active_hints_.erase(hint_it);
}
void LoadingPredictor::CleanupAbandonedHintsAndNavigations(
const NavigationID& navigation_id) {
base::TimeTicks time_now = base::TimeTicks::Now();
const base::TimeDelta max_navigation_age =
base::TimeDelta::FromSeconds(config_.max_navigation_lifetime_seconds);
// Hints.
for (auto it = active_hints_.begin(); it != active_hints_.end();) {
base::TimeDelta prefetch_age = time_now - it->second;
if (prefetch_age > max_navigation_age) {
// Will go to the last bucket in the duration reported in
// CancelActiveHint() meaning that the duration was unlimited.
it = CancelActiveHint(it);
} else {
++it;
}
}
// Navigations.
for (auto it = active_navigations_.begin();
it != active_navigations_.end();) {
if ((it->tab_id == navigation_id.tab_id) ||
(time_now - it->creation_time > max_navigation_age)) {
CancelActiveHint(active_hints_.find(it->main_frame_url));
it = active_navigations_.erase(it);
} else {
++it;
}
}
}
void LoadingPredictor::MaybeAddPreconnect(const GURL& url,
PreconnectPrediction prediction,
HintOrigin origin) {
DCHECK(!shutdown_);
if (!prediction.prefetch_requests.empty()) {
DCHECK(base::FeatureList::IsEnabled(features::kLoadingPredictorPrefetch));
prefetch_manager()->Start(url, std::move(prediction.prefetch_requests));
}
if (!prediction.requests.empty())
preconnect_manager()->Start(url, std::move(prediction.requests));
}
void LoadingPredictor::MaybeRemovePreconnect(const GURL& url) {
DCHECK(!shutdown_);
if (preconnect_manager_)
preconnect_manager_->Stop(url);
if (prefetch_manager_)
prefetch_manager_->Stop(url);
}
void LoadingPredictor::HandleOmniboxHint(const GURL& url, bool preconnectable) {
if (!url.is_valid() || !url.has_host() || !IsPreconnectAllowed(profile_))
return;
url::Origin origin = url::Origin::Create(url);
bool is_new_origin = origin != last_omnibox_origin_;
last_omnibox_origin_ = origin;
net::NetworkIsolationKey network_isolation_key(origin, origin);
base::TimeTicks now = base::TimeTicks::Now();
if (preconnectable) {
if (is_new_origin || now - last_omnibox_preconnect_time_ >=
kMinDelayBetweenPreconnectRequests) {
last_omnibox_preconnect_time_ = now;
preconnect_manager()->StartPreconnectUrl(url, true,
network_isolation_key);
}
return;
}
if (is_new_origin || now - last_omnibox_preresolve_time_ >=
kMinDelayBetweenPreresolveRequests) {
last_omnibox_preresolve_time_ = now;
preconnect_manager()->StartPreresolveHost(url, network_isolation_key);
}
}
void LoadingPredictor::PreconnectInitiated(const GURL& url,
const GURL& preconnect_url) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (shutdown_)
return;
auto nav_id_set_it = active_urls_to_navigations_.find(url);
if (nav_id_set_it == active_urls_to_navigations_.end())
return;
for (const auto& nav_id : nav_id_set_it->second)
loading_data_collector_->RecordPreconnectInitiated(nav_id, preconnect_url);
}
void LoadingPredictor::PreconnectFinished(
std::unique_ptr<PreconnectStats> stats) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (shutdown_)
return;
DCHECK(stats);
active_hints_.erase(stats->url);
stats_collector_->RecordPreconnectStats(std::move(stats));
}
void LoadingPredictor::PrefetchInitiated(const GURL& url,
const GURL& prefetch_url) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (shutdown_)
return;
auto nav_id_set_it = active_urls_to_navigations_.find(url);
if (nav_id_set_it == active_urls_to_navigations_.end())
return;
for (const auto& nav_id : nav_id_set_it->second)
loading_data_collector_->RecordPrefetchInitiated(nav_id, prefetch_url);
}
void LoadingPredictor::PrefetchFinished(std::unique_ptr<PrefetchStats> stats) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (shutdown_)
return;
active_hints_.erase(stats->url);
}
void LoadingPredictor::PreconnectURLIfAllowed(
const GURL& url,
bool allow_credentials,
const net::NetworkIsolationKey& network_isolation_key) {
if (!url.is_valid() || !url.has_host() || !IsPreconnectAllowed(profile_))
return;
preconnect_manager()->StartPreconnectUrl(url, allow_credentials,
network_isolation_key);
}
} // namespace predictors