| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include <stddef.h> | 5 #include <stddef.h> |
| 6 #include <stdint.h> | 6 #include <stdint.h> |
| 7 | 7 |
| 8 #include <algorithm> | 8 #include <algorithm> |
| 9 #include <memory> | 9 #include <memory> |
| 10 #include <set> | 10 #include <set> |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 62 net::NetworkDelegate* delegate) { | 62 net::NetworkDelegate* delegate) { |
| 63 const char kPlainTextHeaders[] = | 63 const char kPlainTextHeaders[] = |
| 64 "HTTP/1.1 200 OK\n" | 64 "HTTP/1.1 200 OK\n" |
| 65 "Content-Type: text/plain\n" | 65 "Content-Type: text/plain\n" |
| 66 "Access-Control-Allow-Origin: *\n" | 66 "Access-Control-Allow-Origin: *\n" |
| 67 "\n"; | 67 "\n"; |
| 68 return new net::URLRequestTestJob(request, delegate, kPlainTextHeaders, "", | 68 return new net::URLRequestTestJob(request, delegate, kPlainTextHeaders, "", |
| 69 true); | 69 true); |
| 70 } | 70 } |
| 71 | 71 |
| 72 net::URLRequestJob* CreateRedirectRequestJob(std::string location, |
| 73 net::URLRequest* request, |
| 74 net::NetworkDelegate* delegate) { |
| 75 char kPlainTextHeaders[] = |
| 76 "HTTP/1.1 302 \n" |
| 77 "Location: %s\n" |
| 78 "Access-Control-Allow-Origin: *\n" |
| 79 "\n"; |
| 80 return new net::URLRequestTestJob( |
| 81 request, delegate, |
| 82 base::StringPrintf(kPlainTextHeaders, location.c_str()), "", true); |
| 83 } |
| 84 |
| 72 // Override the test server to redirect requests matching some path. This is | 85 // Override the test server to redirect requests matching some path. This is |
| 73 // used because the predictor only learns simple redirects with a path of "/" | 86 // used because the predictor only learns simple redirects with a path of "/" |
| 74 std::unique_ptr<net::test_server::HttpResponse> RedirectForPathHandler( | 87 std::unique_ptr<net::test_server::HttpResponse> RedirectForPathHandler( |
| 75 const std::string& path, | 88 const std::string& path, |
| 76 const GURL& redirect_url, | 89 const GURL& redirect_url, |
| 77 const net::test_server::HttpRequest& request) { | 90 const net::test_server::HttpRequest& request) { |
| 78 if (request.GetURL().path() != path) | 91 if (request.GetURL().path() != path) |
| 79 return nullptr; | 92 return nullptr; |
| 80 std::unique_ptr<net::test_server::BasicHttpResponse> response( | 93 std::unique_ptr<net::test_server::BasicHttpResponse> response( |
| 81 new net::test_server::BasicHttpResponse); | 94 new net::test_server::BasicHttpResponse); |
| (...skipping 206 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 288 class CrossSitePredictorObserver | 301 class CrossSitePredictorObserver |
| 289 : public chrome_browser_net::PredictorObserver { | 302 : public chrome_browser_net::PredictorObserver { |
| 290 public: | 303 public: |
| 291 CrossSitePredictorObserver(const GURL& source_host, | 304 CrossSitePredictorObserver(const GURL& source_host, |
| 292 const GURL& cross_site_host) | 305 const GURL& cross_site_host) |
| 293 : source_host_(source_host), | 306 : source_host_(source_host), |
| 294 cross_site_host_(cross_site_host), | 307 cross_site_host_(cross_site_host), |
| 295 cross_site_learned_(0), | 308 cross_site_learned_(0), |
| 296 cross_site_preconnected_(0), | 309 cross_site_preconnected_(0), |
| 297 same_site_preconnected_(0), | 310 same_site_preconnected_(0), |
| 298 dns_run_loop_(nullptr) {} | 311 dns_run_loop_(nullptr), |
| 312 strict_(true) {} |
| 299 | 313 |
| 300 void OnPreconnectUrl( | 314 void OnPreconnectUrl( |
| 301 const GURL& original_url, | 315 const GURL& original_url, |
| 302 const GURL& first_party_for_cookies, | 316 const GURL& first_party_for_cookies, |
| 303 chrome_browser_net::UrlInfo::ResolutionMotivation motivation, | 317 chrome_browser_net::UrlInfo::ResolutionMotivation motivation, |
| 304 int count) override { | 318 int count) override { |
| 305 base::AutoLock lock(lock_); | 319 base::AutoLock lock(lock_); |
| 306 if (original_url == cross_site_host_) { | 320 if (original_url == cross_site_host_) { |
| 307 cross_site_preconnected_ = std::max(cross_site_preconnected_, count); | 321 cross_site_preconnected_ = std::max(cross_site_preconnected_, count); |
| 308 } else if (original_url == source_host_) { | 322 } else if (original_url == source_host_) { |
| 309 same_site_preconnected_ = std::max(same_site_preconnected_, count); | 323 same_site_preconnected_ = std::max(same_site_preconnected_, count); |
| 310 } else { | 324 } else if (strict_) { |
| 311 ADD_FAILURE() << "Preconnected " << original_url | 325 ADD_FAILURE() << "Preconnected " << original_url |
| 312 << " when should only be preconnecting the source host: " | 326 << " when should only be preconnecting the source host: " |
| 313 << source_host_ | 327 << source_host_ |
| 314 << " or the cross site host: " << cross_site_host_; | 328 << " or the cross site host: " << cross_site_host_; |
| 315 } | 329 } |
| 316 } | 330 } |
| 317 | 331 |
| 318 void OnLearnFromNavigation(const GURL& referring_url, | 332 void OnLearnFromNavigation(const GURL& referring_url, |
| 319 const GURL& target_url) override { | 333 const GURL& target_url) override { |
| 320 base::AutoLock lock(lock_); | 334 base::AutoLock lock(lock_); |
| 321 // There are three possibilities: | 335 // There are three possibilities: |
| 322 // source => target | 336 // source => target |
| 323 // source => source | 337 // source => source |
| 324 // target => target | 338 // target => target |
| 325 if (referring_url == source_host_ && target_url == cross_site_host_) { | 339 if (referring_url == source_host_ && target_url == cross_site_host_) { |
| 326 cross_site_learned_++; | 340 cross_site_learned_++; |
| 327 } else if (referring_url == source_host_ && target_url == source_host_) { | 341 } else if (referring_url == source_host_ && target_url == source_host_) { |
| 328 // Same site learned. Branch retained for clarity. | 342 // Same site learned. Branch retained for clarity. |
| 329 } else if (!(referring_url == cross_site_host_ && | 343 } else if (strict_ && |
| 344 !(referring_url == cross_site_host_ && |
| 330 target_url == cross_site_host_)) { | 345 target_url == cross_site_host_)) { |
| 331 ADD_FAILURE() << "Learned " << referring_url << " => " << target_url | 346 ADD_FAILURE() << "Learned " << referring_url << " => " << target_url |
| 332 << " when should only be learning the source host: " | 347 << " when should only be learning the source host: " |
| 333 << source_host_ | 348 << source_host_ |
| 334 << " or the cross site host: " << cross_site_host_; | 349 << " or the cross site host: " << cross_site_host_; |
| 335 } | 350 } |
| 336 } | 351 } |
| 337 | 352 |
| 338 void OnDnsLookupFinished(const GURL& url, bool found) override { | 353 void OnDnsLookupFinished(const GURL& url, bool found) override { |
| 339 base::AutoLock lock(lock_); | 354 base::AutoLock lock(lock_); |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 417 EXPECT_TRUE(HasHostBeenLookedUpLocked(url)) << "Expected to have looked up " | 432 EXPECT_TRUE(HasHostBeenLookedUpLocked(url)) << "Expected to have looked up " |
| 418 << url.spec(); | 433 << url.spec(); |
| 419 return ContainsKey(successful_dns_lookups_, url); | 434 return ContainsKey(successful_dns_lookups_, url); |
| 420 } | 435 } |
| 421 | 436 |
| 422 void set_task_runner( | 437 void set_task_runner( |
| 423 scoped_refptr<base::SingleThreadTaskRunner> task_runner) { | 438 scoped_refptr<base::SingleThreadTaskRunner> task_runner) { |
| 424 task_runner_.swap(task_runner); | 439 task_runner_.swap(task_runner); |
| 425 } | 440 } |
| 426 | 441 |
| 442 // Optionally allows the object to observe preconnects / learning from other |
| 443 // hosts. |
| 444 void SetStrict(bool strict) { |
| 445 base::AutoLock lock(lock_); |
| 446 strict_ = strict; |
| 447 } |
| 448 |
| 427 private: | 449 private: |
| 428 const GURL source_host_; | 450 const GURL source_host_; |
| 429 const GURL cross_site_host_; | 451 const GURL cross_site_host_; |
| 430 | 452 |
| 431 GURL waiting_on_dns_; | 453 GURL waiting_on_dns_; |
| 432 | 454 |
| 433 scoped_refptr<base::SingleThreadTaskRunner> task_runner_; | 455 scoped_refptr<base::SingleThreadTaskRunner> task_runner_; |
| 434 | 456 |
| 435 // Protects all following members. They are read and updated from different | 457 // Protects all following members. They are read and updated from different |
| 436 // threads. | 458 // threads. |
| 437 base::Lock lock_; | 459 base::Lock lock_; |
| 438 | 460 |
| 439 int cross_site_learned_; | 461 int cross_site_learned_; |
| 440 int cross_site_preconnected_; | 462 int cross_site_preconnected_; |
| 441 int same_site_preconnected_; | 463 int same_site_preconnected_; |
| 442 | 464 |
| 443 std::set<GURL> successful_dns_lookups_; | 465 std::set<GURL> successful_dns_lookups_; |
| 444 std::set<GURL> unsuccessful_dns_lookups_; | 466 std::set<GURL> unsuccessful_dns_lookups_; |
| 445 base::RunLoop* dns_run_loop_; | 467 base::RunLoop* dns_run_loop_; |
| 446 | 468 |
| 469 // This member can be set to optionally allow url learning other than from |
| 470 // source => source, source => target, or target => target. It will also allow |
| 471 // preconnects to other hosts. |
| 472 bool strict_; |
| 473 |
| 447 DISALLOW_COPY_AND_ASSIGN(CrossSitePredictorObserver); | 474 DISALLOW_COPY_AND_ASSIGN(CrossSitePredictorObserver); |
| 448 }; | 475 }; |
| 449 | 476 |
| 450 } // namespace | 477 } // namespace |
| 451 | 478 |
| 452 namespace chrome_browser_net { | 479 namespace chrome_browser_net { |
| 453 | 480 |
| 454 class PredictorBrowserTest : public InProcessBrowserTest { | 481 class PredictorBrowserTest : public InProcessBrowserTest { |
| 455 public: | 482 public: |
| 456 PredictorBrowserTest() | 483 PredictorBrowserTest() |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 506 base::Bind(&RedirectForPathHandler, "/", | 533 base::Bind(&RedirectForPathHandler, "/", |
| 507 cross_site_test_server()->GetURL("/title1.html"))); | 534 cross_site_test_server()->GetURL("/title1.html"))); |
| 508 | 535 |
| 509 predictor()->SetPreconnectEnabledForTest(true); | 536 predictor()->SetPreconnectEnabledForTest(true); |
| 510 InstallPredictorObserver(embedded_test_server()->base_url(), | 537 InstallPredictorObserver(embedded_test_server()->base_url(), |
| 511 cross_site_test_server()->base_url()); | 538 cross_site_test_server()->base_url()); |
| 512 observer()->set_task_runner(task_runner_); | 539 observer()->set_task_runner(task_runner_); |
| 513 StartInterceptingCrossSiteOnUI(); | 540 StartInterceptingCrossSiteOnUI(); |
| 514 } | 541 } |
| 515 | 542 |
| 516 // Intercepts all requests to the specified host and returns a response with | 543 static void StartInterceptingHostWithCreateJobCallback( |
| 517 // an empty body. Needed to prevent requests from actually going to the test | 544 const GURL& url, |
| 518 // server, to avoid any races related to socket accounting. Note, the | 545 const MatchingPortRequestInterceptor::CreateJobCallback& callback) { |
| 519 // interceptor also looks at the port, to differentiate between the | |
| 520 // two test servers. | |
| 521 static void StartInterceptingHost(const GURL& url) { | |
| 522 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 546 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 523 net::URLRequestFilter::GetInstance()->AddHostnameInterceptor( | 547 net::URLRequestFilter::GetInstance()->AddHostnameInterceptor( |
| 524 url.scheme(), url.host(), | 548 url.scheme(), url.host(), |
| 525 base::WrapUnique(new MatchingPortRequestInterceptor( | 549 base::WrapUnique(new MatchingPortRequestInterceptor( |
| 526 url.EffectiveIntPort(), base::Bind(&CreateEmptyBodyRequestJob)))); | 550 url.EffectiveIntPort(), callback))); |
| 527 } | 551 } |
| 528 | 552 |
| 529 static void StopInterceptingHost(const GURL& url) { | 553 static void StopInterceptingHost(const GURL& url) { |
| 530 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 554 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 531 net::URLRequestFilter::GetInstance()->RemoveHostnameHandler(url.scheme(), | 555 net::URLRequestFilter::GetInstance()->RemoveHostnameHandler(url.scheme(), |
| 532 url.host()); | 556 url.host()); |
| 533 } | 557 } |
| 534 | 558 |
| 559 // Intercepts all requests to the specified host and returns a response with |
| 560 // an empty body. Needed to prevent requests from actually going to the test |
| 561 // server, to avoid any races related to socket accounting. Note, the |
| 562 // interceptor also looks at the port, to differentiate between the |
| 563 // two test servers. |
| 535 void StartInterceptingCrossSiteOnUI() { | 564 void StartInterceptingCrossSiteOnUI() { |
| 536 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 565 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 537 content::BrowserThread::PostTask( | 566 content::BrowserThread::PostTask( |
| 538 content::BrowserThread::IO, FROM_HERE, | 567 content::BrowserThread::IO, FROM_HERE, |
| 539 base::Bind(&PredictorBrowserTest::StartInterceptingHost, | 568 base::Bind( |
| 540 cross_site_test_server()->base_url())); | 569 &PredictorBrowserTest::StartInterceptingHostWithCreateJobCallback, |
| 570 cross_site_test_server()->base_url(), |
| 571 base::Bind(&CreateEmptyBodyRequestJob))); |
| 541 } | 572 } |
| 542 | 573 |
| 543 void StopInterceptingCrossSiteOnUI() { | 574 void StopInterceptingCrossSiteOnUI() { |
| 544 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 575 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 545 content::BrowserThread::PostTask( | 576 content::BrowserThread::PostTask( |
| 546 content::BrowserThread::IO, FROM_HERE, | 577 content::BrowserThread::IO, FROM_HERE, |
| 547 base::Bind(&PredictorBrowserTest::StopInterceptingHost, | 578 base::Bind(&PredictorBrowserTest::StopInterceptingHost, |
| 548 cross_site_test_server()->base_url())); | 579 cross_site_test_server()->base_url())); |
| 549 } | 580 } |
| 550 | 581 |
| (...skipping 358 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 909 // embedded test servers have the same host_piece, an extra preconnect is | 940 // embedded test servers have the same host_piece, an extra preconnect is |
| 910 // issued. This results in ceil(2.33) + 1 = 4 preconnects. | 941 // issued. This results in ceil(2.33) + 1 = 4 preconnects. |
| 911 ui_test_utils::NavigateToURL(browser(), | 942 ui_test_utils::NavigateToURL(browser(), |
| 912 embedded_test_server()->GetURL("/title1.html")); | 943 embedded_test_server()->GetURL("/title1.html")); |
| 913 // Just check that predictor has initiated preconnects to the cross site test | 944 // Just check that predictor has initiated preconnects to the cross site test |
| 914 // server. It's tricky to reset the connections to the test server, and | 945 // server. It's tricky to reset the connections to the test server, and |
| 915 // sockets can be reused. | 946 // sockets can be reused. |
| 916 EXPECT_EQ(4, observer()->CrossSitePreconnected()); | 947 EXPECT_EQ(4, observer()->CrossSitePreconnected()); |
| 917 } | 948 } |
| 918 | 949 |
| 950 // 1. Navigate to A.com learning B.com |
| 951 // 2. Navigate to B.com with subresource from C.com redirecting to A.com. |
| 952 // 3. Assert that the redirect does not cause us to preconnect to B.com (via |
| 953 // A.com). |
| 954 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest, DontPredictBasedOnSubresources) { |
| 955 GURL redirector_url = GURL("http://redirector.com"); |
| 956 |
| 957 NavigateToCrossSiteHtmlUrl(1 /* num_cors */, "" /* file_suffix */); |
| 958 EXPECT_EQ(1, observer()->CrossSiteLearned()); |
| 959 EXPECT_EQ(0, observer()->CrossSitePreconnected()); |
| 960 |
| 961 EXPECT_EQ(0u, cross_site_connection_listener_->GetAcceptedSocketCount()); |
| 962 |
| 963 // Stop intercepting so that the test can actually navigate to the cross site |
| 964 // server. |
| 965 StopInterceptingCrossSiteOnUI(); |
| 966 |
| 967 // All requests with the redirector url as base url should redirect to the |
| 968 // embedded_test_server_. |
| 969 content::BrowserThread::PostTask( |
| 970 content::BrowserThread::IO, FROM_HERE, |
| 971 base::Bind( |
| 972 &PredictorBrowserTest::StartInterceptingHostWithCreateJobCallback, |
| 973 redirector_url, |
| 974 base::Bind( |
| 975 &CreateRedirectRequestJob, |
| 976 embedded_test_server()->GetURL("/predictor/empty.js").spec()))); |
| 977 |
| 978 // Reduce the strictness, because the below logic causes the predictor to |
| 979 // learn cross_site_test_server_ => redirector, as well as |
| 980 // cross_site_test_server_ => embedded_test_server_ (via referrer header). |
| 981 observer()->SetStrict(false); |
| 982 |
| 983 GURL redirect_requesting_url = |
| 984 cross_site_test_server()->GetURL(base::StringPrintf( |
| 985 "/predictor/" |
| 986 "predictor_cross_site.html?subresourceHost=%s&numCORSResources=1", |
| 987 redirector_url.spec().c_str())); |
| 988 ui_test_utils::NavigateToURL(browser(), redirect_requesting_url); |
| 989 bool result = false; |
| 990 |
| 991 int navigation_preconnects = observer()->CrossSitePreconnected(); |
| 992 EXPECT_EQ(2, navigation_preconnects); |
| 993 |
| 994 EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| 995 browser()->tab_strip_model()->GetActiveWebContents(), |
| 996 "startFetchesAndWaitForReply()", &result)); |
| 997 EXPECT_TRUE(result); |
| 998 |
| 999 // The number of preconnects should not increase. Note that the predictor |
| 1000 // would preconnect 4 sockets if it were doing so based on learning. |
| 1001 EXPECT_EQ(navigation_preconnects, observer()->CrossSitePreconnected()); |
| 1002 } |
| 1003 |
| 1004 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest, PredictBasedOnSubframeRedirect) { |
| 1005 // A test server is needed here because data url navigations with redirect |
| 1006 // interceptors don't interact well with the ResourceTiming API. |
| 1007 // TODO(csharrison): Possibly this is a bug in either net or Blink, and it |
| 1008 // might be worthwhile to investigate. |
| 1009 std::unique_ptr<net::EmbeddedTestServer> redirector = |
| 1010 base::WrapUnique(new net::EmbeddedTestServer()); |
| 1011 ASSERT_TRUE(redirector->Start()); |
| 1012 |
| 1013 NavigateToCrossSiteHtmlUrl(1 /* num_cors */, "" /* file_suffix */); |
| 1014 EXPECT_EQ(1, observer()->CrossSiteLearned()); |
| 1015 EXPECT_EQ(0u, cross_site_connection_listener_->GetAcceptedSocketCount()); |
| 1016 |
| 1017 redirector->RegisterRequestHandler( |
| 1018 base::Bind(&RedirectForPathHandler, "/", |
| 1019 embedded_test_server()->GetURL("/title1.html"))); |
| 1020 |
| 1021 // Note that the observer will see preconnects to the redirector, and the |
| 1022 // predictor will learn redirector->embedded_test_server. |
| 1023 observer()->SetStrict(false); |
| 1024 |
| 1025 NavigateToDataURLWithContent(base::StringPrintf( |
| 1026 "<iframe src='%s'></iframe>", redirector->base_url().spec().c_str())); |
| 1027 |
| 1028 EXPECT_EQ(4, observer()->CrossSitePreconnected()); |
| 1029 cross_site_connection_listener_->WaitForAcceptedConnectionsOnUI(4u); |
| 1030 } |
| 1031 |
| 919 // Expect that the predictor correctly predicts subframe navigations. | 1032 // Expect that the predictor correctly predicts subframe navigations. |
| 920 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest, SubframeCrossSitePrediction) { | 1033 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest, SubframeCrossSitePrediction) { |
| 921 ui_test_utils::NavigateToURL( | 1034 ui_test_utils::NavigateToURL( |
| 922 browser(), embedded_test_server()->GetURL( | 1035 browser(), embedded_test_server()->GetURL( |
| 923 "/predictor/predictor_cross_site_subframe_nav.html")); | 1036 "/predictor/predictor_cross_site_subframe_nav.html")); |
| 924 bool result = false; | 1037 bool result = false; |
| 925 EXPECT_TRUE(content::ExecuteScriptAndExtractBool( | 1038 EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| 926 browser()->tab_strip_model()->GetActiveWebContents(), | 1039 browser()->tab_strip_model()->GetActiveWebContents(), |
| 927 base::StringPrintf( | 1040 base::StringPrintf( |
| 928 "navigateSubframe('%s')", | 1041 "navigateSubframe('%s')", |
| (...skipping 463 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1392 // Second navigation to content with an img. | 1505 // Second navigation to content with an img. |
| 1393 std::string img_content = | 1506 std::string img_content = |
| 1394 "<img src=\"" + preconnect_url.spec() + "test.gif\">"; | 1507 "<img src=\"" + preconnect_url.spec() + "test.gif\">"; |
| 1395 NavigateToDataURLWithContent(img_content); | 1508 NavigateToDataURLWithContent(img_content); |
| 1396 connection_listener_->WaitUntilFirstConnectionRead(); | 1509 connection_listener_->WaitUntilFirstConnectionRead(); |
| 1397 EXPECT_EQ(2u, connection_listener_->GetAcceptedSocketCount()); | 1510 EXPECT_EQ(2u, connection_listener_->GetAcceptedSocketCount()); |
| 1398 EXPECT_EQ(1u, connection_listener_->GetReadSocketCount()); | 1511 EXPECT_EQ(1u, connection_listener_->GetReadSocketCount()); |
| 1399 } | 1512 } |
| 1400 | 1513 |
| 1401 } // namespace chrome_browser_net | 1514 } // namespace chrome_browser_net |
| OLD | NEW |