1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
7 #include "base/base64.h"
8 #include "base/command_line.h"
9 #include "base/json/json_string_value_serializer.h"
10 #include "base/macros.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/synchronization/lock.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/net/chrome_net_log.h"
15 #include "chrome/browser/net/predictor.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/common/pref_names.h"
19 #include "chrome/test/base/in_process_browser_test.h"
20 #include "chrome/test/base/ui_test_utils.h"
21 #include "content/public/common/content_switches.h"
22 #include "content/public/test/test_utils.h"
23 #include "net/base/host_port_pair.h"
24 #include "net/base/ip_endpoint.h"
25 #include "net/base/net_errors.h"
26 #include "net/dns/host_resolver_proc.h"
27 #include "net/dns/mock_host_resolver.h"
28 #include "net/test/embedded_test_server/embedded_test_server.h"
29 #include "net/test/embedded_test_server/embedded_test_server_connection_listener.h"
30 #include "testing/gmock/include/gmock/gmock.h"
32 using content::BrowserThread
;
33 using testing::HasSubstr
;
37 const char kBlinkPreconnectFeature
[] = "LinkPreconnect";
38 const char kChromiumHostname
[] = "chromium.org";
39 const char kInvalidLongHostname
[] = "illegally-long-hostname-over-255-"
40 "characters-should-not-send-an-ipc-message-to-the-browser-"
41 "0000000000000000000000000000000000000000000000000000000000000000000000000"
42 "0000000000000000000000000000000000000000000000000000000000000000000000000"
43 "000000000000000000000000000000000000000000000000000000.org";
45 // Gets notified by the EmbeddedTestServer on incoming connections being
46 // accepted or read from, keeps track of them and exposes that info to
48 // A port being reused is currently considered an error. If a test
49 // needs to verify multiple connections are opened in sequence, that will need
51 class ConnectionListener
52 : public net::test_server::EmbeddedTestServerConnectionListener
{
54 ConnectionListener() : task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
56 ~ConnectionListener() override
{}
58 // Get called from the EmbeddedTestServer thread to be notified that
59 // a connection was accepted.
61 const net::test_server::StreamListenSocket
& connection
) override
{
62 base::AutoLock
lock(lock_
);
63 uint16_t socket
= GetPort(connection
);
64 EXPECT_TRUE(sockets_
.find(socket
) == sockets_
.end());
66 sockets_
[socket
] = SOCKET_ACCEPTED
;
67 task_runner_
->PostTask(FROM_HERE
, accept_loop_
.QuitClosure());
70 // Get called from the EmbeddedTestServer thread to be notified that
71 // a connection was read from.
73 const net::test_server::StreamListenSocket
& connection
) override
{
74 base::AutoLock
lock(lock_
);
75 uint16_t socket
= GetPort(connection
);
76 EXPECT_FALSE(sockets_
.find(socket
) == sockets_
.end());
78 sockets_
[socket
] = SOCKET_READ_FROM
;
79 task_runner_
->PostTask(FROM_HERE
, read_loop_
.QuitClosure());
82 // Returns the number of sockets that were accepted by the server.
83 size_t GetAcceptedSocketCount() const {
84 base::AutoLock
lock(lock_
);
85 return sockets_
.size();
88 // Returns the number of sockets that were read from by the server.
89 size_t GetReadSocketCount() const {
90 base::AutoLock
lock(lock_
);
91 size_t read_sockets
= 0;
92 for (const auto& socket
: sockets_
) {
93 if (socket
.second
== SOCKET_READ_FROM
)
99 void WaitUntilFirstConnectionAccepted() { accept_loop_
.Run(); }
101 void WaitUntilFirstConnectionRead() { read_loop_
.Run(); }
104 static uint16_t GetPort(
105 const net::test_server::StreamListenSocket
& connection
) {
106 net::IPEndPoint address
;
107 EXPECT_EQ(net::OK
, connection
.GetLocalAddress(&address
));
108 return address
.port();
111 enum SocketStatus
{ SOCKET_ACCEPTED
, SOCKET_READ_FROM
};
113 typedef base::hash_map
<uint16_t, SocketStatus
> SocketContainer
;
114 SocketContainer sockets_
;
116 base::RunLoop accept_loop_
;
117 base::RunLoop read_loop_
;
118 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner_
;
120 mutable base::Lock lock_
;
122 DISALLOW_COPY_AND_ASSIGN(ConnectionListener
);
125 // Records a history of all hostnames for which resolving has been requested,
126 // and immediately fails the resolution requests themselves.
127 class HostResolutionRequestRecorder
: public net::HostResolverProc
{
129 HostResolutionRequestRecorder()
130 : HostResolverProc(NULL
),
131 is_waiting_for_hostname_(false) {
134 int Resolve(const std::string
& host
,
135 net::AddressFamily address_family
,
136 net::HostResolverFlags host_resolver_flags
,
137 net::AddressList
* addrlist
,
138 int* os_error
) override
{
139 BrowserThread::PostTask(
142 base::Bind(&HostResolutionRequestRecorder::AddToHistory
,
143 base::Unretained(this),
145 return net::ERR_NAME_NOT_RESOLVED
;
148 int RequestedHostnameCount() const {
149 return requested_hostnames_
.size();
152 bool HasHostBeenRequested(const std::string
& hostname
) const {
153 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
154 return std::find(requested_hostnames_
.begin(),
155 requested_hostnames_
.end(),
156 hostname
) != requested_hostnames_
.end();
159 void WaitUntilHostHasBeenRequested(const std::string
& hostname
) {
160 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
161 DCHECK(!is_waiting_for_hostname_
);
162 if (HasHostBeenRequested(hostname
))
164 waiting_for_hostname_
= hostname
;
165 is_waiting_for_hostname_
= true;
166 content::RunMessageLoop();
170 ~HostResolutionRequestRecorder() override
{}
172 void AddToHistory(const std::string
& hostname
) {
173 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
174 requested_hostnames_
.push_back(hostname
);
175 if (is_waiting_for_hostname_
&& waiting_for_hostname_
== hostname
) {
176 is_waiting_for_hostname_
= false;
177 waiting_for_hostname_
.clear();
178 base::MessageLoop::current()->Quit();
182 // The hostname which WaitUntilHostHasBeenRequested is currently waiting for
184 std::string waiting_for_hostname_
;
186 // Whether WaitUntilHostHasBeenRequested is waiting for a hostname to be
187 // requested and thus is running a nested message loop.
188 bool is_waiting_for_hostname_
;
190 // A list of hostnames for which resolution has already been requested. Only
191 // to be accessed from the UI thread.
192 std::vector
<std::string
> requested_hostnames_
;
194 DISALLOW_COPY_AND_ASSIGN(HostResolutionRequestRecorder
);
199 namespace chrome_browser_net
{
201 class PredictorBrowserTest
: public InProcessBrowserTest
{
203 PredictorBrowserTest()
204 : startup_url_("http://host1:1"),
205 referring_url_("http://host2:1"),
206 target_url_("http://host3:1"),
207 host_resolution_request_recorder_(new HostResolutionRequestRecorder
) {
211 void SetUpInProcessBrowserTestFixture() override
{
212 scoped_host_resolver_proc_
.reset(new net::ScopedDefaultHostResolverProc(
213 host_resolution_request_recorder_
.get()));
214 InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
217 void SetUpCommandLine(base::CommandLine
* command_line
) override
{
218 command_line
->AppendSwitch(
219 switches::kEnableExperimentalWebPlatformFeatures
);
220 command_line
->AppendSwitchASCII(
221 switches::kEnableBlinkFeatures
, kBlinkPreconnectFeature
);
224 void SetUpOnMainThread() override
{
225 connection_listener_
.reset(new ConnectionListener());
226 embedded_test_server()->SetConnectionListener(connection_listener_
.get());
227 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
230 void TearDownOnMainThread() override
{
231 ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
234 // Navigates to a data URL containing the given content, with a MIME type of
236 void NavigateToDataURLWithContent(const std::string
& content
) {
237 std::string encoded_content
;
238 base::Base64Encode(content
, &encoded_content
);
239 std::string data_uri_content
= "data:text/html;base64," + encoded_content
;
240 ui_test_utils::NavigateToURL(browser(), GURL(data_uri_content
));
243 void TearDownInProcessBrowserTestFixture() override
{
244 InProcessBrowserTest::TearDownInProcessBrowserTestFixture();
245 scoped_host_resolver_proc_
.reset();
248 void LearnAboutInitialNavigation(const GURL
& url
) {
249 Predictor
* predictor
= browser()->profile()->GetNetworkPredictor();
250 BrowserThread::PostTask(BrowserThread::IO
,
252 base::Bind(&Predictor::LearnAboutInitialNavigation
,
253 base::Unretained(predictor
),
255 content::RunAllPendingInMessageLoop(BrowserThread::IO
);
258 void LearnFromNavigation(const GURL
& referring_url
, const GURL
& target_url
) {
259 Predictor
* predictor
= browser()->profile()->GetNetworkPredictor();
260 BrowserThread::PostTask(BrowserThread::IO
,
262 base::Bind(&Predictor::LearnFromNavigation
,
263 base::Unretained(predictor
),
266 content::RunAllPendingInMessageLoop(BrowserThread::IO
);
269 void PrepareFrameSubresources(const GURL
& url
) {
270 Predictor
* predictor
= browser()->profile()->GetNetworkPredictor();
271 predictor
->PredictFrameSubresources(url
, GURL());
274 void GetListFromPrefsAsString(const char* list_path
,
275 std::string
* value_as_string
) const {
276 PrefService
* prefs
= browser()->profile()->GetPrefs();
277 const base::ListValue
* list_value
= prefs
->GetList(list_path
);
278 JSONStringValueSerializer
serializer(value_as_string
);
279 serializer
.Serialize(*list_value
);
282 bool HasHostBeenRequested(const std::string
& hostname
) const {
283 return host_resolution_request_recorder_
->HasHostBeenRequested(hostname
);
286 void WaitUntilHostHasBeenRequested(const std::string
& hostname
) {
287 host_resolution_request_recorder_
->WaitUntilHostHasBeenRequested(hostname
);
290 int RequestedHostnameCount() const {
291 return host_resolution_request_recorder_
->RequestedHostnameCount();
294 const GURL startup_url_
;
295 const GURL referring_url_
;
296 const GURL target_url_
;
297 scoped_ptr
<ConnectionListener
> connection_listener_
;
300 scoped_refptr
<HostResolutionRequestRecorder
>
301 host_resolution_request_recorder_
;
302 scoped_ptr
<net::ScopedDefaultHostResolverProc
> scoped_host_resolver_proc_
;
305 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest
, PRE_ShutdownStartupCycle
) {
306 // Prepare state that will be serialized on this shut-down and read on next
308 LearnAboutInitialNavigation(startup_url_
);
309 LearnFromNavigation(referring_url_
, target_url_
);
312 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest
, ShutdownStartupCycle
) {
313 // Make sure that the Preferences file is actually wiped of all DNS prefetch
314 // related data after start-up.
315 std::string cleared_startup_list
;
316 std::string cleared_referral_list
;
317 GetListFromPrefsAsString(prefs::kDnsPrefetchingStartupList
,
318 &cleared_startup_list
);
319 GetListFromPrefsAsString(prefs::kDnsPrefetchingHostReferralList
,
320 &cleared_referral_list
);
322 EXPECT_THAT(cleared_startup_list
, Not(HasSubstr(startup_url_
.host())));
323 EXPECT_THAT(cleared_referral_list
, Not(HasSubstr(referring_url_
.host())));
324 EXPECT_THAT(cleared_referral_list
, Not(HasSubstr(target_url_
.host())));
326 // But also make sure this data has been first loaded into the Predictor, by
327 // inspecting that the Predictor starts making the expected hostname requests.
328 PrepareFrameSubresources(referring_url_
);
329 WaitUntilHostHasBeenRequested(startup_url_
.host());
330 WaitUntilHostHasBeenRequested(target_url_
.host());
333 // Flaky on Windows: http://crbug.com/469120
335 #define MAYBE_DnsPrefetch DISABLED_DnsPrefetch
337 #define MAYBE_DnsPrefetch DnsPrefetch
339 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest
, MAYBE_DnsPrefetch
) {
340 ASSERT_TRUE(test_server()->Start());
341 int hostnames_requested_before_load
= RequestedHostnameCount();
342 ui_test_utils::NavigateToURL(
344 GURL(test_server()->GetURL("files/predictor/dns_prefetch.html")));
345 WaitUntilHostHasBeenRequested(kChromiumHostname
);
346 ASSERT_FALSE(HasHostBeenRequested(kInvalidLongHostname
));
347 ASSERT_EQ(hostnames_requested_before_load
+ 1, RequestedHostnameCount());
350 // Tests that preconnect warms up a socket connection to a test server.
351 // Note: This test uses a data URI to serve the preconnect hint, to make sure
352 // that the network stack doesn't just re-use its connection to the test server.
353 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest
, Preconnect
) {
354 GURL preconnect_url
= embedded_test_server()->base_url();
355 std::string preconnect_content
=
356 "<link rel=\"preconnect\" href=\"" + preconnect_url
.spec() + "\">";
357 NavigateToDataURLWithContent(preconnect_content
);
358 connection_listener_
->WaitUntilFirstConnectionAccepted();
359 EXPECT_EQ(1u, connection_listener_
->GetAcceptedSocketCount());
360 EXPECT_EQ(0u, connection_listener_
->GetReadSocketCount());
363 // Tests that preconnect warms up a socket connection to a test server,
364 // and that that socket is later used when fetching a resource.
365 // Note: This test uses a data URI to serve the preconnect hint, to make sure
366 // that the network stack doesn't just re-use its connection to the test server.
367 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest
, PreconnectAndUse
) {
368 GURL preconnect_url
= embedded_test_server()->base_url();
369 // First navigation to content with a preconnect hint.
370 std::string preconnect_content
=
371 "<link rel=\"preconnect\" href=\"" + preconnect_url
.spec() + "\">";
372 NavigateToDataURLWithContent(preconnect_content
);
373 connection_listener_
->WaitUntilFirstConnectionAccepted();
374 EXPECT_EQ(1u, connection_listener_
->GetAcceptedSocketCount());
375 EXPECT_EQ(0u, connection_listener_
->GetReadSocketCount());
377 // Second navigation to content with an img.
378 std::string img_content
=
379 "<img src=\"" + preconnect_url
.spec() + "test.gif\">";
380 NavigateToDataURLWithContent(img_content
);
381 connection_listener_
->WaitUntilFirstConnectionRead();
382 EXPECT_EQ(1u, connection_listener_
->GetAcceptedSocketCount());
383 EXPECT_EQ(1u, connection_listener_
->GetReadSocketCount());
386 } // namespace chrome_browser_net