blob: 8df721e7561b1bb950f87089e1e7a4e616291aaf [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/preconnect_manager.h"
#include <utility>
#include "base/bind.h"
#include "base/task/post_task.h"
#include "base/trace_event/trace_event.h"
#include "chrome/browser/predictors/resource_prefetch_predictor.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "net/base/load_flags.h"
#include "services/network/public/mojom/network_context.mojom.h"
namespace predictors {
const bool kAllowCredentialsOnPreconnectByDefault = true;
PreconnectedRequestStats::PreconnectedRequestStats(const GURL& origin,
bool was_preconnected)
: origin(origin),
was_preconnected(was_preconnected) {}
PreconnectedRequestStats::PreconnectedRequestStats(
const PreconnectedRequestStats& other) = default;
PreconnectedRequestStats::~PreconnectedRequestStats() = default;
PreconnectStats::PreconnectStats(const GURL& url)
: url(url), start_time(base::TimeTicks::Now()) {}
PreconnectStats::~PreconnectStats() = default;
PreresolveInfo::PreresolveInfo(const GURL& url, size_t count)
: url(url),
queued_count(count),
inflight_count(0),
was_canceled(false),
stats(std::make_unique<PreconnectStats>(url)) {}
PreresolveInfo::~PreresolveInfo() = default;
PreresolveJob::PreresolveJob(const GURL& url,
int num_sockets,
bool allow_credentials,
PreresolveInfo* info)
: url(url),
num_sockets(num_sockets),
allow_credentials(allow_credentials),
info(info) {
DCHECK_GE(num_sockets, 0);
}
PreresolveJob::PreresolveJob(PreresolveJob&& other) = default;
PreresolveJob::~PreresolveJob() = default;
PreconnectManager::PreconnectManager(base::WeakPtr<Delegate> delegate,
Profile* profile)
: delegate_(std::move(delegate)),
profile_(profile),
inflight_preresolves_count_(0),
weak_factory_(this) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(profile_);
}
PreconnectManager::~PreconnectManager() = default;
void PreconnectManager::Start(const GURL& url,
std::vector<PreconnectRequest> requests) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const std::string host = url.host();
if (preresolve_info_.find(host) != preresolve_info_.end())
return;
auto iterator_and_whether_inserted = preresolve_info_.emplace(
host, std::make_unique<PreresolveInfo>(url, requests.size()));
PreresolveInfo* info = iterator_and_whether_inserted.first->second.get();
for (const auto& request : requests) {
DCHECK(request.origin.GetOrigin() == request.origin);
PreresolveJobId job_id = preresolve_jobs_.Add(
std::make_unique<PreresolveJob>(request.origin, request.num_sockets,
request.allow_credentials, info));
queued_jobs_.push_back(job_id);
}
TryToLaunchPreresolveJobs();
}
void PreconnectManager::StartPreresolveHost(const GURL& url) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!url.SchemeIsHTTPOrHTTPS())
return;
PreresolveJobId job_id = preresolve_jobs_.Add(std::make_unique<PreresolveJob>(
url.GetOrigin(), 0, kAllowCredentialsOnPreconnectByDefault, nullptr));
queued_jobs_.push_front(job_id);
TryToLaunchPreresolveJobs();
}
void PreconnectManager::StartPreresolveHosts(
const std::vector<std::string>& hostnames) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Push jobs in front of the queue due to higher priority.
for (auto it = hostnames.rbegin(); it != hostnames.rend(); ++it) {
PreresolveJobId job_id =
preresolve_jobs_.Add(std::make_unique<PreresolveJob>(
GURL("http://" + *it), 0, kAllowCredentialsOnPreconnectByDefault,
nullptr));
queued_jobs_.push_front(job_id);
}
TryToLaunchPreresolveJobs();
}
void PreconnectManager::StartPreconnectUrl(const GURL& url,
bool allow_credentials) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!url.SchemeIsHTTPOrHTTPS())
return;
PreresolveJobId job_id = preresolve_jobs_.Add(std::make_unique<PreresolveJob>(
url.GetOrigin(), 1, allow_credentials, nullptr));
queued_jobs_.push_front(job_id);
TryToLaunchPreresolveJobs();
}
void PreconnectManager::Stop(const GURL& url) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto it = preresolve_info_.find(url.host());
if (it == preresolve_info_.end()) {
return;
}
it->second->was_canceled = true;
}
void PreconnectManager::PreconnectUrl(const GURL& url,
int num_sockets,
bool allow_credentials) const {
DCHECK(url.GetOrigin() == url);
DCHECK(url.SchemeIsHTTPOrHTTPS());
if (observer_)
observer_->OnPreconnectUrl(url, num_sockets, allow_credentials);
auto* network_context = GetNetworkContext();
if (!network_context)
return;
bool privacy_mode = false;
int load_flags = net::LOAD_NORMAL;
if (!allow_credentials) {
privacy_mode = true;
load_flags = net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES |
net::LOAD_DO_NOT_SEND_AUTH_DATA;
}
network_context->PreconnectSockets(num_sockets, url, load_flags,
privacy_mode);
}
std::unique_ptr<ResolveHostClientImpl> PreconnectManager::PreresolveUrl(
const GURL& url,
ResolveHostCallback callback) const {
DCHECK(url.GetOrigin() == url);
DCHECK(url.SchemeIsHTTPOrHTTPS());
auto* network_context = GetNetworkContext();
if (!network_context) {
// Cannot invoke the callback right away because it would cause the
// use-after-free after returning from this function.
base::PostTaskWithTraits(
FROM_HERE,
{content::BrowserThread::UI, content::BrowserTaskType::kPreconnect},
base::BindOnce(std::move(callback), false));
return nullptr;
}
return std::make_unique<ResolveHostClientImpl>(url, std::move(callback),
network_context);
}
std::unique_ptr<ProxyLookupClientImpl> PreconnectManager::LookupProxyForUrl(
const GURL& url,
ProxyLookupCallback callback) const {
DCHECK(url.GetOrigin() == url);
DCHECK(url.SchemeIsHTTPOrHTTPS());
auto* network_context = GetNetworkContext();
if (!network_context) {
std::move(callback).Run(false);
return nullptr;
}
return std::make_unique<ProxyLookupClientImpl>(url, std::move(callback),
network_context);
}
void PreconnectManager::TryToLaunchPreresolveJobs() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
while (!queued_jobs_.empty() &&
inflight_preresolves_count_ < kMaxInflightPreresolves) {
auto job_id = queued_jobs_.front();
queued_jobs_.pop_front();
PreresolveJob* job = preresolve_jobs_.Lookup(job_id);
DCHECK(job);
PreresolveInfo* info = job->info;
if (!(info && info->was_canceled)) {
// This is used to avoid issuing DNS requests when a fixed proxy
// configuration is in place, which improves efficiency, and is also
// important if the unproxied DNS may contain incorrect entries.
job->proxy_lookup_client = LookupProxyForUrl(
job->url, base::BindOnce(&PreconnectManager::OnProxyLookupFinished,
weak_factory_.GetWeakPtr(), job_id));
if (info)
++info->inflight_count;
++inflight_preresolves_count_;
} else {
preresolve_jobs_.Remove(job_id);
}
if (info) {
DCHECK_LE(1u, info->queued_count);
--info->queued_count;
if (info->is_done()) {
AllPreresolvesForUrlFinished(info);
}
}
}
}
void PreconnectManager::OnPreresolveFinished(PreresolveJobId job_id,
bool success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
PreresolveJob* job = preresolve_jobs_.Lookup(job_id);
DCHECK(job);
if (observer_)
observer_->OnPreresolveFinished(job->url, success);
job->resolve_host_client = nullptr;
FinishPreresolveJob(job_id, success);
}
void PreconnectManager::OnProxyLookupFinished(PreresolveJobId job_id,
bool success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
PreresolveJob* job = preresolve_jobs_.Lookup(job_id);
DCHECK(job);
if (observer_)
observer_->OnProxyLookupFinished(job->url, success);
job->proxy_lookup_client = nullptr;
if (success) {
FinishPreresolveJob(job_id, success);
} else {
job->resolve_host_client = PreresolveUrl(
job->url, base::BindOnce(&PreconnectManager::OnPreresolveFinished,
weak_factory_.GetWeakPtr(), job_id));
}
}
void PreconnectManager::FinishPreresolveJob(PreresolveJobId job_id,
bool success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
PreresolveJob* job = preresolve_jobs_.Lookup(job_id);
DCHECK(job);
bool need_preconnect = success && job->need_preconnect();
if (need_preconnect)
PreconnectUrl(job->url, job->num_sockets, job->allow_credentials);
PreresolveInfo* info = job->info;
if (info)
info->stats->requests_stats.emplace_back(job->url, need_preconnect);
preresolve_jobs_.Remove(job_id);
--inflight_preresolves_count_;
if (info) {
DCHECK_LE(1u, info->inflight_count);
--info->inflight_count;
}
if (info && info->is_done())
AllPreresolvesForUrlFinished(info);
TryToLaunchPreresolveJobs();
}
void PreconnectManager::AllPreresolvesForUrlFinished(PreresolveInfo* info) {
DCHECK(info);
DCHECK(info->is_done());
auto it = preresolve_info_.find(info->url.host());
DCHECK(it != preresolve_info_.end());
DCHECK(info == it->second.get());
if (delegate_)
delegate_->PreconnectFinished(std::move(info->stats));
preresolve_info_.erase(it);
}
network::mojom::NetworkContext* PreconnectManager::GetNetworkContext() const {
if (network_context_)
return network_context_;
if (profile_->AsTestingProfile()) {
// We're testing and |network_context_| wasn't set. Return nullptr to avoid
// hitting the network.
return nullptr;
}
return content::BrowserContext::GetDefaultStoragePartition(profile_)
->GetNetworkContext();
}
} // namespace predictors