// Copyright (c) 2012 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 #include #include "base/bind.h" #include "base/callback_helpers.h" #include "base/command_line.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/run_loop.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/test/bind.h" #include "build/build_config.h" #include "content/browser/download/download_manager_impl.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/site_isolation_policy.h" #include "content/public/browser/web_contents.h" #include "content/public/common/content_client.h" #include "content/public/common/network_service_util.h" #include "content/public/common/url_constants.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/content_browser_test.h" #include "content/public/test/content_browser_test_utils.h" #include "content/public/test/test_navigation_observer.h" #include "content/public/test/test_utils.h" #include "content/public/test/url_loader_interceptor.h" #include "content/shell/browser/shell.h" #include "content/shell/browser/shell_content_browser_client.h" #include "content/test/test_content_browser_client.h" #include "net/base/filename_util.h" #include "net/base/load_flags.h" #include "net/base/net_errors.h" #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/embedded_test_server/http_request.h" #include "net/test/embedded_test_server/http_response.h" #include "net/test/url_request/url_request_failed_job.h" #include "net/test/url_request/url_request_mock_http_job.h" #include "services/network/public/cpp/features.h" #include "testing/gmock/include/gmock/gmock.h" #include "third_party/blink/public/common/loader/previews_state.h" #include "third_party/blink/public/common/loader/url_loader_throttle.h" #include "url/gurl.h" using base::ASCIIToUTF16; using testing::HasSubstr; using testing::Not; namespace content { class LoaderBrowserTest : public ContentBrowserTest, public DownloadManager::Observer { public: LoaderBrowserTest() : got_downloads_(false) {} protected: void SetUpOnMainThread() override { base::FilePath path = GetTestFilePath("", ""); GetIOThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(&net::URLRequestMockHTTPJob::AddUrlHandlers, path)); GetIOThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(&net::URLRequestFailedJob::AddUrlHandler)); host_resolver()->AddRule("*", "127.0.0.1"); } void CheckTitleTest(const GURL& url, const std::string& expected_title) { base::string16 expected_title16(ASCIIToUTF16(expected_title)); TitleWatcher title_watcher(shell()->web_contents(), expected_title16); EXPECT_TRUE(NavigateToURL(shell(), url)); EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle()); } bool GetPopupTitle(const GURL& url, base::string16* title) { EXPECT_TRUE(NavigateToURL(shell(), url)); ShellAddedObserver new_shell_observer; // Create dynamic popup. if (!ExecuteScript(shell(), "OpenPopup();")) return false; Shell* new_shell = new_shell_observer.GetShell(); *title = new_shell->web_contents()->GetTitle(); return true; } std::string GetCookies(const GURL& url) { return content::GetCookies(shell()->web_contents()->GetBrowserContext(), url); } bool got_downloads() const { return got_downloads_; } private: void SetUp() override { base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( "cors_exempt_header_list", "ExemptFoo"); ContentBrowserTest::SetUp(); } bool got_downloads_; }; // Test title for content created by javascript window.open(). // See http://crbug.com/5988 IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, DynamicTitle1) { ASSERT_TRUE(embedded_test_server()->Start()); GURL url(embedded_test_server()->GetURL("/dynamic1.html")); base::string16 title; ASSERT_TRUE(GetPopupTitle(url, &title)); EXPECT_TRUE(base::StartsWith(title, ASCIIToUTF16("My Popup Title"), base::CompareCase::SENSITIVE)) << "Actual title: " << title; } // Test title for content created by javascript window.open(). // See http://crbug.com/5988 IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, DynamicTitle2) { ASSERT_TRUE(embedded_test_server()->Start()); GURL url(embedded_test_server()->GetURL("/dynamic2.html")); base::string16 title; ASSERT_TRUE(GetPopupTitle(url, &title)); EXPECT_TRUE(base::StartsWith(title, ASCIIToUTF16("My Dynamic Title"), base::CompareCase::SENSITIVE)) << "Actual title: " << title; } // Tests that the renderer does not crash when issuing a stale-revalidation // request when the enable_referrers renderer preference is `false`. See // https://crbug.com/966140. IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, DisableReferrersStaleWhileRevalidate) { ASSERT_TRUE(embedded_test_server()->Start()); WebContentsImpl* web_contents = static_cast(shell()->web_contents()); // Navigate to the page that will eventually fetch a stale-revalidation // request. Ensure that the renderer has not crashed. ASSERT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL("/stale-while-revalidate.html"))); // Force-disable the |enable_referrers| preference. web_contents->GetMutableRendererPrefs()->enable_referrers = false; web_contents->SyncRendererPrefs(); // Wait for the stale-while-revalidate tests to pass by observing the page's // title. If the renderer crashes, the test immediately fails. base::string16 expected_title = base::ASCIIToUTF16("Pass"); TitleWatcher title_watcher(web_contents, expected_title); // The invocation of runTest() below starts a test written in JavaScript, that // after some time, creates a stale-revalidation request. The above IPC // message should be handled by the renderer (thus updating its preferences), // before this stale-revalidation request is sent. Technically nothing // guarantees this will happen, so it is theoretically possible the test is // racy, however in practice the renderer will always handle the IPC message // before the stale-revalidation request. This is because the renderer is // never completely blocked from the time the test starts. EXPECT_TRUE(ExecuteScript(shell(), "runTest()")); ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle()); } IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, SniffNoContentTypeNoData) { // Make sure no downloads start. BrowserContext::GetDownloadManager( shell()->web_contents()->GetBrowserContext()) ->AddObserver(this); ASSERT_TRUE(embedded_test_server()->Start()); GURL url(embedded_test_server()->GetURL("/content-sniffer-test3.html")); CheckTitleTest(url, "Content Sniffer Test 3"); EXPECT_EQ(1u, Shell::windows().size()); ASSERT_FALSE(got_downloads()); } // Make sure file URLs are not sniffed as HTML when they don't end in HTML. IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, DoNotSniffHTMLFromFileUrl) { base::FilePath path = GetTestFilePath(nullptr, "content-sniffer-test5.not-html"); GURL file_url = net::FilePathToFileURL(path); // If the file isn't rendered as HTML, the title will match the name of the // file, rather than the contents of the file's title tag. CheckTitleTest(file_url, path.BaseName().MaybeAsASCII()); } IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, ContentDispositionEmpty) { ASSERT_TRUE(embedded_test_server()->Start()); GURL url(embedded_test_server()->GetURL("/content-disposition-empty.html")); CheckTitleTest(url, "success"); } IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, ContentDispositionInline) { ASSERT_TRUE(embedded_test_server()->Start()); GURL url(embedded_test_server()->GetURL("/content-disposition-inline.html")); CheckTitleTest(url, "success"); } // Test for bug #1091358. IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, SyncXMLHttpRequest) { ASSERT_TRUE(embedded_test_server()->Start()); EXPECT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL("/sync_xmlhttprequest.html"))); // Let's check the XMLHttpRequest ran successfully. bool success = false; EXPECT_TRUE(ExecuteScriptAndExtractBool( shell(), "window.domAutomationController.send(DidSyncRequestSucceed());", &success)); EXPECT_TRUE(success); } // If this flakes, use http://crbug.com/62776. IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, SyncXMLHttpRequest_Disallowed) { ASSERT_TRUE(embedded_test_server()->Start()); EXPECT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL("/sync_xmlhttprequest_disallowed.html"))); // Let's check the XMLHttpRequest ran successfully. bool success = false; EXPECT_TRUE(ExecuteScriptAndExtractBool( shell(), "window.domAutomationController.send(DidSucceed());", &success)); EXPECT_TRUE(success); } // Test for bug #1159553 -- A synchronous xhr (whose content-type is // downloadable) would trigger download and hang the renderer process, // if executed while navigating to a new page. // Disabled on Mac: see http://crbug.com/56264 #if defined(OS_MAC) #define MAYBE_SyncXMLHttpRequest_DuringUnload \ DISABLED_SyncXMLHttpRequest_DuringUnload #else #define MAYBE_SyncXMLHttpRequest_DuringUnload SyncXMLHttpRequest_DuringUnload #endif IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, MAYBE_SyncXMLHttpRequest_DuringUnload) { ASSERT_TRUE(embedded_test_server()->Start()); BrowserContext::GetDownloadManager( shell()->web_contents()->GetBrowserContext()) ->AddObserver(this); CheckTitleTest( embedded_test_server()->GetURL("/sync_xmlhttprequest_during_unload.html"), "sync xhr on unload"); // Navigate to a new page, to dispatch unload event and trigger xhr. // (the bug would make this step hang the renderer). CheckTitleTest(embedded_test_server()->GetURL("/title2.html"), "Title Of Awesomeness"); ASSERT_FALSE(got_downloads()); } namespace { // Responds with a HungResponse for the specified URL to hang on the request. // It crashes the process. // // |crash_network_service_callback| crashes the network service when invoked, // and must be called on the UI thread. std::unique_ptr CancelOnRequest( const std::string& relative_url, int child_id, base::RepeatingClosure crash_network_service_callback, const net::test_server::HttpRequest& request) { if (request.relative_url != relative_url) return nullptr; content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, crash_network_service_callback); return std::make_unique(); } } // namespace // Tests the case where the request is cancelled by a layer above the // URLRequest, which passes the error on ResourceLoader teardown, rather than in // response to call to AsyncResourceHandler::OnResponseComplete. // Failed on Android M builder. See crbug/1111427. #if defined(OS_ANDROID) #define MAYBE_SyncXMLHttpRequest_Cancelled DISABLED_SyncXMLHttpRequest_Cancelled #else #define MAYBE_SyncXMLHttpRequest_Cancelled SyncXMLHttpRequest_Cancelled #endif IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, MAYBE_SyncXMLHttpRequest_Cancelled) { // If network service is running in-process, we can't simulate a crash. if (IsInProcessNetworkService()) return; embedded_test_server()->RegisterRequestHandler(base::BindRepeating( &CancelOnRequest, "/hung", shell()->web_contents()->GetMainFrame()->GetProcess()->GetID(), base::BindRepeating(&BrowserTestBase::SimulateNetworkServiceCrash, base::Unretained(this)))); ASSERT_TRUE(embedded_test_server()->Start()); WaitForLoadStop(shell()->web_contents()); EXPECT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL("/sync_xmlhttprequest_cancelled.html"))); int status_code = -1; EXPECT_TRUE(ExecuteScriptAndExtractInt( shell(), "window.domAutomationController.send(getErrorCode());", &status_code)); // 19 is the value of NETWORK_ERROR on DOMException. EXPECT_EQ(19, status_code); } // Flaky everywhere. http://crbug.com/130404 // Tests that onunload is run for cross-site requests. (Bug 1114994) IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, DISABLED_CrossSiteOnunloadCookie) { ASSERT_TRUE(embedded_test_server()->Start()); GURL url = embedded_test_server()->GetURL("/onunload_cookie.html"); CheckTitleTest(url, "set cookie on unload"); // Navigate to a new cross-site page, to dispatch unload event and set the // cookie. CheckTitleTest( net::URLRequestMockHTTPJob::GetMockUrl("content-sniffer-test0.html"), "Content Sniffer Test 0"); // Check that the cookie was set. EXPECT_EQ("onunloadCookie=foo", GetCookies(url)); } // If this flakes, use http://crbug.com/130404 // Tests that onunload is run for cross-site requests to URLs that complete // without network loads (e.g., about:blank, data URLs). IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, DISABLED_CrossSiteImmediateLoadOnunloadCookie) { ASSERT_TRUE(embedded_test_server()->Start()); GURL url = embedded_test_server()->GetURL("/onunload_cookie.html"); CheckTitleTest(url, "set cookie on unload"); // Navigate to a cross-site page that loads immediately without making a // network request. The unload event should still be run. EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); // Check that the cookie was set. EXPECT_EQ("onunloadCookie=foo", GetCookies(url)); } namespace { // Handles |request| by serving a redirect response. std::unique_ptr NoContentResponseHandler( const std::string& path, const net::test_server::HttpRequest& request) { if (!base::StartsWith(path, request.relative_url, base::CompareCase::SENSITIVE)) return std::unique_ptr(); std::unique_ptr http_response( new net::test_server::BasicHttpResponse); http_response->set_code(net::HTTP_NO_CONTENT); return std::move(http_response); } } // namespace // Tests that the unload handler is not run for 204 responses. // If this flakes use http://crbug.com/80596. IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, CrossSiteNoUnloadOn204) { const char kNoContentPath[] = "/nocontent"; embedded_test_server()->RegisterRequestHandler( base::BindRepeating(&NoContentResponseHandler, kNoContentPath)); ASSERT_TRUE(embedded_test_server()->Start()); // Start with a URL that sets a cookie in its unload handler. GURL url = embedded_test_server()->GetURL("/onunload_cookie.html"); CheckTitleTest(url, "set cookie on unload"); // Navigate to a cross-site URL that returns a 204 No Content response. EXPECT_TRUE(NavigateToURLAndExpectNoCommit( shell(), embedded_test_server()->GetURL(kNoContentPath))); // Check that the unload cookie was not set. EXPECT_EQ("", GetCookies(url)); } // Tests that the onbeforeunload and onunload logic is short-circuited if the // old renderer is gone. In that case, we don't want to wait for the old // renderer to run the handlers. // We need to disable this on Mac because the crash causes the OS CrashReporter // process to kick in to analyze the poor dead renderer. Unfortunately, if the // app isn't stripped of debug symbols, this takes about five minutes to // complete and isn't conducive to quick turnarounds. As we don't currently // strip the app on the build bots, this is bad times. #if defined(OS_MAC) #define MAYBE_CrossSiteAfterCrash DISABLED_CrossSiteAfterCrash #else #define MAYBE_CrossSiteAfterCrash CrossSiteAfterCrash #endif IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, MAYBE_CrossSiteAfterCrash) { // Make sure we have a live process before trying to kill it. EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); // Cause the renderer to crash. RenderProcessHostWatcher crash_observer( shell()->web_contents(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); EXPECT_FALSE(NavigateToURL(shell(), GURL(kChromeUICrashURL))); // Wait for browser to notice the renderer crash. crash_observer.Wait(); // Navigate to a new cross-site page. The browser should not wait around for // the old renderer's on{before}unload handlers to run. ASSERT_TRUE(embedded_test_server()->Start()); GURL url(embedded_test_server()->GetURL("/content-sniffer-test0.html")); CheckTitleTest(url, "Content Sniffer Test 0"); } // Tests that cross-site navigations work when the new page does not go through // the BufferedEventHandler (e.g., non-http{s} URLs). (Bug 1225872) IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, CrossSiteNavigationNonBuffered) { // Start with an HTTP page. ASSERT_TRUE(embedded_test_server()->Start()); GURL url1(embedded_test_server()->GetURL("/content-sniffer-test0.html")); CheckTitleTest(url1, "Content Sniffer Test 0"); // Now load a file:// page, which does not use the BufferedEventHandler. // Make sure that the page loads and displays a title, and doesn't get stuck. GURL url2 = GetTestUrl("", "title2.html"); CheckTitleTest(url2, "Title Of Awesomeness"); } // Flaky everywhere. http://crbug.com/130404 // Tests that a cross-site navigation to an error page (resulting in the link // doctor page) still runs the onunload handler and can support navigations // away from the link doctor page. (Bug 1235537) IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, DISABLED_CrossSiteNavigationErrorPage) { ASSERT_TRUE(embedded_test_server()->Start()); GURL url(embedded_test_server()->GetURL("/onunload_cookie.html")); CheckTitleTest(url, "set cookie on unload"); // Navigate to a new cross-site URL that results in an error. // TODO(creis): If this causes crashes or hangs, it might be for the same // reason as ErrorPageTest::DNSError. See bug 1199491 and // http://crbug.com/22877. GURL failed_url(embedded_test_server()->GetURL("a.com", "/title1.html")); std::unique_ptr url_interceptor = URLLoaderInterceptor::SetupRequestFailForURL(failed_url, net::ERR_NAME_NOT_RESOLVED); EXPECT_FALSE(NavigateToURL(shell(), failed_url)); EXPECT_NE(ASCIIToUTF16("set cookie on unload"), shell()->web_contents()->GetTitle()); // Check that the cookie was set, meaning that the onunload handler ran. EXPECT_EQ("onunloadCookie=foo", GetCookies(url)); // Check that renderer-initiated navigations still work. In a previous bug, // the ResourceDispatcherHost would think that such navigations were // cross-site, because we didn't clean up from the previous request. Since // WebContentsImpl was in the NORMAL state, it would ignore the attempt to run // the onunload handler, and the navigation would fail. We can't test by // redirecting to javascript:window.location='someURL', since javascript: // URLs are prohibited by policy from interacting with sensitive chrome // pages of which the error page is one. Instead, use automation to kick // off the navigation, and wait to see that the tab loads. base::string16 expected_title16(ASCIIToUTF16("Title Of Awesomeness")); TitleWatcher title_watcher(shell()->web_contents(), expected_title16); bool success; GURL test_url(embedded_test_server()->GetURL("/title2.html")); std::string redirect_script = "window.location='" + test_url.possibly_invalid_spec() + "';" + "window.domAutomationController.send(true);"; EXPECT_TRUE(ExecuteScriptAndExtractBool(shell(), redirect_script, &success)); EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle()); } IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, CrossSiteNavigationErrorPage2) { ASSERT_TRUE(embedded_test_server()->Start()); GURL url(embedded_test_server()->GetURL("/title2.html")); CheckTitleTest(url, "Title Of Awesomeness"); // Navigate to a new cross-site URL that results in an error. // TODO(creis): If this causes crashes or hangs, it might be for the same // reason as ErrorPageTest::DNSError. See bug 1199491 and // http://crbug.com/22877. GURL failed_url(embedded_test_server()->GetURL("a.com", "/title1.html")); std::unique_ptr url_interceptor = URLLoaderInterceptor::SetupRequestFailForURL(failed_url, net::ERR_NAME_NOT_RESOLVED); EXPECT_FALSE(NavigateToURL(shell(), failed_url)); EXPECT_NE(ASCIIToUTF16("Title Of Awesomeness"), shell()->web_contents()->GetTitle()); // Repeat navigation. We are testing that this completes. EXPECT_FALSE(NavigateToURL(shell(), failed_url)); EXPECT_NE(ASCIIToUTF16("Title Of Awesomeness"), shell()->web_contents()->GetTitle()); } IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, CrossOriginRedirectBlocked) { ASSERT_TRUE(embedded_test_server()->Start()); GURL url( embedded_test_server()->GetURL("/cross-origin-redirect-blocked.html")); // We expect the following URL requests from this test: // 1- navigation to http://127.0.0.1:[port]/cross-origin-redirect-blocked.html // 2- XHR to // http://127.0.0.1:[port]/server-redirect-302?http://a.com:[port]/title2.html // 3- above XHR is redirected to http://a.com:[port]/title2.html which should // be blocked // 4- When the page notices the above request is blocked, it issues an XHR to // http://127.0.0.1:[port]/title2.html // 5- When the above XHR succeed, the page navigates to // http://127.0.0.1:[port]/title3.html // // If the redirect in #3 were not blocked, we'd instead see a navigation // to http://a.com[port]/title2.html, and the title would be different. CheckTitleTest(url, "Title Of More Awesomeness"); } // Tests that ResourceRequestInfoImpl is updated correctly on failed // requests, to prevent calling Read on a request that has already failed. // See bug 40250. IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, CrossSiteFailedRequest) { // Visit another URL first to trigger a cross-site navigation. EXPECT_TRUE(NavigateToURL(shell(), GetTestUrl("", "simple_page.html"))); // Visit a URL that fails without calling ResourceDispatcherHost::Read. GURL broken_url("chrome://theme"); EXPECT_FALSE(NavigateToURL(shell(), broken_url)); } namespace { std::unique_ptr HandleRedirectRequest( const std::string& request_path, const net::test_server::HttpRequest& request) { if (!base::StartsWith(request.relative_url, request_path, base::CompareCase::SENSITIVE)) return std::unique_ptr(); std::unique_ptr http_response( new net::test_server::BasicHttpResponse); http_response->set_code(net::HTTP_FOUND); http_response->AddCustomHeader( "Location", request.relative_url.substr(request_path.length())); return std::move(http_response); } } // namespace // Test that we update the cookie policy URLs correctly when transferring // navigations. IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, CookiePolicy) { embedded_test_server()->RegisterRequestHandler( base::BindRepeating(&HandleRedirectRequest, "/redirect?")); ASSERT_TRUE(embedded_test_server()->Start()); std::string set_cookie_url(base::StringPrintf( "http://localhost:%u/set_cookie.html", embedded_test_server()->port())); GURL url(embedded_test_server()->GetURL("/redirect?" + set_cookie_url)); base::string16 expected_title16(ASCIIToUTF16("cookie set")); TitleWatcher title_watcher(shell()->web_contents(), expected_title16); EXPECT_TRUE(NavigateToURL(shell(), url, GURL(set_cookie_url) /* expected_commit_url */)); EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle()); } // Test that ui::PAGE_TRANSITION_CLIENT_REDIRECT is correctly set // when encountering a meta refresh tag. IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, PageTransitionClientRedirect) { ASSERT_TRUE(embedded_test_server()->Start()); NavigateToURLBlockUntilNavigationsComplete( shell(), embedded_test_server()->GetURL("/client_redirect.html"), 2); NavigationEntry* entry = shell()->web_contents()->GetController().GetLastCommittedEntry(); EXPECT_TRUE(entry->GetTransitionType() & ui::PAGE_TRANSITION_CLIENT_REDIRECT); } IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, SubresourceRedirectToDataURLBlocked) { ASSERT_TRUE(embedded_test_server()->Start()); EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo"))); GURL subresource_url = embedded_test_server()->GetURL( "/server-redirect?data:text/plain,redirected1"); std::string script = R"((url => { var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onload = () => domAutomationController.send("ALLOWED"); xhr.onerror = () => domAutomationController.send("BLOCKED"); xhr.send(); }))"; std::string result; ASSERT_TRUE(ExecuteScriptAndExtractString( shell(), script + "('" + subresource_url.spec() + "')", &result)); EXPECT_EQ("BLOCKED", result); } IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, RedirectToDataURLBlocked) { ASSERT_TRUE(embedded_test_server()->Start()); EXPECT_FALSE(NavigateToURL( shell(), embedded_test_server()->GetURL( "/server-redirect?data:text/plain,redirected1"))); } IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, RedirectToAboutURLBlocked) { ASSERT_TRUE(embedded_test_server()->Start()); EXPECT_FALSE(NavigateToURL( shell(), embedded_test_server()->GetURL( "/server-redirect?" + std::string(url::kAboutBlankURL)))); } namespace { // Creates a valid filesystem URL. GURL CreateFileSystemURL(Shell* window) { std::string filesystem_url_string; EXPECT_TRUE( ExecuteScriptAndExtractString(window, R"( var blob = new Blob(['hello'], {type: 'text/html'}); window.webkitRequestFileSystem(TEMPORARY, blob.size, fs => { fs.root.getFile('foo.html', {create: true}, file => { file.createWriter(writer => { writer.write(blob); writer.onwriteend = () => { domAutomationController.send(file.toURL()); } }); }); });)", &filesystem_url_string)); GURL filesystem_url(filesystem_url_string); EXPECT_TRUE(filesystem_url.is_valid()); EXPECT_TRUE(filesystem_url.SchemeIsFileSystem()); return filesystem_url; } } // namespace IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, SubresourceRedirectToFileSystemURLBlocked) { ASSERT_TRUE(embedded_test_server()->Start()); EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo"))); GURL subresource_url = embedded_test_server()->GetURL( "/server-redirect?" + CreateFileSystemURL(shell()).spec()); std::string script = R"((url => { var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onload = () => domAutomationController.send("ALLOWED"); xhr.onerror = () => domAutomationController.send("BLOCKED"); xhr.send(); }))"; std::string result; ASSERT_TRUE(ExecuteScriptAndExtractString( shell(), script + "('" + subresource_url.spec() + "')", &result)); EXPECT_EQ("BLOCKED", result); } IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, RedirectToFileSystemURLBlocked) { ASSERT_TRUE(embedded_test_server()->Start()); // Need to navigate to a URL first so the filesystem can be created. EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo"))); EXPECT_FALSE(NavigateToURL( shell(), embedded_test_server()->GetURL( "/server-redirect?" + CreateFileSystemURL(shell()).spec()))); } namespace { struct RequestData { const GURL url; const net::SiteForCookies site_for_cookies; const base::Optional initiator; const int load_flags; const std::string referrer; RequestData(const GURL& url, const net::SiteForCookies& site_for_cookies, const base::Optional& initiator, int load_flags, const std::string& referrer) : url(url), site_for_cookies(site_for_cookies), initiator(initiator), load_flags(load_flags), referrer(referrer) {} }; } // namespace class RequestDataBrowserTest : public ContentBrowserTest { public: RequestDataBrowserTest() : interceptor_(std::make_unique( base::BindRepeating(&RequestDataBrowserTest::OnRequest, base::Unretained(this)))) {} ~RequestDataBrowserTest() override {} std::vector data() { base::AutoLock auto_lock(requests_lock_); auto copy = requests_; return copy; } void WaitForRequests(size_t count) { while (true) { base::RunLoop run_loop; { base::AutoLock auto_lock(requests_lock_); if (requests_.size() == count) return; requests_closure_ = run_loop.QuitClosure(); } run_loop.Run(); } } private: void SetUpOnMainThread() override { ContentBrowserTest::SetUpOnMainThread(); ASSERT_TRUE(embedded_test_server()->Start()); host_resolver()->AddRule("*", "127.0.0.1"); } void TearDownOnMainThread() override { interceptor_.reset(); } bool OnRequest(URLLoaderInterceptor::RequestParams* params) { RequestCreated(RequestData( params->url_request.url, params->url_request.site_for_cookies, params->url_request.request_initiator, params->url_request.load_flags, params->url_request.referrer.spec())); return false; } void RequestCreated(RequestData data) { base::AutoLock auto_lock(requests_lock_); requests_.push_back(data); if (requests_closure_) std::move(requests_closure_).Run(); } base::Lock requests_lock_; std::vector requests_; base::OnceClosure requests_closure_; std::unique_ptr interceptor_; }; IN_PROC_BROWSER_TEST_F(RequestDataBrowserTest, Basic) { GURL top_url(embedded_test_server()->GetURL("/page_with_subresources.html")); url::Origin top_origin = url::Origin::Create(top_url); NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1); auto requests = data(); EXPECT_EQ(8u, requests.size()); // All resources loaded directly by the top-level document should have a // |first_party| and |initiator| that match the URL of the top-level document. // The top-level document itself doesn't have an |initiator|. const RequestData* first_request = &requests[0]; EXPECT_TRUE(first_request->site_for_cookies.IsEquivalent( net::SiteForCookies::FromUrl(top_url))); EXPECT_FALSE(first_request->initiator.has_value()); for (size_t i = 1; i < requests.size(); i++) { const RequestData* request = &requests[i]; EXPECT_TRUE(request->site_for_cookies.IsEquivalent( net::SiteForCookies::FromOrigin(top_origin))); ASSERT_TRUE(request->initiator.has_value()); EXPECT_EQ(top_origin, request->initiator); } } IN_PROC_BROWSER_TEST_F(RequestDataBrowserTest, LinkRelPrefetch) { GURL top_url(embedded_test_server()->GetURL("/link_rel_prefetch.html")); url::Origin top_origin = url::Origin::Create(top_url); NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1); WaitForRequests(2u); auto requests = data(); EXPECT_EQ(2u, requests.size()); auto* request = &requests[1]; EXPECT_EQ(top_origin, request->initiator); EXPECT_EQ(top_url, request->referrer); EXPECT_TRUE(request->load_flags & net::LOAD_PREFETCH); } IN_PROC_BROWSER_TEST_F(RequestDataBrowserTest, LinkRelPrefetchReferrerPolicy) { GURL top_url(embedded_test_server()->GetURL( "/link_rel_prefetch_referrer_policy.html")); GURL img_url(embedded_test_server()->GetURL("/image.jpg")); url::Origin top_origin = url::Origin::Create(top_url); NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1); WaitForRequests(2u); auto requests = data(); EXPECT_EQ(2u, requests.size()); auto* main_frame_request = &requests[0]; auto* image_request = &requests[1]; // Check the main frame request. EXPECT_EQ(top_url, main_frame_request->url); EXPECT_FALSE(main_frame_request->initiator.has_value()); // Check the image request. EXPECT_EQ(img_url, image_request->url); EXPECT_TRUE(image_request->initiator.has_value()); EXPECT_EQ(top_origin, image_request->initiator); // Respect the "origin" policy set by the tag. EXPECT_EQ(top_url.GetOrigin().spec(), image_request->referrer); EXPECT_TRUE(image_request->load_flags & net::LOAD_PREFETCH); } IN_PROC_BROWSER_TEST_F(RequestDataBrowserTest, BasicCrossSite) { GURL top_url(embedded_test_server()->GetURL( "a.com", "/nested_page_with_subresources.html")); GURL nested_url(embedded_test_server()->GetURL( "not-a.com", "/page_with_subresources.html")); url::Origin top_origin = url::Origin::Create(top_url); url::Origin nested_origin = url::Origin::Create(nested_url); NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1); auto requests = data(); EXPECT_EQ(9u, requests.size()); // The first items loaded are the top-level and nested documents. These should // both have a |site_for_cookies| that matches the origin of the top-level // document. The top-level document has no initiator and the nested frame is // initiated by the top-level document. EXPECT_EQ(top_url, requests[0].url); EXPECT_TRUE(requests[0].site_for_cookies.IsEquivalent( net::SiteForCookies::FromOrigin(top_origin))); EXPECT_FALSE(requests[0].initiator.has_value()); EXPECT_EQ(nested_url, requests[1].url); EXPECT_TRUE(requests[1].site_for_cookies.IsEquivalent( net::SiteForCookies::FromOrigin(top_origin))); EXPECT_EQ(top_origin, requests[1].initiator); // The remaining items are loaded as subresources in the nested document, and // should have a unique first-party, and an initiator that matches the // document in which they're embedded. for (size_t i = 2; i < requests.size(); i++) { SCOPED_TRACE(requests[i].url); EXPECT_TRUE(requests[i].site_for_cookies.IsNull()); EXPECT_EQ(nested_origin, requests[i].initiator); } } IN_PROC_BROWSER_TEST_F(RequestDataBrowserTest, SameOriginNested) { GURL top_url(embedded_test_server()->GetURL("/page_with_iframe.html")); GURL image_url(embedded_test_server()->GetURL("/image.jpg")); GURL nested_url(embedded_test_server()->GetURL("/title1.html")); url::Origin top_origin = url::Origin::Create(top_url); NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1); auto requests = data(); EXPECT_EQ(3u, requests.size()); // User-initiated top-level navigations have a first-party that matches the // URL to which they navigate. The navigation was initiated outside of a // document, so there is no |initiator|. EXPECT_EQ(top_url, requests[0].url); EXPECT_TRUE(requests[0].site_for_cookies.IsEquivalent( net::SiteForCookies::FromOrigin(top_origin))); EXPECT_FALSE(requests[0].initiator.has_value()); // Subresource requests have a first-party and initiator that matches the // document in which they're embedded. EXPECT_EQ(image_url, requests[1].url); EXPECT_TRUE(requests[1].site_for_cookies.IsEquivalent( net::SiteForCookies::FromOrigin(top_origin))); EXPECT_EQ(top_origin, requests[1].initiator); // Same-origin nested frames have a first-party and initiator that matches // the document in which they're embedded (since the frame is same site with // toplevel). EXPECT_EQ(nested_url, requests[2].url); EXPECT_TRUE(requests[2].site_for_cookies.IsEquivalent( net::SiteForCookies::FromOrigin(top_origin))); EXPECT_EQ(top_origin, requests[2].initiator); } IN_PROC_BROWSER_TEST_F(RequestDataBrowserTest, SameOriginAuxiliary) { GURL top_url(embedded_test_server()->GetURL("/simple_links.html")); GURL auxiliary_url(embedded_test_server()->GetURL("/title2.html")); url::Origin top_origin = url::Origin::Create(top_url); NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1); ShellAddedObserver new_shell_observer; bool success = false; EXPECT_TRUE(ExecuteScriptAndExtractBool( shell(), "window.domAutomationController.send(clickSameSiteNewWindowLink());", &success)); EXPECT_TRUE(success); Shell* new_shell = new_shell_observer.GetShell(); EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents())); auto requests = data(); EXPECT_EQ(2u, requests.size()); // User-initiated top-level navigations have a first-party that matches the // URL to which they navigate, even if they fail to load. The navigation was // initiated outside of a document, so there is no |initiator|. EXPECT_EQ(top_url, requests[0].url); EXPECT_TRUE(requests[0].site_for_cookies.IsEquivalent( net::SiteForCookies::FromOrigin(top_origin))); EXPECT_FALSE(requests[0].initiator.has_value()); // Auxiliary navigations have a first-party that matches the URL to which they // navigate, and an initiator that matches the document that triggered them. EXPECT_EQ(auxiliary_url, requests[1].url); EXPECT_TRUE(requests[1].site_for_cookies.IsEquivalent( net::SiteForCookies::FromUrl(auxiliary_url))); EXPECT_EQ(top_origin, requests[1].initiator); } IN_PROC_BROWSER_TEST_F(RequestDataBrowserTest, CrossOriginAuxiliary) { GURL top_url(embedded_test_server()->GetURL("/simple_links.html")); GURL auxiliary_url(embedded_test_server()->GetURL("foo.com", "/title2.html")); url::Origin top_origin = url::Origin::Create(top_url); NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1); const char kReplacePortNumber[] = "window.domAutomationController.send(setPortNumber(%d));"; uint16_t port_number = embedded_test_server()->port(); bool success = false; EXPECT_TRUE(ExecuteScriptAndExtractBool( shell(), base::StringPrintf(kReplacePortNumber, port_number), &success)); success = false; ShellAddedObserver new_shell_observer; success = false; EXPECT_TRUE(ExecuteScriptAndExtractBool( shell(), "window.domAutomationController.send(clickCrossSiteNewWindowLink());", &success)); EXPECT_TRUE(success); Shell* new_shell = new_shell_observer.GetShell(); EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents())); auto requests = data(); EXPECT_EQ(2u, requests.size()); // User-initiated top-level navigations have a first-party that matches the // URL to which they navigate, even if they fail to load. The navigation was // initiated outside of a document, so there is no initiator. EXPECT_EQ(top_url, requests[0].url); EXPECT_TRUE(requests[0].site_for_cookies.IsEquivalent( net::SiteForCookies::FromOrigin(top_origin))); EXPECT_FALSE(requests[0].initiator.has_value()); // Auxiliary navigations have a first-party that matches the URL to which they // navigate, and an initiator that matches the document that triggered them. EXPECT_EQ(auxiliary_url, requests[1].url); EXPECT_TRUE(requests[1].site_for_cookies.IsEquivalent( net::SiteForCookies::FromUrl(auxiliary_url))); EXPECT_EQ(top_origin, requests[1].initiator); } IN_PROC_BROWSER_TEST_F(RequestDataBrowserTest, FailedNavigation) { // Navigating to this URL will fail, as we haven't taught the host resolver // about 'a.com'. GURL top_url(embedded_test_server()->GetURL("a.com", "/simple_page.html")); url::Origin top_origin = url::Origin::Create(top_url); NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1); auto requests = data(); EXPECT_EQ(1u, requests.size()); // User-initiated top-level navigations have a first-party that matches the // URL to which they navigate, even if they fail to load. The navigation was // initiated outside of a document, so there is no initiator. EXPECT_EQ(top_url, requests[0].url); EXPECT_TRUE(requests[0].site_for_cookies.IsEquivalent( net::SiteForCookies::FromOrigin(top_origin))); EXPECT_FALSE(requests[0].initiator.has_value()); } IN_PROC_BROWSER_TEST_F(RequestDataBrowserTest, CrossOriginNested) { GURL top_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b)")); GURL top_js_url( embedded_test_server()->GetURL("a.com", "/tree_parser_util.js")); GURL nested_url(embedded_test_server()->GetURL( "b.com", "/cross_site_iframe_factory.html?b()")); GURL nested_js_url( embedded_test_server()->GetURL("b.com", "/tree_parser_util.js")); url::Origin top_origin = url::Origin::Create(top_url); url::Origin nested_origin = url::Origin::Create(nested_url); NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1); auto requests = data(); EXPECT_EQ(4u, requests.size()); // User-initiated top-level navigations have a |first-party|. The navigation // was initiated outside of a document, so there are no initiator. EXPECT_EQ(top_url, requests[0].url); EXPECT_TRUE(requests[0].site_for_cookies.IsEquivalent( net::SiteForCookies::FromOrigin(top_origin))); EXPECT_FALSE(requests[0].initiator.has_value()); EXPECT_EQ(top_js_url, requests[1].url); EXPECT_TRUE(requests[1].site_for_cookies.IsEquivalent( net::SiteForCookies::FromOrigin(top_origin))); EXPECT_EQ(top_origin, requests[1].initiator); // Cross-origin frames have a first-party and initiator that matches the URL // in which they're embedded (if they are the first cross-origin thing) EXPECT_EQ(nested_url, requests[2].url); EXPECT_TRUE(requests[2].site_for_cookies.IsEquivalent( net::SiteForCookies::FromOrigin(top_origin))); EXPECT_EQ(top_origin, requests[2].initiator); // Cross-origin subresource requests have a unique first-party, and an // initiator that matches the document in which they're embedded. EXPECT_EQ(nested_js_url, requests[3].url); EXPECT_TRUE(requests[3].site_for_cookies.IsNull()); EXPECT_EQ(nested_origin, requests[3].initiator); } // Regression test for https://crbug.com/648608. An attacker could trivially // bypass cookies SameSite=Strict protections by navigating a new window twice. IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, CookieSameSiteStrictOpenNewNamedWindowTwice) { ASSERT_TRUE(embedded_test_server()->Start()); // 1) Add cookies for 'a.com', one of them with the "SameSite=Strict" option. BrowserContext* context = shell()->web_contents()->GetBrowserContext(); GURL a_url("http://a.com"); EXPECT_TRUE(SetCookie(context, a_url, "cookie_A=A; SameSite=Strict;")); EXPECT_TRUE(SetCookie(context, a_url, "cookie_B=B")); // 2) Navigate to malicious.com. EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL( "malicious.com", "/title1.html"))); // 2.1) malicious.com opens a new window to 'http://a.com/echoall'. GURL echoall_url = embedded_test_server()->GetURL("a.com", "/echoall"); std::string script = base::StringPrintf("window.open('%s', 'named_frame');", echoall_url.spec().c_str()); { TestNavigationObserver new_tab_observer(shell()->web_contents(), 1); new_tab_observer.StartWatchingNewWebContents(); EXPECT_TRUE(ExecuteScript(shell(), script)); new_tab_observer.Wait(); ASSERT_EQ(2u, Shell::windows().size()); Shell* new_shell = Shell::windows()[1]; EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents())); // Only the cookie without "SameSite=Strict" should be sent. std::string html_content; EXPECT_TRUE(ExecuteScriptAndExtractString( new_shell, "domAutomationController.send(document.body.textContent)", &html_content)); EXPECT_THAT(html_content.c_str(), Not(HasSubstr("cookie_A=A"))); EXPECT_THAT(html_content.c_str(), HasSubstr("cookie_B=B")); } // 2.2) Same as in 2.1). The difference is that the new tab will be reused. { Shell* new_shell = Shell::windows()[1]; TestNavigationObserver new_tab_observer(new_shell->web_contents(), 1); EXPECT_TRUE(ExecuteScript(shell(), script)); new_tab_observer.Wait(); ASSERT_EQ(2u, Shell::windows().size()); EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents())); // Only the cookie without "SameSite=Strict" should be sent. std::string html_content; EXPECT_TRUE(ExecuteScriptAndExtractString( new_shell, "domAutomationController.send(document.body.textContent)", &html_content)); EXPECT_THAT(html_content.c_str(), Not(HasSubstr("cookie_A=A"))); EXPECT_THAT(html_content.c_str(), HasSubstr("cookie_B=B")); } } class URLModifyingThrottle : public blink::URLLoaderThrottle { public: URLModifyingThrottle(bool modify_start, bool modify_redirect) : modify_start_(modify_start), modify_redirect_(modify_redirect) {} ~URLModifyingThrottle() override = default; void WillStartRequest(network::ResourceRequest* request, bool* defer) override { if (!modify_start_) return; GURL::Replacements replacements; replacements.SetQueryStr("foo=bar"); request->url = request->url.ReplaceComponents(replacements); request->headers.SetHeader("Foo", "BarRequest"); request->cors_exempt_headers.SetHeader("ExemptFoo", "ExemptBarRequest"); } void WillRedirectRequest( net::RedirectInfo* redirect_info, const network::mojom::URLResponseHead& response_head, bool* defer, std::vector* to_be_removed_request_headers, net::HttpRequestHeaders* modified_request_headers, net::HttpRequestHeaders* modified_cors_exempt_request_headers) override { if (!modify_redirect_) return; modified_request_headers->SetHeader("Foo", "BarRedirect"); modified_cors_exempt_request_headers->SetHeader("ExemptFoo", "ExemptBarRedirect"); if (modified_redirect_url_) return; // Only need to do this once. modified_redirect_url_ = true; GURL::Replacements replacements; replacements.SetQueryStr("foo=bar"); redirect_info->new_url = redirect_info->new_url.ReplaceComponents(replacements); } private: bool modify_start_; bool modify_redirect_; bool modified_redirect_url_ = false; DISALLOW_COPY_AND_ASSIGN(URLModifyingThrottle); }; class ThrottleContentBrowserClient : public TestContentBrowserClient { public: ThrottleContentBrowserClient(bool modify_start, bool modify_redirect) : TestContentBrowserClient(), modify_start_(modify_start), modify_redirect_(modify_redirect) {} ~ThrottleContentBrowserClient() override {} // ContentBrowserClient overrides: std::vector> CreateURLLoaderThrottles( const network::ResourceRequest& request, BrowserContext* browser_context, const base::RepeatingCallback& wc_getter, NavigationUIData* navigation_ui_data, int frame_tree_node_id) override { std::vector> throttles; auto throttle = std::make_unique(modify_start_, modify_redirect_); throttles.push_back(std::move(throttle)); return throttles; } private: bool modify_start_; bool modify_redirect_; DISALLOW_COPY_AND_ASSIGN(ThrottleContentBrowserClient); }; // Ensures if a URLLoaderThrottle modifies a URL in WillStartRequest the // new request matches IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, URLLoaderThrottleStartModify) { base::Lock lock; ThrottleContentBrowserClient content_browser_client(true, false); auto* old_content_browser_client = SetBrowserClientForTesting(&content_browser_client); std::set urls_requested; std::map header_map; embedded_test_server()->RegisterRequestMonitor(base::BindLambdaForTesting( [&](const net::test_server::HttpRequest& request) { base::AutoLock auto_lock(lock); urls_requested.insert(request.GetURL()); header_map[request.GetURL()] = request.headers; })); ASSERT_TRUE(embedded_test_server()->Start()); GURL url = embedded_test_server()->GetURL("/simple_page.html"); GURL expected_url(url.spec() + "?foo=bar"); EXPECT_TRUE( NavigateToURL(shell(), url, expected_url /* expected_commit_url */)); { base::AutoLock auto_lock(lock); ASSERT_TRUE(urls_requested.find(expected_url) != urls_requested.end()); ASSERT_TRUE(header_map[expected_url]["Foo"] == "BarRequest"); ASSERT_TRUE(header_map[expected_url]["ExemptFoo"] == "ExemptBarRequest"); } SetBrowserClientForTesting(old_content_browser_client); } // Ensures if a URLLoaderThrottle modifies a URL and headers in // WillRedirectRequest the new request matches. IN_PROC_BROWSER_TEST_F(LoaderBrowserTest, URLLoaderThrottleRedirectModify) { base::Lock lock; ThrottleContentBrowserClient content_browser_client(false, true); auto* old_content_browser_client = SetBrowserClientForTesting(&content_browser_client); std::set urls_requested; std::map header_map; embedded_test_server()->RegisterRequestMonitor(base::BindLambdaForTesting( [&](const net::test_server::HttpRequest& request) { base::AutoLock auto_lock(lock); urls_requested.insert(request.GetURL()); header_map[request.GetURL()] = request.headers; })); ASSERT_TRUE(embedded_test_server()->Start()); GURL url = embedded_test_server()->GetURL("/server-redirect?simple_page.html"); GURL expected_url = embedded_test_server()->GetURL("/simple_page.html?foo=bar"); EXPECT_TRUE( NavigateToURL(shell(), url, expected_url /* expected_commit_url */)); { base::AutoLock auto_lock(lock); ASSERT_EQ(header_map[expected_url]["Foo"], "BarRedirect"); ASSERT_EQ(header_map[expected_url]["ExemptFoo"], "ExemptBarRedirect"); ASSERT_NE(urls_requested.find(expected_url), urls_requested.end()); } SetBrowserClientForTesting(old_content_browser_client); } } // namespace content