Commit 248c267f authored by Alexandr Ilin's avatar Alexandr Ilin Committed by Commit Bot

predictors: Request a proxy lookup in parallel with host resolve request

This CL adds an asynchronous proxy lookup request to the PreconnectManager
after the synchronous request was removed in https://crrev.com/c/1165550.
The idea is that we don't need to wait for a host to be resolved before a
preconnect if a proxy is enabled.

Since we don't have a synchronous API for proxy lookup anymore, we issue both
host and proxy requests in parallel. Then we wait until either one of the
requests completes with success or both requests fail.

Bug: 838763
Change-Id: Ib3fa47884ba82d1fc12c62f7fd24dd21c279e9c5
Reviewed-on: https://chromium-review.googlesource.com/1202222
Commit-Queue: Alexandr Ilin <alexilin@chromium.org>
Reviewed-by: default avatarBenoit L <lizeb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#589877}
parent 6bb20b5e
......@@ -1132,6 +1132,8 @@ jumbo_split_static_library("browser") {
"predictors/predictor_database_factory.h",
"predictors/predictor_table_base.cc",
"predictors/predictor_table_base.h",
"predictors/proxy_lookup_client_impl.cc",
"predictors/proxy_lookup_client_impl.h",
"predictors/resolve_host_client_impl.cc",
"predictors/resolve_host_client_impl.h",
"predictors/resource_prefetch_common.cc",
......
......@@ -9,6 +9,8 @@
#include <vector>
#include "base/base64.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/single_thread_task_runner.h"
......@@ -19,6 +21,7 @@
#include "chrome/browser/predictors/preconnect_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/browser_thread.h"
......@@ -56,14 +59,12 @@ const char kHtmlSubresourcesPath[] = "/predictors/html_subresources.html";
const std::string kHtmlSubresourcesHosts[] = {"test.com", "baz.com", "foo.com",
"bar.com"};
GURL GetURLWithReplacements(net::test_server::EmbeddedTestServer* server,
const std::string& host,
const std::string& path) {
std::string port = base::StringPrintf("%d", server->port());
std::string GetPathWithPortReplacement(const std::string& path, uint16_t port) {
std::string string_port = base::StringPrintf("%d", port);
std::string path_with_replacements;
net::test_server::GetFilePathWithReplacements(
path, {{"REPLACE_WITH_PORT", port}}, &path_with_replacements);
return server->GetURL(host, path_with_replacements);
path, {{"REPLACE_WITH_PORT", string_port}}, &path_with_replacements);
return path_with_replacements;
}
GURL GetDataURLWithContent(const std::string& content) {
......@@ -275,14 +276,27 @@ class TestPreconnectManagerObserver : public PreconnectManager::Observer {
CheckForWaitingLoop();
}
void OnProxyLookupFinished(const GURL& url, bool success) override {
GURL origin = url.GetOrigin();
if (success)
successful_proxy_lookups_.insert(origin);
else
unsuccessful_proxy_lookups_.insert(origin);
CheckForWaitingLoop();
}
void WaitUntilHostLookedUp(const std::string& host) {
base::RunLoop run_loop;
wait_event_ = WaitEvent::kDns;
DCHECK(waiting_on_dns_.empty());
DCHECK(!dns_run_loop_);
dns_run_loop_ = &run_loop;
waiting_on_dns_ = host;
CheckForWaitingLoop();
run_loop.Run();
Wait();
}
void WaitUntilProxyLookedUp(const GURL& url) {
wait_event_ = WaitEvent::kProxy;
DCHECK(waiting_on_proxy_.is_empty());
waiting_on_proxy_ = url;
Wait();
}
bool HasOriginAttemptedToPreconnect(const GURL& origin) {
......@@ -295,26 +309,62 @@ class TestPreconnectManagerObserver : public PreconnectManager::Observer {
base::ContainsKey(unsuccessful_dns_lookups_, host);
}
bool HostFound(const GURL& url) {
return base::ContainsKey(successful_dns_lookups_, url.host());
bool HostFound(const std::string& host) {
return base::ContainsKey(successful_dns_lookups_, host);
}
bool HasProxyBeenLookedUp(const GURL& url) {
return base::ContainsKey(successful_proxy_lookups_, url.GetOrigin()) ||
base::ContainsKey(unsuccessful_proxy_lookups_, url.GetOrigin());
}
bool ProxyFound(const GURL& url) {
return base::ContainsKey(successful_proxy_lookups_, url.GetOrigin());
}
private:
enum class WaitEvent { kNone, kDns, kProxy };
void Wait() {
base::RunLoop run_loop;
DCHECK(!run_loop_);
run_loop_ = &run_loop;
CheckForWaitingLoop();
run_loop.Run();
}
void CheckForWaitingLoop() {
if (waiting_on_dns_.empty())
return;
if (!HasHostBeenLookedUp(waiting_on_dns_))
return;
DCHECK(dns_run_loop_);
waiting_on_dns_ = std::string();
dns_run_loop_->Quit();
dns_run_loop_ = nullptr;
switch (wait_event_) {
case WaitEvent::kNone:
return;
case WaitEvent::kDns:
if (!HasHostBeenLookedUp(waiting_on_dns_))
return;
waiting_on_dns_ = std::string();
break;
case WaitEvent::kProxy:
if (!HasProxyBeenLookedUp(waiting_on_proxy_))
return;
waiting_on_proxy_ = GURL();
break;
}
DCHECK(run_loop_);
run_loop_->Quit();
run_loop_ = nullptr;
wait_event_ = WaitEvent::kNone;
}
WaitEvent wait_event_ = WaitEvent::kNone;
base::RunLoop* run_loop_ = nullptr;
std::string waiting_on_dns_;
base::RunLoop* dns_run_loop_ = nullptr;
std::set<std::string> successful_dns_lookups_;
std::set<std::string> unsuccessful_dns_lookups_;
GURL waiting_on_proxy_;
std::set<GURL> successful_proxy_lookups_;
std::set<GURL> unsuccessful_proxy_lookups_;
std::set<GURL> preconnect_url_attempts_;
};
......@@ -323,6 +373,11 @@ class LoadingPredictorBrowserTest : public InProcessBrowserTest {
LoadingPredictorBrowserTest() {}
~LoadingPredictorBrowserTest() override {}
void SetUp() override {
ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
InProcessBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
......@@ -330,7 +385,7 @@ class LoadingPredictorBrowserTest : public InProcessBrowserTest {
connection_listener_ =
std::make_unique<ConnectionListener>(connection_tracker_.get());
embedded_test_server()->SetConnectionListener(connection_listener_.get());
ASSERT_TRUE(embedded_test_server()->Start());
embedded_test_server()->StartAcceptingConnections();
loading_predictor_ =
LoadingPredictorFactory::GetForProfile(browser()->profile());
......@@ -483,8 +538,9 @@ IN_PROC_BROWSER_TEST_F(LoadingPredictorBrowserTest,
IN_PROC_BROWSER_TEST_F(LoadingPredictorBrowserTest,
PrepareForPageLoadWithoutPrediction) {
// Navigate the first time to fill the HTTP cache.
GURL url = GetURLWithReplacements(embedded_test_server(), "test.com",
kHtmlSubresourcesPath);
GURL url = embedded_test_server()->GetURL(
"test.com", GetPathWithPortReplacement(kHtmlSubresourcesPath,
embedded_test_server()->port()));
ui_test_utils::NavigateToURL(browser(), url);
ResetNetworkState();
ResetPredictorState();
......@@ -495,7 +551,7 @@ IN_PROC_BROWSER_TEST_F(LoadingPredictorBrowserTest,
browser(), url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
preconnect_manager_observer()->WaitUntilHostLookedUp(url.host());
EXPECT_TRUE(preconnect_manager_observer()->HostFound(url));
EXPECT_TRUE(preconnect_manager_observer()->HostFound(url.host()));
// We should preconnect only 2 sockets for the main frame host.
const size_t expected_connections = 2;
connection_tracker()->WaitForAcceptedConnections(expected_connections);
......@@ -508,8 +564,9 @@ IN_PROC_BROWSER_TEST_F(LoadingPredictorBrowserTest,
// Tests that the LoadingPredictor has a prediction for a host after navigating
// to it.
IN_PROC_BROWSER_TEST_F(LoadingPredictorBrowserTest, LearnFromNavigation) {
GURL url = GetURLWithReplacements(embedded_test_server(), "test.com",
kHtmlSubresourcesPath);
GURL url = embedded_test_server()->GetURL(
"test.com", GetPathWithPortReplacement(kHtmlSubresourcesPath,
embedded_test_server()->port()));
std::vector<PreconnectRequest> requests;
for (const auto& host : kHtmlSubresourcesHosts)
requests.emplace_back(embedded_test_server()->GetURL(host, "/"), 1);
......@@ -527,8 +584,9 @@ IN_PROC_BROWSER_TEST_F(LoadingPredictorBrowserTest, LearnFromNavigation) {
// redirect.
IN_PROC_BROWSER_TEST_F(LoadingPredictorBrowserTest,
LearnFromNavigationWithRedirect) {
GURL redirect_url = GetURLWithReplacements(embedded_test_server(), "test.com",
kHtmlSubresourcesPath);
GURL redirect_url = embedded_test_server()->GetURL(
"test.com", GetPathWithPortReplacement(kHtmlSubresourcesPath,
embedded_test_server()->port()));
GURL original_url = embedded_test_server()->GetURL(
"redirect.com",
base::StringPrintf("/server-redirect?%s", redirect_url.spec().c_str()));
......@@ -565,8 +623,9 @@ IN_PROC_BROWSER_TEST_F(LoadingPredictorBrowserTest,
PrepareForPageLoadWithPrediction) {
// Navigate the first time to fill the predictor's database and the HTTP
// cache.
GURL url = GetURLWithReplacements(embedded_test_server(), "test.com",
kHtmlSubresourcesPath);
GURL url = embedded_test_server()->GetURL(
"test.com", GetPathWithPortReplacement(kHtmlSubresourcesPath,
embedded_test_server()->port()));
ui_test_utils::NavigateToURL(browser(), url);
ResetNetworkState();
......@@ -578,7 +637,7 @@ IN_PROC_BROWSER_TEST_F(LoadingPredictorBrowserTest,
for (const auto& host : kHtmlSubresourcesHosts) {
GURL url(base::StringPrintf("http://%s", host.c_str()));
preconnect_manager_observer()->WaitUntilHostLookedUp(url.host());
EXPECT_TRUE(preconnect_manager_observer()->HostFound(url));
EXPECT_TRUE(preconnect_manager_observer()->HostFound(url.host()));
}
// 2 connections to the main frame host + 1 connection per host for others.
const size_t expected_connections = base::size(kHtmlSubresourcesHosts) + 1;
......@@ -597,7 +656,8 @@ IN_PROC_BROWSER_TEST_F(LoadingPredictorBrowserTest, DnsPrefetch) {
GURL(kChromiumUrl).host());
EXPECT_FALSE(preconnect_manager_observer()->HasHostBeenLookedUp(
GURL(kInvalidLongUrl).host()));
EXPECT_TRUE(preconnect_manager_observer()->HostFound(GURL(kChromiumUrl)));
EXPECT_TRUE(
preconnect_manager_observer()->HostFound(GURL(kChromiumUrl).host()));
}
// Tests that preconnect warms up a socket connection to a test server.
......@@ -714,4 +774,88 @@ IN_PROC_BROWSER_TEST_F(LoadingPredictorBrowserTest,
EXPECT_EQ(1u, connection_tracker()->GetReadSocketCount());
}
class LoadingPredictorBrowserTestWithProxy
: public LoadingPredictorBrowserTest {
public:
void SetUp() override {
pac_script_server_ = std::make_unique<net::EmbeddedTestServer>();
pac_script_server_->AddDefaultHandlers(
base::FilePath(FILE_PATH_LITERAL("chrome/test/data")));
ASSERT_TRUE(pac_script_server_->InitializeAndListen());
LoadingPredictorBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
LoadingPredictorBrowserTest::SetUpOnMainThread();
// This will make all dns requests fail.
host_resolver()->ClearRules();
pac_script_server_->StartAcceptingConnections();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
GURL pac_url = pac_script_server_->GetURL(GetPathWithPortReplacement(
"/predictors/proxy.pac", embedded_test_server()->port()));
command_line->AppendSwitchASCII(switches::kProxyPacUrl, pac_url.spec());
}
private:
std::unique_ptr<net::EmbeddedTestServer> pac_script_server_;
};
IN_PROC_BROWSER_TEST_F(LoadingPredictorBrowserTestWithProxy,
PrepareForPageLoadWithoutPrediction) {
// Navigate the first time to fill the HTTP cache.
GURL url = embedded_test_server()->GetURL(
"test.com", GetPathWithPortReplacement(kHtmlSubresourcesPath,
embedded_test_server()->port()));
ui_test_utils::NavigateToURL(browser(), url);
ResetNetworkState();
ResetPredictorState();
// Open in a new foreground tab to avoid being classified as a reload since
// reload requests are always revalidated.
ui_test_utils::NavigateToURLWithDisposition(
browser(), url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
preconnect_manager_observer()->WaitUntilProxyLookedUp(url);
EXPECT_TRUE(preconnect_manager_observer()->ProxyFound(url));
// We should preconnect only 2 sockets for the main frame host.
const size_t expected_connections = 2;
connection_tracker()->WaitForAcceptedConnections(expected_connections);
EXPECT_EQ(expected_connections,
connection_tracker()->GetAcceptedSocketCount());
// No reads since all resources should be cached.
EXPECT_EQ(0u, connection_tracker()->GetReadSocketCount());
}
IN_PROC_BROWSER_TEST_F(LoadingPredictorBrowserTestWithProxy,
PrepareForPageLoadWithPrediction) {
// Navigate the first time to fill the predictor's database and the HTTP
// cache.
GURL url = embedded_test_server()->GetURL(
"test.com", GetPathWithPortReplacement(kHtmlSubresourcesPath,
embedded_test_server()->port()));
ui_test_utils::NavigateToURL(browser(), url);
ResetNetworkState();
// Open in a new foreground tab to avoid being classified as a reload since
// reload requests are always revalidated.
ui_test_utils::NavigateToURLWithDisposition(
browser(), url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
for (const auto& host : kHtmlSubresourcesHosts) {
GURL url = embedded_test_server()->GetURL(host, "/");
preconnect_manager_observer()->WaitUntilProxyLookedUp(url);
EXPECT_TRUE(preconnect_manager_observer()->ProxyFound(url));
}
// 2 connections to the main frame host + 1 connection per host for others.
const size_t expected_connections = base::size(kHtmlSubresourcesHosts) + 1;
connection_tracker()->WaitForAcceptedConnections(expected_connections);
EXPECT_EQ(expected_connections,
connection_tracker()->GetAcceptedSocketCount());
// No reads since all resources should be cached.
EXPECT_EQ(0u, connection_tracker()->GetReadSocketCount());
}
} // namespace predictors
......@@ -174,11 +174,11 @@ TEST_F(LoadingStatsCollectorTest, TestPreconnectHistograms) {
// Initialize PreconnectStats.
// These two are hits.
PreconnectedRequestStats origin1(GURL(gen(1)).GetOrigin(), false, true);
PreconnectedRequestStats origin2(GURL(gen(2)).GetOrigin(), true, false);
PreconnectedRequestStats origin1(GURL(gen(1)).GetOrigin(), true);
PreconnectedRequestStats origin2(GURL(gen(2)).GetOrigin(), false);
// And these two are misses.
PreconnectedRequestStats origin3(GURL(gen(3)).GetOrigin(), false, false);
PreconnectedRequestStats origin4(GURL(gen(4)).GetOrigin(), true, true);
PreconnectedRequestStats origin3(GURL(gen(3)).GetOrigin(), false);
PreconnectedRequestStats origin4(GURL(gen(4)).GetOrigin(), true);
auto stats = std::make_unique<PreconnectStats>(GURL(main_frame_url));
stats->requests_stats = {origin1, origin2, origin3, origin4};
......@@ -257,11 +257,11 @@ TEST_F(LoadingStatsCollectorTest, TestPreconnectHistogramsPreresolvesOnly) {
// Initialize PreconnectStats.
// These two are hits.
PreconnectedRequestStats origin1(GURL(gen(1)).GetOrigin(), false, false);
PreconnectedRequestStats origin2(GURL(gen(2)).GetOrigin(), true, false);
PreconnectedRequestStats origin1(GURL(gen(1)).GetOrigin(), false);
PreconnectedRequestStats origin2(GURL(gen(2)).GetOrigin(), false);
// And these two are misses.
PreconnectedRequestStats origin3(GURL(gen(3)).GetOrigin(), false, false);
PreconnectedRequestStats origin4(GURL(gen(4)).GetOrigin(), true, false);
PreconnectedRequestStats origin3(GURL(gen(3)).GetOrigin(), false);
PreconnectedRequestStats origin4(GURL(gen(4)).GetOrigin(), false);
auto stats = std::make_unique<PreconnectStats>(GURL(main_frame_url));
stats->requests_stats = {origin1, origin2, origin3, origin4};
......
......@@ -19,10 +19,8 @@ namespace predictors {
const bool kAllowCredentialsOnPreconnectByDefault = true;
PreconnectedRequestStats::PreconnectedRequestStats(const GURL& origin,
bool was_preresolve_cached,
bool was_preconnected)
: origin(origin),
was_preresolve_cached(was_preresolve_cached),
was_preconnected(was_preconnected) {}
PreconnectedRequestStats::PreconnectedRequestStats(
......@@ -141,7 +139,6 @@ void PreconnectManager::Stop(const GURL& url) {
}
void PreconnectManager::PreconnectUrl(const GURL& url,
const GURL& site_for_cookies,
int num_sockets,
bool allow_credentials) const {
DCHECK(url.GetOrigin() == url);
......@@ -171,8 +168,6 @@ std::unique_ptr<ResolveHostClientImpl> PreconnectManager::PreresolveUrl(
ResolveHostCallback callback) const {
DCHECK(url.GetOrigin() == url);
DCHECK(url.SchemeIsHTTPOrHTTPS());
if (observer_)
observer_->OnPreresolveUrl(url);
auto* network_context = GetNetworkContext();
if (!network_context) {
......@@ -185,7 +180,24 @@ std::unique_ptr<ResolveHostClientImpl> PreconnectManager::PreresolveUrl(
}
return std::make_unique<ResolveHostClientImpl>(url, std::move(callback),
GetNetworkContext());
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) {
// It's okay to not invoke the callback here because PreresolveUrl()
// callback will be invoked.
return nullptr;
}
return std::make_unique<ProxyLookupClientImpl>(url, std::move(callback),
network_context);
}
void PreconnectManager::TryToLaunchPreresolveJobs() {
......@@ -200,7 +212,13 @@ void PreconnectManager::TryToLaunchPreresolveJobs() {
PreresolveInfo* info = job->info;
if (!(info && info->was_canceled)) {
// TODO(crbug.com/838763): Preconnect to url if proxy is enabled.
// 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));
job->resolve_host_client = PreresolveUrl(
job->url, base::BindOnce(&PreconnectManager::OnPreresolveFinished,
weak_factory_.GetWeakPtr(), job_id));
......@@ -221,37 +239,52 @@ void PreconnectManager::OnPreresolveFinished(PreresolveJobId job_id,
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
PreresolveJob* job = preresolve_jobs_.Lookup(job_id);
DCHECK(job);
PreresolveInfo* info = job->info;
if (observer_)
observer_->OnPreresolveFinished(job->url, success);
FinishPreresolve(job_id, success, false);
--inflight_preresolves_count_;
if (info)
--info->inflight_count;
if (info && info->is_done())
AllPreresolvesForUrlFinished(info);
TryToLaunchPreresolveJobs();
job->resolve_host_client = nullptr;
FinishPreresolveJob(job_id, success);
}
void PreconnectManager::FinishPreresolve(PreresolveJobId job_id,
bool found,
bool cached) {
void PreconnectManager::OnProxyLookupFinished(PreresolveJobId job_id,
bool success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
PreresolveJob* job = preresolve_jobs_.Lookup(job_id);
DCHECK(job);
PreresolveInfo* info = job->info;
bool need_preconnect =
found && job->need_preconnect() && (!info || !info->was_canceled);
if (need_preconnect) {
PreconnectUrl(job->url, info ? info->url : GURL(), job->num_sockets,
job->allow_credentials);
}
if (info && found)
info->stats->requests_stats.emplace_back(job->url, cached, need_preconnect);
if (observer_)
observer_->OnProxyLookupFinished(job->url, success);
job->proxy_lookup_client = nullptr;
FinishPreresolveJob(job_id, success);
}
void PreconnectManager::FinishPreresolveJob(PreresolveJobId job_id,
bool success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
PreresolveJob* job = preresolve_jobs_.Lookup(job_id);
DCHECK(job);
// In case one of the clients failed, wait for the second one, because it may
// be successful.
if (!success && (job->resolve_host_client || job->proxy_lookup_client))
return;
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)
--info->inflight_count;
if (info && info->is_done())
AllPreresolvesForUrlFinished(info);
TryToLaunchPreresolveJobs();
}
void PreconnectManager::AllPreresolvesForUrlFinished(PreresolveInfo* info) {
......
......@@ -14,6 +14,7 @@
#include "base/containers/id_map.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/predictors/proxy_lookup_client_impl.h"
#include "chrome/browser/predictors/resolve_host_client_impl.h"
#include "chrome/browser/predictors/resource_prefetch_predictor.h"
#include "url/gurl.h"
......@@ -32,13 +33,11 @@ struct PreconnectRequest;
struct PreconnectedRequestStats {
PreconnectedRequestStats(const GURL& origin,
bool was_preresolve_cached,
bool was_preconnected);
PreconnectedRequestStats(const PreconnectedRequestStats& other);
~PreconnectedRequestStats();
GURL origin;
bool was_preresolve_cached;
bool was_preconnected;
};
......@@ -79,7 +78,9 @@ struct PreresolveJob {
PreresolveInfo* info);
PreresolveJob(PreresolveJob&& other);
~PreresolveJob();
bool need_preconnect() const { return num_sockets > 0; }
bool need_preconnect() const {
return num_sockets > 0 && !(info && info->was_canceled);
}
GURL url;
int num_sockets;
......@@ -90,6 +91,7 @@ struct PreresolveJob {
// May be equal to nullptr in case of detached job.
PreresolveInfo* info;
std::unique_ptr<ResolveHostClientImpl> resolve_host_client;
std::unique_ptr<ProxyLookupClientImpl> proxy_lookup_client;
DISALLOW_COPY_AND_ASSIGN(PreresolveJob);
};
......@@ -125,9 +127,9 @@ class PreconnectManager {
virtual void OnPreconnectUrl(const GURL& url,
int num_sockets,
bool allow_credentials) {}
virtual void OnPreresolveUrl(const GURL& url) {}
virtual void OnPreresolveFinished(const GURL& url, bool success) {}
virtual void OnProxyLookupFinished(const GURL& url, bool success) {}
};
static const size_t kMaxInflightPreresolves = 3;
......@@ -165,16 +167,19 @@ class PreconnectManager {
friend class PreconnectManagerTest;
void PreconnectUrl(const GURL& url,
const GURL& site_for_cookies,
int num_sockets,
bool allow_credentials) const;
std::unique_ptr<ResolveHostClientImpl> PreresolveUrl(
const GURL& url,
ResolveHostCallback callback) const;
std::unique_ptr<ProxyLookupClientImpl> LookupProxyForUrl(
const GURL& url,
ProxyLookupCallback callback) const;
void TryToLaunchPreresolveJobs();
void OnPreresolveFinished(PreresolveJobId job_id, bool success);
void FinishPreresolve(PreresolveJobId job_id, bool found, bool cached);
void OnProxyLookupFinished(PreresolveJobId job_id, bool success);
void FinishPreresolveJob(PreresolveJobId job_id, bool success);
void AllPreresolvesForUrlFinished(PreresolveInfo* info);
network::mojom::NetworkContext* GetNetworkContext() const;
......
......@@ -14,6 +14,8 @@
#include "base/strings/stringprintf.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/predictors/loading_test_util.h"
#include "chrome/browser/predictors/proxy_lookup_client_impl.h"
#include "chrome/browser/predictors/resolve_host_client_impl.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "net/base/load_flags.h"
......@@ -34,6 +36,18 @@ constexpr int kPrivateLoadFlags = net::LOAD_DO_NOT_SEND_COOKIES |
net::LOAD_DO_NOT_SAVE_COOKIES |
net::LOAD_DO_NOT_SEND_AUTH_DATA;
net::ProxyInfo GetIndirectProxyInfo() {
net::ProxyInfo proxy_info;
proxy_info.UseNamedProxy("proxy.com");
return proxy_info;
}
net::ProxyInfo GetDirectProxyInfo() {
net::ProxyInfo proxy_info;
proxy_info.UseDirect();
return proxy_info;
}
class MockPreconnectManagerDelegate
: public PreconnectManager::Delegate,
public base::SupportsWeakPtr<MockPreconnectManagerDelegate> {
......@@ -51,12 +65,12 @@ class MockNetworkContext : public network::TestNetworkContext {
MockNetworkContext() = default;
~MockNetworkContext() override {
EXPECT_TRUE(resolve_host_clients_.empty())
<< "Not all requests were satisfied";
<< "Not all resolve host requests were satisfied";
};
void ResolveHost(
const net::HostPortPair& host_port,
network::mojom::ResolveHostParametersPtr control_handle,
network::mojom::ResolveHostParametersPtr optional_parameters,
network::mojom::ResolveHostClientPtr response_client) override {
const std::string& host = host_port.host();
EXPECT_TRUE(
......@@ -64,7 +78,15 @@ class MockNetworkContext : public network::TestNetworkContext {
ResolveHostProxy(host);
}
void CompleteRequest(const std::string& host, int result) {
void LookUpProxyForURL(
const GURL& url,
network::mojom::ProxyLookupClientPtr proxy_lookup_client) override {
EXPECT_TRUE(
proxy_lookup_clients_.emplace(url, std::move(proxy_lookup_client))
.second);
}
void CompleteHostLookup(const std::string& host, int result) {
auto it = resolve_host_clients_.find(host);
if (it == resolve_host_clients_.end()) {
ADD_FAILURE() << host << " wasn't found";
......@@ -76,6 +98,19 @@ class MockNetworkContext : public network::TestNetworkContext {
base::RunLoop().RunUntilIdle();
}
void CompleteProxyLookup(const GURL& url,
const base::Optional<net::ProxyInfo>& result) {
auto it = proxy_lookup_clients_.find(url);
if (it == proxy_lookup_clients_.end()) {
ADD_FAILURE() << url.spec() << " wasn't found";
return;
}
it->second->OnProxyLookupComplete(result);
proxy_lookup_clients_.erase(it);
// Wait for OnProxyLookupComplete() to be executed on the UI thread.
base::RunLoop().RunUntilIdle();
}
MOCK_METHOD1(ResolveHostProxy, void(const std::string& host));
MOCK_METHOD4(PreconnectSockets,
void(uint32_t num_streams,
......@@ -86,6 +121,7 @@ class MockNetworkContext : public network::TestNetworkContext {
private:
std::map<std::string, network::mojom::ResolveHostClientPtr>
resolve_host_clients_;
std::map<GURL, network::mojom::ProxyLookupClientPtr> proxy_lookup_clients_;
};
class PreconnectManagerTest : public testing::Test {
......@@ -126,7 +162,7 @@ TEST_F(PreconnectManagerTest, TestStartOneUrlPreresolve) {
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
preconnect_manager_->Start(main_frame_url,
{PreconnectRequest(url_to_preresolve, 0)});
mock_network_context_->CompleteRequest(url_to_preresolve.host(), net::OK);
mock_network_context_->CompleteHostLookup(url_to_preresolve.host(), net::OK);
}
TEST_F(PreconnectManagerTest, TestStartOneUrlPreconnect) {
......@@ -140,7 +176,7 @@ TEST_F(PreconnectManagerTest, TestStartOneUrlPreconnect) {
EXPECT_CALL(*mock_network_context_,
PreconnectSockets(1, url_to_preconnect, kNormalLoadFlags, false));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
mock_network_context_->CompleteRequest(url_to_preconnect.host(), net::OK);
mock_network_context_->CompleteHostLookup(url_to_preconnect.host(), net::OK);
}
TEST_F(PreconnectManagerTest, TestStopOneUrlBeforePreconnect) {
......@@ -156,7 +192,7 @@ TEST_F(PreconnectManagerTest, TestStopOneUrlBeforePreconnect) {
// Stop all jobs for |main_frame_url| before we get the callback.
preconnect_manager_->Stop(main_frame_url);
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
mock_network_context_->CompleteRequest(url_to_preconnect.host(), net::OK);
mock_network_context_->CompleteHostLookup(url_to_preconnect.host(), net::OK);
}
TEST_F(PreconnectManagerTest, TestGetCallbackAfterDestruction) {
......@@ -169,7 +205,7 @@ TEST_F(PreconnectManagerTest, TestGetCallbackAfterDestruction) {
// Callback may outlive PreconnectManager but it shouldn't cause a crash.
preconnect_manager_ = nullptr;
mock_network_context_->CompleteRequest(url_to_preconnect.host(), net::OK);
mock_network_context_->CompleteHostLookup(url_to_preconnect.host(), net::OK);
}
TEST_F(PreconnectManagerTest, TestUnqueuedPreresolvesCanceled) {
......@@ -190,7 +226,8 @@ TEST_F(PreconnectManagerTest, TestUnqueuedPreresolvesCanceled) {
preconnect_manager_->Stop(main_frame_url);
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
for (size_t i = 0; i < count; ++i)
mock_network_context_->CompleteRequest(requests[i].origin.host(), net::OK);
mock_network_context_->CompleteHostLookup(requests[i].origin.host(),
net::OK);
}
TEST_F(PreconnectManagerTest, TestTwoConcurrentMainFrameUrls) {
......@@ -216,10 +253,10 @@ TEST_F(PreconnectManagerTest, TestTwoConcurrentMainFrameUrls) {
*mock_network_context_,
PreconnectSockets(1, url_to_preconnect1, kNormalLoadFlags, false));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url1));
mock_network_context_->CompleteRequest(url_to_preconnect1.host(), net::OK);
mock_network_context_->CompleteHostLookup(url_to_preconnect1.host(), net::OK);
// No preconnect for the second url.
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url2));
mock_network_context_->CompleteRequest(url_to_preconnect2.host(), net::OK);
mock_network_context_->CompleteHostLookup(url_to_preconnect2.host(), net::OK);
}
// Checks that the PreconnectManager handles no more than one URL per host
......@@ -243,7 +280,7 @@ TEST_F(PreconnectManagerTest, TestTwoConcurrentSameHostMainFrameUrls) {
*mock_network_context_,
PreconnectSockets(1, url_to_preconnect1, kNormalLoadFlags, false));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url1));
mock_network_context_->CompleteRequest(url_to_preconnect1.host(), net::OK);
mock_network_context_->CompleteHostLookup(url_to_preconnect1.host(), net::OK);
}
TEST_F(PreconnectManagerTest, TestStartPreresolveHost) {
......@@ -253,7 +290,7 @@ TEST_F(PreconnectManagerTest, TestStartPreresolveHost) {
// PreconnectFinished shouldn't be called.
EXPECT_CALL(*mock_network_context_, ResolveHostProxy(origin.host()));
preconnect_manager_->StartPreresolveHost(url);
mock_network_context_->CompleteRequest(origin.host(), net::OK);
mock_network_context_->CompleteHostLookup(origin.host(), net::OK);
// Non http url shouldn't be preresovled.
GURL non_http_url("file:///tmp/index.html");
......@@ -267,8 +304,8 @@ TEST_F(PreconnectManagerTest, TestStartPreresolveHosts) {
EXPECT_CALL(*mock_network_context_, ResolveHostProxy(cdn.host()));
EXPECT_CALL(*mock_network_context_, ResolveHostProxy(fonts.host()));
preconnect_manager_->StartPreresolveHosts({cdn.host(), fonts.host()});
mock_network_context_->CompleteRequest(cdn.host(), net::OK);
mock_network_context_->CompleteRequest(fonts.host(), net::OK);
mock_network_context_->CompleteHostLookup(cdn.host(), net::OK);
mock_network_context_->CompleteHostLookup(fonts.host(), net::OK);
}
TEST_F(PreconnectManagerTest, TestStartPreconnectUrl) {
......@@ -282,7 +319,7 @@ TEST_F(PreconnectManagerTest, TestStartPreconnectUrl) {
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, origin, kPrivateLoadFlags, !allow_credentials));
mock_network_context_->CompleteRequest(origin.host(), net::OK);
mock_network_context_->CompleteHostLookup(origin.host(), net::OK);
// Non http url shouldn't be preconnected.
GURL non_http_url("file:///tmp/index.html");
......@@ -312,16 +349,112 @@ TEST_F(PreconnectManagerTest, TestDetachedRequestHasHigherPriority) {
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(detached_preresolve.host()));
mock_network_context_->CompleteRequest(requests[0].origin.host(), net::OK);
mock_network_context_->CompleteHostLookup(requests[0].origin.host(), net::OK);
Mock::VerifyAndClearExpectations(preconnect_manager_.get());
EXPECT_CALL(*mock_network_context_, ResolveHostProxy(queued_url.host()));
mock_network_context_->CompleteRequest(detached_preresolve.host(), net::OK);
mock_network_context_->CompleteRequest(queued_url.host(), net::OK);
mock_network_context_->CompleteHostLookup(detached_preresolve.host(),
net::OK);
mock_network_context_->CompleteHostLookup(queued_url.host(), net::OK);
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
for (size_t i = 1; i < count; ++i)
mock_network_context_->CompleteRequest(requests[i].origin.host(), net::OK);
mock_network_context_->CompleteHostLookup(requests[i].origin.host(),
net::OK);
}
TEST_F(PreconnectManagerTest, TestSuccessfulProxyLookup) {
GURL main_frame_url("http://google.com");
GURL url_to_preconnect("http://cdn.google.com");
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(url_to_preconnect.host()));
preconnect_manager_->Start(main_frame_url,
{PreconnectRequest(url_to_preconnect, 1)});
EXPECT_CALL(*mock_network_context_,
PreconnectSockets(1, url_to_preconnect, kNormalLoadFlags, false));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
mock_network_context_->CompleteProxyLookup(url_to_preconnect,
GetIndirectProxyInfo());
// We should preconnect socket before the host lookup is complete.
Mock::VerifyAndClearExpectations(mock_network_context_.get());
mock_network_context_->CompleteHostLookup(url_to_preconnect.host(), net::OK);
}
TEST_F(PreconnectManagerTest, TestSuccessfulProxyLookupAfterPreresolveFailure) {
GURL main_frame_url("http://google.com");
GURL url_to_preconnect("http://cdn.google.com");
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(url_to_preconnect.host()));
preconnect_manager_->Start(main_frame_url,
{PreconnectRequest(url_to_preconnect, 1)});
mock_network_context_->CompleteHostLookup(url_to_preconnect.host(),
net::ERR_FAILED);
Mock::VerifyAndClearExpectations(mock_network_context_.get());
EXPECT_CALL(*mock_network_context_,
PreconnectSockets(1, url_to_preconnect, kNormalLoadFlags, false));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
mock_network_context_->CompleteProxyLookup(url_to_preconnect,
GetIndirectProxyInfo());
}
TEST_F(PreconnectManagerTest, TestSuccessfulHostLookupAfterProxyLookupFailure) {
GURL main_frame_url("http://google.com");
GURL url_to_preconnect("http://cdn.google.com");
GURL url_to_preconnect2("http://ads.google.com");
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(url_to_preconnect.host()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(url_to_preconnect2.host()));
preconnect_manager_->Start(main_frame_url,
{PreconnectRequest(url_to_preconnect, 1),
PreconnectRequest(url_to_preconnect2, 1)});
// First URL uses direct connection.
mock_network_context_->CompleteProxyLookup(url_to_preconnect,
GetDirectProxyInfo());
// Second URL proxy lookup failed.
mock_network_context_->CompleteProxyLookup(url_to_preconnect2, base::nullopt);
Mock::VerifyAndClearExpectations(mock_network_context_.get());
EXPECT_CALL(*mock_network_context_,
PreconnectSockets(1, url_to_preconnect, kNormalLoadFlags, false));
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, url_to_preconnect2, kNormalLoadFlags, false));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
mock_network_context_->CompleteHostLookup(url_to_preconnect.host(), net::OK);
mock_network_context_->CompleteHostLookup(url_to_preconnect2.host(), net::OK);
}
TEST_F(PreconnectManagerTest, TestBothProxyAndHostLookupFailed) {
GURL main_frame_url("http://google.com");
GURL url_to_preconnect("http://cdn.google.com");
GURL url_to_preconnect2("http://ads.google.com");
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(url_to_preconnect.host()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(url_to_preconnect2.host()));
preconnect_manager_->Start(main_frame_url,
{PreconnectRequest(url_to_preconnect, 1),
PreconnectRequest(url_to_preconnect2, 1)});
// Test two failures in different order:
// proxy -> host for |url_to_preconnect|
// host -> proxy for |url_to_preconnect2|
mock_network_context_->CompleteProxyLookup(url_to_preconnect, base::nullopt);
mock_network_context_->CompleteHostLookup(url_to_preconnect2.host(),
net::ERR_FAILED);
Mock::VerifyAndClearExpectations(mock_network_context_.get());
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
mock_network_context_->CompleteHostLookup(url_to_preconnect.host(),
net::ERR_FAILED);
mock_network_context_->CompleteProxyLookup(url_to_preconnect2, base::nullopt);
}
} // namespace predictors
// Copyright 2018 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/proxy_lookup_client_impl.h"
#include <utility>
#include "base/bind.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "net/proxy_resolution/proxy_info.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "url/gurl.h"
namespace predictors {
ProxyLookupClientImpl::ProxyLookupClientImpl(
const GURL& url,
ProxyLookupCallback callback,
network::mojom::NetworkContext* network_context)
: binding_(this), callback_(std::move(callback)) {
network::mojom::ProxyLookupClientPtr proxy_lookup_client_ptr;
binding_.Bind(mojo::MakeRequest(&proxy_lookup_client_ptr));
network_context->LookUpProxyForURL(url, std::move(proxy_lookup_client_ptr));
binding_.set_connection_error_handler(
base::BindOnce(&ProxyLookupClientImpl::OnProxyLookupComplete,
base::Unretained(this), base::nullopt));
}
ProxyLookupClientImpl::~ProxyLookupClientImpl() = default;
void ProxyLookupClientImpl::OnProxyLookupComplete(
const base::Optional<net::ProxyInfo>& proxy_info) {
bool success = proxy_info.has_value() && !proxy_info->is_direct();
std::move(callback_).Run(success);
}
} // namespace predictors
// Copyright 2018 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.
#ifndef CHROME_BROWSER_PREDICTORS_PROXY_LOOKUP_CLIENT_IMPL_H_
#define CHROME_BROWSER_PREDICTORS_PROXY_LOOKUP_CLIENT_IMPL_H_
#include "base/bind.h"
#include "base/macros.h"
#include "base/optional.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "services/network/public/mojom/proxy_lookup_client.mojom.h"
class GURL;
namespace network {
namespace mojom {
class NetworkContext;
}
} // namespace network
namespace predictors {
using ProxyLookupCallback = base::OnceCallback<void(bool success)>;
// This class helps perform the proxy lookup using the NetworkContext.
// An instance of this class must be deleted after the callback is invoked.
class ProxyLookupClientImpl : public network::mojom::ProxyLookupClient {
public:
// Starts the proxy lookup for |url|. |callback| is called when the proxy
// lookup is completed or when an error occurs.
ProxyLookupClientImpl(const GURL& url,
ProxyLookupCallback callback,
network::mojom::NetworkContext* network_context);
// Cancels the request if it hasn't been completed yet.
~ProxyLookupClientImpl() override;
// network::mojom::ProxyLookupClient:
void OnProxyLookupComplete(
const base::Optional<net::ProxyInfo>& proxy_info) override;
private:
mojo::Binding<network::mojom::ProxyLookupClient> binding_;
ProxyLookupCallback callback_;
DISALLOW_COPY_AND_ASSIGN(ProxyLookupClientImpl);
};
} // namespace predictors
#endif // CHROME_BROWSER_PREDICTORS_PROXY_LOOKUP_CLIENT_IMPL_H_
function FindProxyForURL(url, host)
{
if (shExpMatch(host, "test.com|foo.com|baz.com|bar.com"))
{
return "PROXY 127.0.0.1:REPLACE_WITH_PORT";
}
// All other requests don't need a proxy:
return "DIRECT";
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment