// Copyright 2019 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 #include "weblayer/test/weblayer_browser_test.h" #include "base/callback.h" #include "base/callback_helpers.h" #include "base/command_line.h" #include "base/files/file_path.h" #include "base/memory/raw_ptr.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/test/bind.h" #include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" #include "build/build_config.h" #include "components/variations/net/variations_http_headers.h" #include "components/variations/variations_ids_provider.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/url_loader_interceptor.h" #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/controllable_http_response.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/embedded_test_server/http_response.h" #include "third_party/blink/public/common/features.h" #include "third_party/blink/public/common/web_preferences/web_preferences.h" #include "weblayer/browser/tab_impl.h" #include "weblayer/public/browser.h" #include "weblayer/public/browser_observer.h" #include "weblayer/public/navigation.h" #include "weblayer/public/navigation_controller.h" #include "weblayer/public/navigation_observer.h" #include "weblayer/public/new_tab_delegate.h" #include "weblayer/shell/browser/shell.h" #include "weblayer/test/interstitial_utils.h" #include "weblayer/test/test_navigation_observer.h" #include "weblayer/test/weblayer_browser_test_utils.h" namespace weblayer { namespace { // NavigationObserver that allows registering a callback for various // NavigationObserver functions. class NavigationObserverImpl : public NavigationObserver { public: explicit NavigationObserverImpl(NavigationController* controller) : controller_(controller) { controller_->AddObserver(this); } ~NavigationObserverImpl() override { controller_->RemoveObserver(this); } using Callback = base::RepeatingCallback; using PageLanguageDeterminedCallback = base::RepeatingCallback; void SetStartedCallback(Callback callback) { started_callback_ = std::move(callback); } void SetRedirectedCallback(Callback callback) { redirected_callback_ = std::move(callback); } void SetFailedCallback(Callback callback) { failed_callback_ = std::move(callback); } void SetCompletedClosure(Callback callback) { completed_callback_ = std::move(callback); } void SetOnPageLanguageDeterminedCallback( PageLanguageDeterminedCallback callback) { on_page_language_determined_callback_ = std::move(callback); } // NavigationObserver: void NavigationStarted(Navigation* navigation) override { if (started_callback_) started_callback_.Run(navigation); } void NavigationRedirected(Navigation* navigation) override { if (redirected_callback_) redirected_callback_.Run(navigation); } void NavigationCompleted(Navigation* navigation) override { if (completed_callback_) completed_callback_.Run(navigation); } void NavigationFailed(Navigation* navigation) override { // As |this| may be deleted when running the callback, the callback must be // copied before running. To do otherwise results in use-after-free. auto callback = failed_callback_; if (callback) callback.Run(navigation); } void OnPageLanguageDetermined(Page* page, const std::string& language) override { if (on_page_language_determined_callback_) on_page_language_determined_callback_.Run(page, language); } private: raw_ptr controller_; Callback started_callback_; Callback redirected_callback_; Callback completed_callback_; Callback failed_callback_; PageLanguageDeterminedCallback on_page_language_determined_callback_; }; } // namespace class NavigationBrowserTest : public WebLayerBrowserTest { public: NavigationController* GetNavigationController() { return shell()->tab()->GetNavigationController(); } void SetUpOnMainThread() override { host_resolver()->AddRule("*", "127.0.0.1"); content::SetupCrossSiteRedirector(embedded_test_server()); } }; IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, NoError) { EXPECT_TRUE(embedded_test_server()->Start()); OneShotNavigationObserver observer(shell()); GetNavigationController()->Navigate( embedded_test_server()->GetURL("/simple_page.html")); observer.WaitForNavigation(); EXPECT_TRUE(observer.completed()); EXPECT_FALSE(observer.is_error_page()); EXPECT_FALSE(observer.is_download()); EXPECT_FALSE(observer.is_reload()); EXPECT_FALSE(observer.was_stop_called()); EXPECT_EQ(observer.load_error(), Navigation::kNoError); EXPECT_EQ(observer.http_status_code(), 200); EXPECT_EQ(observer.navigation_state(), NavigationState::kComplete); } IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, IsPageInitiatedTrueForWindowHistoryBack) { EXPECT_TRUE(embedded_test_server()->Start()); std::unique_ptr observer = std::make_unique(shell()); GetNavigationController()->Navigate( embedded_test_server()->GetURL("a.com", "/simple_page.html")); observer->WaitForNavigation(); ASSERT_TRUE(observer->completed()); EXPECT_FALSE(observer->is_page_initiated()); observer = std::make_unique(shell()); GetNavigationController()->Navigate( embedded_test_server()->GetURL("b.com", "/simple_page.html")); observer->WaitForNavigation(); ASSERT_TRUE(observer->completed()); EXPECT_FALSE(observer->is_page_initiated()); observer = std::make_unique(shell()); shell()->tab()->ExecuteScript( u"window.history.back();", false, base::BindLambdaForTesting( [&](base::Value value) { LOG(ERROR) << "executescript result"; })); observer->WaitForNavigation(); ASSERT_TRUE(observer->completed()); EXPECT_TRUE(observer->is_page_initiated()); } // Http client error when the server returns a non-empty response. IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, HttpClientError) { EXPECT_TRUE(embedded_test_server()->Start()); OneShotNavigationObserver observer(shell()); GetNavigationController()->Navigate( embedded_test_server()->GetURL("/non_empty404.html")); observer.WaitForNavigation(); EXPECT_TRUE(observer.completed()); EXPECT_FALSE(observer.is_error_page()); EXPECT_EQ(observer.load_error(), Navigation::kHttpClientError); EXPECT_EQ(observer.http_status_code(), 404); EXPECT_EQ(observer.navigation_state(), NavigationState::kComplete); } // Http client error when the server returns an empty response. IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, HttpClientErrorEmptyResponse) { EXPECT_TRUE(embedded_test_server()->Start()); OneShotNavigationObserver observer(shell()); GetNavigationController()->Navigate( embedded_test_server()->GetURL("/empty404.html")); observer.WaitForNavigation(); EXPECT_FALSE(observer.completed()); EXPECT_TRUE(observer.is_error_page()); EXPECT_EQ(observer.load_error(), Navigation::kHttpClientError); EXPECT_EQ(observer.http_status_code(), 404); EXPECT_EQ(observer.navigation_state(), NavigationState::kFailed); } IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, HttpServerError) { EXPECT_TRUE(embedded_test_server()->Start()); OneShotNavigationObserver observer(shell()); GetNavigationController()->Navigate( embedded_test_server()->GetURL("/echo?status=500")); observer.WaitForNavigation(); EXPECT_TRUE(observer.completed()); EXPECT_FALSE(observer.is_error_page()); EXPECT_EQ(observer.load_error(), Navigation::kHttpServerError); EXPECT_EQ(observer.http_status_code(), 500); EXPECT_EQ(observer.navigation_state(), NavigationState::kComplete); } IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, SSLError) { net::EmbeddedTestServer https_server_mismatched( net::EmbeddedTestServer::TYPE_HTTPS); https_server_mismatched.SetSSLConfig( net::EmbeddedTestServer::CERT_MISMATCHED_NAME); https_server_mismatched.AddDefaultHandlers( base::FilePath(FILE_PATH_LITERAL("weblayer/test/data"))); ASSERT_TRUE(https_server_mismatched.Start()); OneShotNavigationObserver observer(shell()); GetNavigationController()->Navigate( https_server_mismatched.GetURL("/simple_page.html")); observer.WaitForNavigation(); EXPECT_FALSE(observer.completed()); EXPECT_TRUE(observer.is_error_page()); EXPECT_EQ(observer.load_error(), Navigation::kSSLError); EXPECT_EQ(observer.navigation_state(), NavigationState::kFailed); } IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, HttpConnectivityError) { GURL url("http://doesntexist.com/foo"); auto interceptor = content::URLLoaderInterceptor::SetupRequestFailForURL( url, net::ERR_NAME_NOT_RESOLVED); OneShotNavigationObserver observer(shell()); GetNavigationController()->Navigate(url); observer.WaitForNavigation(); EXPECT_FALSE(observer.completed()); EXPECT_TRUE(observer.is_error_page()); EXPECT_EQ(observer.load_error(), Navigation::kConnectivityError); EXPECT_EQ(observer.navigation_state(), NavigationState::kFailed); } #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) // https://crbug.com/1296643 #define MAYBE_Download DISABLED_Download #else #define MAYBE_Download Download #endif IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, MAYBE_Download) { EXPECT_TRUE(embedded_test_server()->Start()); GURL url(embedded_test_server()->GetURL("/content-disposition.html")); OneShotNavigationObserver observer(shell()); GetNavigationController()->Navigate(url); observer.WaitForNavigation(); EXPECT_FALSE(observer.completed()); EXPECT_FALSE(observer.is_error_page()); EXPECT_TRUE(observer.is_download()); EXPECT_FALSE(observer.was_stop_called()); EXPECT_EQ(observer.load_error(), Navigation::kOtherError); EXPECT_EQ(observer.navigation_state(), NavigationState::kFailed); } IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, StopInOnStart) { ASSERT_TRUE(embedded_test_server()->Start()); base::RunLoop run_loop; NavigationObserverImpl observer(GetNavigationController()); observer.SetStartedCallback(base::BindLambdaForTesting( [&](Navigation*) { GetNavigationController()->Stop(); })); observer.SetFailedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { ASSERT_TRUE(navigation->WasStopCalled()); run_loop.Quit(); })); GetNavigationController()->Navigate( embedded_test_server()->GetURL("/simple_page.html")); run_loop.Run(); } IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, DestroyTabInNavigation) { ASSERT_TRUE(embedded_test_server()->Start()); Tab* new_tab = shell()->browser()->CreateTab(); base::RunLoop run_loop; std::unique_ptr observer = std::make_unique( new_tab->GetNavigationController()); observer->SetFailedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { observer.reset(); shell()->browser()->DestroyTab(new_tab); // Destroying the tab posts a task to delete the WebContents, which must // be run before the test shuts down lest it access deleted state. base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, run_loop.QuitClosure()); })); new_tab->GetNavigationController()->Navigate( embedded_test_server()->GetURL("/simple_pageX.html")); run_loop.Run(); } IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, StopInOnRedirect) { ASSERT_TRUE(embedded_test_server()->Start()); base::RunLoop run_loop; NavigationObserverImpl observer(GetNavigationController()); observer.SetRedirectedCallback(base::BindLambdaForTesting( [&](Navigation*) { GetNavigationController()->Stop(); })); observer.SetFailedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { ASSERT_TRUE(navigation->WasStopCalled()); run_loop.Quit(); })); const GURL original_url = embedded_test_server()->GetURL("/simple_page.html"); GetNavigationController()->Navigate(embedded_test_server()->GetURL( "/server-redirect?" + original_url.spec())); run_loop.Run(); } IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, NavigateFromRendererInitiatedNavigation) { ASSERT_TRUE(embedded_test_server()->Start()); NavigationController* controller = shell()->tab()->GetNavigationController(); const GURL final_url = embedded_test_server()->GetURL("/simple_page2.html"); int failed_count = 0; int completed_count = 0; NavigationObserverImpl observer(controller); base::RunLoop run_loop; observer.SetFailedCallback( base::BindLambdaForTesting([&](Navigation*) { failed_count++; })); observer.SetCompletedClosure( base::BindLambdaForTesting([&](Navigation* navigation) { completed_count++; if (navigation->GetURL().path() == "/simple_page2.html") run_loop.Quit(); })); observer.SetStartedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { if (navigation->GetURL().path() == "/simple_page.html") controller->Navigate(final_url); })); controller->Navigate(embedded_test_server()->GetURL("/simple_page4.html")); run_loop.Run(); EXPECT_EQ(1, failed_count); EXPECT_EQ(2, completed_count); ASSERT_EQ(2, controller->GetNavigationListSize()); EXPECT_EQ(final_url, controller->GetNavigationEntryDisplayURL(1)); } class BrowserObserverImpl : public BrowserObserver { public: explicit BrowserObserverImpl(Browser* browser) : browser_(browser) { browser->AddObserver(this); } ~BrowserObserverImpl() override { browser_->RemoveObserver(this); } void SetNewTabCallback(base::RepeatingCallback callback) { new_tab_callback_ = callback; } // BrowserObserver: void OnTabAdded(Tab* tab) override { new_tab_callback_.Run(tab); } private: base::RepeatingCallback new_tab_callback_; raw_ptr browser_; }; class NewTabDelegateImpl : public NewTabDelegate { public: // NewTabDelegate: void OnNewTab(Tab* new_tab, NewTabType type) override {} }; // Ensures calling Navigate() from within NavigationStarted() for a popup does // not crash. IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, NavigateFromNewWindow) { ASSERT_TRUE(embedded_test_server()->Start()); NavigateAndWaitForCompletion( embedded_test_server()->GetURL("/simple_page2.html"), shell()); NewTabDelegate* old_new_tab_delegate = static_cast(shell()->tab())->new_tab_delegate(); NewTabDelegateImpl new_tab_delegate; shell()->tab()->SetNewTabDelegate(&new_tab_delegate); BrowserObserverImpl browser_observer(shell()->tab()->GetBrowser()); std::unique_ptr popup_navigation_observer; base::RunLoop run_loop; Tab* popup_tab = nullptr; auto popup_started_navigation = [&](Navigation* navigation) { if (navigation->GetURL().path() == "/simple_page.html") { popup_tab->GetNavigationController()->Navigate( embedded_test_server()->GetURL("/simple_page3.html")); } else if (navigation->GetURL().path() == "/simple_page3.html") { run_loop.Quit(); } }; browser_observer.SetNewTabCallback(base::BindLambdaForTesting([&](Tab* tab) { popup_tab = tab; popup_navigation_observer = std::make_unique( tab->GetNavigationController()); popup_navigation_observer->SetStartedCallback( base::BindLambdaForTesting(popup_started_navigation)); })); // 'noopener' is key to triggering the problematic case. const std::string window_open = base::StringPrintf( "window.open('%s', '', 'noopener')", embedded_test_server()->GetURL("/simple_page.html").spec().c_str()); ExecuteScriptWithUserGesture(shell()->tab(), window_open); run_loop.Run(); // Restore the old delegate to make sure it is cleaned up on Android. shell()->tab()->SetNewTabDelegate(old_new_tab_delegate); } // Verifies calling Navigate() from NavigationRedirected() works. IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, NavigateFromRedirect) { net::test_server::ControllableHttpResponse response_1(embedded_test_server(), "", true); net::test_server::ControllableHttpResponse response_2(embedded_test_server(), "", true); ASSERT_TRUE(embedded_test_server()->Start()); NavigationObserverImpl observer(GetNavigationController()); bool got_redirect = false; const GURL url_to_load_on_redirect = embedded_test_server()->GetURL("/url_to_load_on_redirect.html"); observer.SetRedirectedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { shell()->LoadURL(url_to_load_on_redirect); got_redirect = true; })); shell()->LoadURL(embedded_test_server()->GetURL("/initial_url.html")); response_1.WaitForRequest(); response_1.Send( "HTTP/1.1 302 Moved Temporarily\r\nLocation: /redirect_dest_url\r\n\r\n"); response_1.Done(); response_2.WaitForRequest(); response_2.Done(); EXPECT_EQ(url_to_load_on_redirect, response_2.http_request()->GetURL()); EXPECT_TRUE(got_redirect); } IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, SetRequestHeader) { net::test_server::ControllableHttpResponse response_1(embedded_test_server(), "", true); net::test_server::ControllableHttpResponse response_2(embedded_test_server(), "", true); ASSERT_TRUE(embedded_test_server()->Start()); const std::string header_name = "header"; const std::string header_value = "value"; NavigationObserverImpl observer(GetNavigationController()); observer.SetStartedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { navigation->SetRequestHeader(header_name, header_value); })); shell()->LoadURL(embedded_test_server()->GetURL("/simple_page.html")); response_1.WaitForRequest(); // Header should be present in initial request. EXPECT_EQ(header_value, response_1.http_request()->headers.at(header_name)); response_1.Send( "HTTP/1.1 302 Moved Temporarily\r\nLocation: /new_doc\r\n\r\n"); response_1.Done(); // Header should carry through to redirect. response_2.WaitForRequest(); EXPECT_EQ(header_value, response_2.http_request()->headers.at(header_name)); } // Verifies setting the 'referer' via SetRequestHeader() works as expected. IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, SetRequestHeaderWithReferer) { net::test_server::ControllableHttpResponse response(embedded_test_server(), "", true); ASSERT_TRUE(embedded_test_server()->Start()); const std::string header_name = "Referer"; const std::string header_value = "http://request.com"; NavigationObserverImpl observer(GetNavigationController()); observer.SetStartedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { navigation->SetRequestHeader(header_name, header_value); })); shell()->LoadURL(embedded_test_server()->GetURL("/simple_page.html")); response.WaitForRequest(); // Verify 'referer' matches expected value. EXPECT_EQ(GURL(header_value), GURL(response.http_request()->headers.at(header_name))); } // Like above but checks that referer isn't sent when it's https and the target // url is http. IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, SetRequestHeaderWithRefererDowngrade) { net::test_server::ControllableHttpResponse response(embedded_test_server(), "", true); ASSERT_TRUE(embedded_test_server()->Start()); const std::string header_name = "Referer"; const std::string header_value = "https://request.com"; NavigationObserverImpl observer(GetNavigationController()); observer.SetStartedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { navigation->SetRequestHeader(header_name, header_value); })); shell()->LoadURL(embedded_test_server()->GetURL("/simple_page.html")); response.WaitForRequest(); EXPECT_EQ(0u, response.http_request()->headers.count(header_name)); } IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, SetRequestHeaderInRedirect) { net::test_server::ControllableHttpResponse response_1(embedded_test_server(), "", true); net::test_server::ControllableHttpResponse response_2(embedded_test_server(), "", true); ASSERT_TRUE(embedded_test_server()->Start()); const std::string header_name = "header"; const std::string header_value = "value"; NavigationObserverImpl observer(GetNavigationController()); observer.SetRedirectedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { navigation->SetRequestHeader(header_name, header_value); })); shell()->LoadURL(embedded_test_server()->GetURL("/simple_page.html")); response_1.WaitForRequest(); // Header should not be present in initial request. EXPECT_FALSE(base::Contains(response_1.http_request()->headers, header_name)); response_1.Send( "HTTP/1.1 302 Moved Temporarily\r\nLocation: /new_doc\r\n\r\n"); response_1.Done(); response_2.WaitForRequest(); // Header should be in redirect. ASSERT_TRUE(base::Contains(response_2.http_request()->headers, header_name)); EXPECT_EQ(header_value, response_2.http_request()->headers.at(header_name)); } class NavigationBrowserTestUserAgentOverrideSubstring : public NavigationBrowserTest { public: void SetUp() override { scoped_feature_list_.InitWithFeatures( {blink::features::kUserAgentOverrideExperiment, blink::features::kUACHOverrideBlank}, {}); NavigationBrowserTest::SetUp(); } private: base::test::ScopedFeatureList scoped_feature_list_; }; IN_PROC_BROWSER_TEST_F(NavigationBrowserTestUserAgentOverrideSubstring, PageSeesUserAgentString) { net::test_server::EmbeddedTestServer https_server( net::test_server::EmbeddedTestServer::TYPE_HTTPS); https_server.AddDefaultHandlers( base::FilePath(FILE_PATH_LITERAL("weblayer/test/data"))); ASSERT_TRUE(https_server.Start()); const std::string custom_ua = "custom"; NavigationObserverImpl observer(GetNavigationController()); observer.SetStartedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { navigation->SetUserAgentString(custom_ua); })); base::HistogramTester histogram; OneShotNavigationObserver navigation_observer(shell()); shell()->LoadURL(https_server.GetURL("/simple_page.html")); navigation_observer.WaitForNavigation(); histogram.ExpectBucketCount( blink::UserAgentOverride::kUserAgentOverrideHistogram, blink::UserAgentOverride::UserAgentOverriden, 1); base::RunLoop run_loop; shell()->tab()->ExecuteScript( u"navigator.userAgent;", false, base::BindLambdaForTesting([&](base::Value value) { ASSERT_TRUE(value.is_string()); EXPECT_EQ(custom_ua, value.GetString()); run_loop.Quit(); })); run_loop.Run(); // Ensure that userAgentData is blank when custom user agent is set. base::RunLoop run_loop2; shell()->tab()->ExecuteScript( u"navigator.userAgentData.platform;", false, base::BindLambdaForTesting([&](base::Value value) { ASSERT_TRUE(value.is_string()); EXPECT_EQ("", value.GetString()); run_loop2.Quit(); })); run_loop2.Run(); } IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, Reload) { ASSERT_TRUE(embedded_test_server()->Start()); OneShotNavigationObserver observer(shell()); GetNavigationController()->Navigate( embedded_test_server()->GetURL("/simple_page.html")); observer.WaitForNavigation(); OneShotNavigationObserver observer2(shell()); shell()->tab()->ExecuteScript(u"location.reload();", false, base::DoNothing()); observer2.WaitForNavigation(); EXPECT_TRUE(observer2.completed()); EXPECT_TRUE(observer2.is_reload()); } IN_PROC_BROWSER_TEST_F(NavigationBrowserTestUserAgentOverrideSubstring, SetUserAgentString) { std::unique_ptr https_server = std::make_unique( net::test_server::EmbeddedTestServer::TYPE_HTTPS); https_server->AddDefaultHandlers( base::FilePath(FILE_PATH_LITERAL("weblayer/test/data"))); net::test_server::ControllableHttpResponse response_1(https_server.get(), "", true); net::test_server::ControllableHttpResponse response_2(https_server.get(), "", true); ASSERT_TRUE(https_server->Start()); const std::string custom_ua = "CUSTOM"; NavigationObserverImpl observer(GetNavigationController()); observer.SetStartedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { navigation->SetUserAgentString(custom_ua); })); base::HistogramTester histogram; shell()->LoadURL(https_server->GetURL("/simple_page.html")); response_1.WaitForRequest(); // |custom_ua| should be present in initial request. ASSERT_TRUE(base::Contains(response_1.http_request()->headers, net::HttpRequestHeaders::kUserAgent)); const std::string new_header = response_1.http_request()->headers.at( net::HttpRequestHeaders::kUserAgent); EXPECT_EQ(custom_ua, new_header); ASSERT_TRUE(base::Contains(response_1.http_request()->headers, "sec-ch-ua")); const std::string new_ch_header = response_1.http_request()->headers.at("Sec-CH-UA"); EXPECT_EQ("", new_ch_header); content::FetchHistogramsFromChildProcesses(); histogram.ExpectBucketCount( blink::UserAgentOverride::kUserAgentOverrideHistogram, blink::UserAgentOverride::UserAgentOverriden, 1); // Header should carry through to redirect. response_1.Send( "HTTP/1.1 302 Moved Temporarily\r\nLocation: /new_doc\r\n\r\n"); response_1.Done(); response_2.WaitForRequest(); EXPECT_EQ(custom_ua, response_2.http_request()->headers.at( net::HttpRequestHeaders::kUserAgent)); EXPECT_EQ("", response_2.http_request()->headers.at("Sec-CH-UA")); } #if BUILDFLAG(IS_ANDROID) IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, SetUserAgentStringDoesntChangeViewportMetaTag) { ASSERT_TRUE(embedded_test_server()->Start()); NavigationObserverImpl observer(GetNavigationController()); const std::string custom_ua = "custom-ua"; observer.SetStartedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { navigation->SetUserAgentString(custom_ua); })); OneShotNavigationObserver load_observer(shell()); shell()->LoadURL(embedded_test_server()->GetURL("/simple_page.html")); load_observer.WaitForNavigation(); // Just because we set a custom user agent doesn't mean we should ignore // viewport meta tags. auto* tab = static_cast(shell()->tab()); auto* web_contents = tab->web_contents(); ASSERT_TRUE(web_contents->GetOrCreateWebPreferences().viewport_meta_enabled); } IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, RequestDesktopSiteChangesViewportMetaTag) { ASSERT_TRUE(embedded_test_server()->Start()); OneShotNavigationObserver load_observer(shell()); shell()->LoadURL(embedded_test_server()->GetURL("/simple_page.html")); load_observer.WaitForNavigation(); auto* tab = static_cast(shell()->tab()); OneShotNavigationObserver load_observer2(shell()); tab->SetDesktopUserAgentEnabled(nullptr, true); load_observer2.WaitForNavigation(); auto* web_contents = tab->web_contents(); ASSERT_FALSE(web_contents->GetOrCreateWebPreferences().viewport_meta_enabled); } #endif // Verifies changing the user agent twice in a row works. IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, UserAgentDoesntCarryThrough1) { net::test_server::ControllableHttpResponse response_1(embedded_test_server(), "", true); net::test_server::ControllableHttpResponse response_2(embedded_test_server(), "", true); ASSERT_TRUE(embedded_test_server()->Start()); const std::string custom_ua1 = "my ua1"; const std::string custom_ua2 = "my ua2"; NavigationObserverImpl observer(GetNavigationController()); observer.SetStartedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { navigation->SetUserAgentString(custom_ua1); })); shell()->LoadURL(embedded_test_server()->GetURL("/simple_page.html")); response_1.WaitForRequest(); EXPECT_EQ(custom_ua1, response_1.http_request()->headers.at( net::HttpRequestHeaders::kUserAgent)); // Before the request is done, start another navigation. observer.SetStartedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { navigation->SetUserAgentString(custom_ua2); })); shell()->LoadURL(embedded_test_server()->GetURL("/simple_page2.html")); response_2.WaitForRequest(); EXPECT_EQ(custom_ua2, response_2.http_request()->headers.at( net::HttpRequestHeaders::kUserAgent)); } // Verifies changing the user agent doesn't bleed through to next navigation. IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, UserAgentDoesntCarryThrough2) { net::test_server::ControllableHttpResponse response_1(embedded_test_server(), "", true); net::test_server::ControllableHttpResponse response_2(embedded_test_server(), "", true); ASSERT_TRUE(embedded_test_server()->Start()); const std::string custom_ua = "my ua1"; NavigationObserverImpl observer(GetNavigationController()); observer.SetStartedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { navigation->SetUserAgentString(custom_ua); })); shell()->LoadURL(embedded_test_server()->GetURL("/simple_page.html")); response_1.WaitForRequest(); EXPECT_EQ(custom_ua, response_1.http_request()->headers.at( net::HttpRequestHeaders::kUserAgent)); // Before the request is done, start another navigation. observer.SetStartedCallback(base::DoNothing()); shell()->LoadURL(embedded_test_server()->GetURL("/simple_page2.html")); response_2.WaitForRequest(); EXPECT_NE(custom_ua, response_2.http_request()->headers.at( net::HttpRequestHeaders::kUserAgent)); EXPECT_FALSE(response_2.http_request() ->headers.at(net::HttpRequestHeaders::kUserAgent) .empty()); } // Verifies changing the user-agent applies to child resources, such as an // . IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, UserAgentAppliesToChildResources) { net::test_server::ControllableHttpResponse response_1(embedded_test_server(), "", true); net::test_server::ControllableHttpResponse response_2(embedded_test_server(), "", true); ASSERT_TRUE(embedded_test_server()->Start()); const std::string custom_ua = "custom-ua"; NavigationObserverImpl observer(GetNavigationController()); observer.SetStartedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { navigation->SetUserAgentString(custom_ua); })); shell()->LoadURL(embedded_test_server()->GetURL("/foo.html")); response_1.WaitForRequest(); response_1.Send(net::HTTP_OK, "text/html", ""); response_1.Done(); EXPECT_EQ(custom_ua, response_1.http_request()->headers.at( net::HttpRequestHeaders::kUserAgent)); observer.SetStartedCallback(base::DoNothing()); response_2.WaitForRequest(); EXPECT_EQ(custom_ua, response_2.http_request()->headers.at( net::HttpRequestHeaders::kUserAgent)); } IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, SetUserAgentStringRendererInitiated) { net::test_server::ControllableHttpResponse response_1(embedded_test_server(), "", true); net::test_server::ControllableHttpResponse response_2(embedded_test_server(), "", true); ASSERT_TRUE(embedded_test_server()->Start()); OneShotNavigationObserver load_observer(shell()); NavigationObserverImpl observer(GetNavigationController()); shell()->LoadURL(embedded_test_server()->GetURL("/simple_page.html")); response_1.WaitForRequest(); response_1.Send(net::HTTP_OK, "text/html", ""); response_1.Done(); load_observer.WaitForNavigation(); const std::string custom_ua = "custom-ua"; observer.SetStartedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { navigation->SetUserAgentString(custom_ua); })); const GURL target_url = embedded_test_server()->GetURL("/foo.html"); shell()->tab()->ExecuteScript( u"location.href='" + base::ASCIIToUTF16(target_url.spec()) + u"';", false, base::DoNothing()); response_2.WaitForRequest(); // |custom_ua| should be present in the renderer initiated navigation. ASSERT_TRUE(base::Contains(response_2.http_request()->headers, net::HttpRequestHeaders::kUserAgent)); const std::string new_ua = response_2.http_request()->headers.at( net::HttpRequestHeaders::kUserAgent); EXPECT_EQ(custom_ua, new_ua); } IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, AutoPlayDefault) { ASSERT_TRUE(embedded_test_server()->Start()); GURL url(embedded_test_server()->GetURL("/autoplay.html")); auto* tab = static_cast(shell()->tab()); NavigateAndWaitForCompletion(url, tab); auto* web_contents = tab->web_contents(); bool playing = false; // There's no notification to watch that would signal video wasn't autoplayed, // so instead check once through javascript. EXPECT_TRUE(content::ExecuteScriptAndExtractBool( web_contents, "window.domAutomationController.send(!document.getElementById('vid')." "paused)", &playing)); ASSERT_FALSE(playing); } namespace { class WaitForMediaPlaying : public content::WebContentsObserver { public: explicit WaitForMediaPlaying(content::WebContents* web_contents) : WebContentsObserver(web_contents) {} WaitForMediaPlaying(const WaitForMediaPlaying&) = delete; WaitForMediaPlaying& operator=(const WaitForMediaPlaying&) = delete; // WebContentsObserver override. void MediaStartedPlaying(const MediaPlayerInfo& info, const content::MediaPlayerId&) final { run_loop_.Quit(); CHECK(info.has_audio); CHECK(info.has_video); } void Wait() { run_loop_.Run(); } private: base::RunLoop run_loop_; }; } // namespace IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, AutoPlayEnabled) { ASSERT_TRUE(embedded_test_server()->Start()); GURL url(embedded_test_server()->GetURL("/autoplay.html")); NavigationController::NavigateParams params; params.enable_auto_play = true; GetNavigationController()->Navigate(url, params); auto* tab = static_cast(shell()->tab()); WaitForMediaPlaying wait_for_media(tab->web_contents()); wait_for_media.Wait(); } class NavigationBrowserTest2 : public NavigationBrowserTest { public: void SetUp() override { // HTTPS server only serves a valid cert for localhost, so this is needed to // load pages from "www.google.com" without an interstitial. base::CommandLine::ForCurrentProcess()->AppendSwitch( "ignore-certificate-errors"); NavigationBrowserTest::SetUp(); } void SetUpOnMainThread() override { NavigationBrowserTest::SetUpOnMainThread(); https_server_ = std::make_unique( net::EmbeddedTestServer::TYPE_HTTPS); // The test makes requests to google.com which we want to redirect to the // test server. host_resolver()->AddRule("*", "127.0.0.1"); // Forces variations code to set the header. auto* variations_provider = variations::VariationsIdsProvider::GetInstance(); variations_provider->ForceVariationIds({"12", "456", "t789"}, ""); } net::EmbeddedTestServer* https_server() { return https_server_.get(); } private: std::unique_ptr https_server_; }; // This test verifies the embedder can replace the X-Client-Data header that // is also set by //components/variations. IN_PROC_BROWSER_TEST_F(NavigationBrowserTest2, ReplaceXClientDataHeader) { std::unique_ptr run_loop = std::make_unique(); std::string last_header_value; auto main_task_runner = base::SequencedTaskRunnerHandle::Get(); https_server()->RegisterRequestHandler(base::BindLambdaForTesting( [&, main_task_runner](const net::test_server::HttpRequest& request) -> std::unique_ptr { auto iter = request.headers.find(variations::kClientDataHeader); if (iter != request.headers.end()) { main_task_runner->PostTask( FROM_HERE, base::BindOnce(base::BindLambdaForTesting( [&](const std::string& value) { last_header_value = value; run_loop->Quit(); }), iter->second)); } return std::make_unique(); })); ASSERT_TRUE(https_server()->Start()); // Verify the header is set by default. const GURL url = https_server()->GetURL("www.google.com", "/"); shell()->LoadURL(url); run_loop->Run(); EXPECT_FALSE(last_header_value.empty()); // Repeat, but clobber the header when navigating. const std::string header_value = "value"; EXPECT_NE(last_header_value, header_value); last_header_value.clear(); run_loop = std::make_unique(); NavigationObserverImpl observer(GetNavigationController()); observer.SetStartedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { navigation->SetRequestHeader(variations::kClientDataHeader, header_value); })); shell()->LoadURL(https_server()->GetURL("www.google.com", "/foo")); run_loop->Run(); EXPECT_EQ(header_value, last_header_value); } IN_PROC_BROWSER_TEST_F(NavigationBrowserTest2, SetXClientDataHeaderCarriesThroughToRedirect) { std::unique_ptr run_loop = std::make_unique(); std::string last_header_value; bool should_redirect = true; auto main_task_runner = base::SequencedTaskRunnerHandle::Get(); https_server()->RegisterRequestHandler(base::BindLambdaForTesting( [&, main_task_runner](const net::test_server::HttpRequest& request) -> std::unique_ptr { auto response = std::make_unique(); if (should_redirect) { should_redirect = false; response->set_code(net::HTTP_MOVED_PERMANENTLY); response->AddCustomHeader( "Location", https_server()->GetURL("www.google.com", "/redirect").spec()); } else { auto iter = request.headers.find(variations::kClientDataHeader); main_task_runner->PostTask( FROM_HERE, base::BindOnce(base::BindLambdaForTesting( [&](const std::string& value) { last_header_value = value; run_loop->Quit(); }), iter->second)); } return response; })); ASSERT_TRUE(https_server()->Start()); const std::string header_value = "value"; run_loop = std::make_unique(); NavigationObserverImpl observer(GetNavigationController()); observer.SetStartedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { navigation->SetRequestHeader(variations::kClientDataHeader, header_value); })); shell()->LoadURL(https_server()->GetURL("www.google.com", "/foo")); run_loop->Run(); EXPECT_EQ(header_value, last_header_value); } IN_PROC_BROWSER_TEST_F(NavigationBrowserTest2, SetXClientDataHeaderInRedirect) { std::unique_ptr run_loop = std::make_unique(); std::string last_header_value; bool should_redirect = true; auto main_task_runner = base::SequencedTaskRunnerHandle::Get(); https_server()->RegisterRequestHandler(base::BindLambdaForTesting( [&, main_task_runner](const net::test_server::HttpRequest& request) -> std::unique_ptr { auto response = std::make_unique(); if (should_redirect) { should_redirect = false; response->set_code(net::HTTP_MOVED_PERMANENTLY); response->AddCustomHeader( "Location", https_server()->GetURL("www.google.com", "/redirect").spec()); } else { auto iter = request.headers.find(variations::kClientDataHeader); main_task_runner->PostTask( FROM_HERE, base::BindOnce(base::BindLambdaForTesting( [&](const std::string& value) { last_header_value = value; run_loop->Quit(); }), iter->second)); } return response; })); ASSERT_TRUE(https_server()->Start()); const std::string header_value = "value"; NavigationObserverImpl observer(GetNavigationController()); observer.SetRedirectedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { navigation->SetRequestHeader(variations::kClientDataHeader, header_value); })); shell()->LoadURL(https_server()->GetURL("www.google.com", "/foo")); run_loop->Run(); EXPECT_EQ(header_value, last_header_value); } #if BUILDFLAG(IS_ANDROID) // Verifies setting the 'referer' to an android-app url works. IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, AndroidAppReferer) { net::test_server::ControllableHttpResponse response(embedded_test_server(), "", true); ASSERT_TRUE(embedded_test_server()->Start()); const std::string header_name = "Referer"; const std::string header_value = "android-app://google.com/"; NavigationObserverImpl observer(GetNavigationController()); observer.SetStartedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { navigation->SetRequestHeader(header_name, header_value); })); shell()->LoadURL(embedded_test_server()->GetURL("/simple_page.html")); response.WaitForRequest(); // Verify 'referer' matches expected value. EXPECT_EQ(header_value, response.http_request()->headers.at(header_name)); } #endif IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, OnPageLanguageDeterminedCallback) { ASSERT_TRUE(embedded_test_server()->Start()); NavigationController* controller = shell()->tab()->GetNavigationController(); NavigationObserverImpl observer(controller); Page* committed_page = nullptr; Page* page_with_language_determined = nullptr; std::string determined_language = ""; base::RunLoop navigation_run_loop1; base::RunLoop page_language_determination_run_loop1; base::RunLoop* navigation_run_loop = &navigation_run_loop1; base::RunLoop* page_language_determination_run_loop = &page_language_determination_run_loop1; observer.SetCompletedClosure( base::BindLambdaForTesting([&](Navigation* navigation) { committed_page = navigation->GetPage(); navigation_run_loop->Quit(); })); observer.SetOnPageLanguageDeterminedCallback( base::BindLambdaForTesting([&](Page* page, std::string language) { page_with_language_determined = page; determined_language = language; page_language_determination_run_loop->Quit(); })); // Navigate to a page in English. controller->Navigate(embedded_test_server()->GetURL("/english_page.html")); navigation_run_loop1.Run(); EXPECT_TRUE(committed_page); // Verify that the language determined event fires as expected. page_language_determination_run_loop1.Run(); EXPECT_EQ(committed_page, page_with_language_determined); EXPECT_EQ("en", determined_language); // Now navigate to a page in French. committed_page = nullptr; page_with_language_determined = nullptr; base::RunLoop navigation_run_loop2; base::RunLoop page_language_determination_run_loop2; navigation_run_loop = &navigation_run_loop2; page_language_determination_run_loop = &page_language_determination_run_loop2; controller->Navigate(embedded_test_server()->GetURL("/french_page.html")); navigation_run_loop2.Run(); EXPECT_TRUE(committed_page); // Verify that the language determined event fires as expected. page_language_determination_run_loop2.Run(); EXPECT_EQ(committed_page, page_with_language_determined); EXPECT_EQ("fr", determined_language); } // Verifies that closing a tab when a navigation is waiting for a response // causes the navigation to be marked as failed to the embedder. IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, CloseTabWithNavigationWaitingForResponse) { net::test_server::ControllableHttpResponse response(embedded_test_server(), "", true); ASSERT_TRUE(embedded_test_server()->Start()); GURL url = embedded_test_server()->GetURL("/initial_url.html"); base::RunLoop run_loop; Navigation* ongoing_navigation = nullptr; auto observer = std::make_unique(GetNavigationController()); observer->SetStartedCallback(base::BindLambdaForTesting( [&](Navigation* navigation) { ongoing_navigation = navigation; })); observer->SetFailedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { EXPECT_EQ(url, navigation->GetURL()); EXPECT_EQ(NavigationState::kFailed, navigation->GetState()); run_loop.Quit(); // The NavigationControllerImpl that |observer| is observing will // be destroyed before control returns to the test, so destroy // |observer| now to avoid UaF. observer.reset(); })); shell()->LoadURL(url); response.WaitForRequest(); EXPECT_EQ(NavigationState::kWaitingResponse, ongoing_navigation->GetState()); shell()->browser()->DestroyTab(shell()->tab()); run_loop.Run(); } // Verifies that closing a tab when a navigation is in the middle of receiving a // response causes the navigation to be marked as failed to the embedder. IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, CloseTabWithNavigationReceivingBytes) { net::test_server::ControllableHttpResponse response(embedded_test_server(), "", true); ASSERT_TRUE(embedded_test_server()->Start()); GURL url = embedded_test_server()->GetURL("/initial_url.html"); base::RunLoop run_loop; Navigation* ongoing_navigation = nullptr; auto observer = std::make_unique(GetNavigationController()); observer->SetStartedCallback(base::BindLambdaForTesting( [&](Navigation* navigation) { ongoing_navigation = navigation; })); observer->SetFailedCallback( base::BindLambdaForTesting([&](Navigation* navigation) { EXPECT_EQ(url, navigation->GetURL()); EXPECT_EQ(NavigationState::kFailed, navigation->GetState()); run_loop.Quit(); // The NavigationControllerImpl that |observer| is observing will // be destroyed before control returns to the test, so destroy // |observer| now to avoid UaF. observer.reset(); })); auto* tab = static_cast(shell()->tab()); auto wait_for_response_start = std::make_unique(tab->web_contents(), url); shell()->LoadURL(url); // Wait until request is ready to start. EXPECT_TRUE(wait_for_response_start->WaitForRequestStart()); // Start the request. wait_for_response_start->ResumeNavigation(); // Wait for the request to arrive to ControllableHttpResponse. response.WaitForRequest(); response.Send(net::HTTP_OK, "text/html", ""); ASSERT_TRUE(wait_for_response_start->WaitForResponse()); EXPECT_EQ(NavigationState::kReceivingBytes, ongoing_navigation->GetState()); // Destroy |wait_for_response_start| before we indirectly destroy the // WebContents it's observing. wait_for_response_start.reset(); shell()->browser()->DestroyTab(tab); run_loop.Run(); } } // namespace weblayer