blob: f2ae6c534f0793f8e91e335e707f9490962a049d [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/resource_prefetch_common.h"
#include "chrome/browser/predictors/resource_prefetch_predictor.h"
#include "content/public/browser/browser_thread.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) {
GURL initial_origin = initial_url.GetOrigin();
// 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.is_valid() &&
initial_origin.SchemeIsHTTPOrHTTPS()) {
prediction->requests.emplace(prediction->requests.begin(), initial_origin,
kMinSockets);
}
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)),
weak_factory_(this) {}
LoadingPredictor::~LoadingPredictor() {
DCHECK(shutdown_);
}
void LoadingPredictor::PrepareForPageLoad(const GURL& url,
HintOrigin origin,
bool preconnectable) {
if (shutdown_)
return;
if (origin == HintOrigin::OMNIBOX) {
// Omnibox hints are lightweight and need a special treatment.
HandleOmniboxHint(url, preconnectable);
return;
}
if (active_hints_.find(url) != active_hints_.end())
return;
bool has_preconnect_prediction = false;
PreconnectPrediction prediction;
has_preconnect_prediction =
resource_prefetch_predictor_->PredictPreconnectOrigins(url, &prediction);
// Try to preconnect to the |url| even if the predictor has no
// prediction.
has_preconnect_prediction =
AddInitialUrlToPreconnectPrediction(url, &prediction);
if (!has_preconnect_prediction)
return;
++total_hints_activated_;
active_hints_.emplace(url, base::TimeTicks::Now());
if (IsPreconnectAllowed(profile_))
MaybeAddPreconnect(url, std::move(prediction.requests), origin);
}
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();
}
void LoadingPredictor::Shutdown() {
DCHECK(!shutdown_);
resource_prefetch_predictor_->Shutdown();
shutdown_ = true;
}
void LoadingPredictor::OnNavigationStarted(const NavigationID& navigation_id) {
if (shutdown_)
return;
loading_data_collector()->RecordStartNavigation(navigation_id);
CleanupAbandonedHintsAndNavigations(navigation_id);
active_navigations_.emplace(navigation_id);
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);
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,
std::vector<PreconnectRequest> requests,
HintOrigin origin) {
DCHECK(!shutdown_);
preconnect_manager()->Start(url, std::move(requests));
}
void LoadingPredictor::MaybeRemovePreconnect(const GURL& url) {
DCHECK(!shutdown_);
if (!preconnect_manager_)
return;
preconnect_manager_->Stop(url);
}
void LoadingPredictor::HandleOmniboxHint(const GURL& url, bool preconnectable) {
if (!url.is_valid() || !url.has_host() || !IsPreconnectAllowed(profile_))
return;
GURL origin = url.GetOrigin();
bool is_new_origin = origin != last_omnibox_origin_;
last_omnibox_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);
}
return;
}
if (is_new_origin || now - last_omnibox_preresolve_time_ >=
kMinDelayBetweenPreresolveRequests) {
last_omnibox_preresolve_time_ = now;
preconnect_manager()->StartPreresolveHost(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));
}
} // namespace predictors