| // Copyright 2014 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 <stdint.h> |
| |
| #include "base/command_line.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "build/build_config.h" |
| #include "content/browser/child_process_security_policy_impl.h" |
| #include "content/browser/frame_host/navigation_handle_impl.h" |
| #include "content/browser/frame_host/navigation_request.h" |
| #include "content/browser/loader/resource_dispatcher_host_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/common/frame_messages.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/download_manager_delegate.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/notification_types.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/browser_side_navigation_policy.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/url_constants.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/navigation_handle_observer.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/shell/browser/shell_download_manager_delegate.h" |
| #include "content/shell/browser/shell_network_delegate.h" |
| #include "content/test/content_browser_test_utils_internal.h" |
| #include "content/test/did_commit_provisional_load_interceptor.h" |
| #include "ipc/ipc_security_test_util.h" |
| #include "net/base/filename_util.h" |
| #include "net/base/load_flags.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/url_request/url_request_failed_job.h" |
| #include "services/network/public/cpp/features.h" |
| #include "url/gurl.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| class InterceptAndCancelDidCommitProvisionalLoad |
| : public DidCommitProvisionalLoadInterceptor { |
| public: |
| explicit InterceptAndCancelDidCommitProvisionalLoad(WebContents* web_contents) |
| : DidCommitProvisionalLoadInterceptor(web_contents) {} |
| ~InterceptAndCancelDidCommitProvisionalLoad() override {} |
| |
| void Wait(size_t number_of_messages) { |
| while (intercepted_messages_.size() < number_of_messages) { |
| loop_.reset(new base::RunLoop); |
| loop_->Run(); |
| } |
| } |
| |
| const std::vector<::FrameHostMsg_DidCommitProvisionalLoad_Params>& |
| intercepted_messages() const { |
| return intercepted_messages_; |
| } |
| |
| std::vector<::service_manager::mojom::InterfaceProviderRequest>& |
| intercepted_requests() { |
| return intercepted_requests_; |
| } |
| |
| protected: |
| bool WillDispatchDidCommitProvisionalLoad( |
| RenderFrameHost* render_frame_host, |
| ::FrameHostMsg_DidCommitProvisionalLoad_Params* params, |
| service_manager::mojom::InterfaceProviderRequest* |
| interface_provider_request) override { |
| intercepted_messages_.push_back(*params); |
| intercepted_requests_.push_back(std::move(*interface_provider_request)); |
| if (loop_) |
| loop_->Quit(); |
| // Do not send the message to the RenderFrameHostImpl. |
| return false; |
| } |
| |
| std::vector<::FrameHostMsg_DidCommitProvisionalLoad_Params> |
| intercepted_messages_; |
| std::vector<::service_manager::mojom::InterfaceProviderRequest> |
| intercepted_requests_; |
| std::unique_ptr<base::RunLoop> loop_; |
| }; |
| |
| // Record every WebContentsObserver's event related to navigation. The goal is |
| // to check these events happen and happen in the expected right order. |
| class NavigationRecorder : public WebContentsObserver { |
| public: |
| NavigationRecorder(WebContents* web_contents) |
| : WebContentsObserver(web_contents) {} |
| |
| // WebContentsObserver implementation. |
| void DidStartNavigation(NavigationHandle* navigation_handle) override { |
| records_.push_back("start " + navigation_handle->GetURL().path()); |
| WakeUp(); |
| } |
| void ReadyToCommitNavigation(NavigationHandle* navigation_handle) override { |
| records_.push_back("ready-to-commit " + navigation_handle->GetURL().path()); |
| WakeUp(); |
| } |
| void DidFinishNavigation(NavigationHandle* navigation_handle) override { |
| records_.push_back("did-commit " + navigation_handle->GetURL().path()); |
| WakeUp(); |
| } |
| |
| void WaitForEvents(size_t numbers_of_events) { |
| while (records_.size() < numbers_of_events) { |
| loop_.reset(new base::RunLoop); |
| loop_->Run(); |
| loop_.reset(); |
| } |
| } |
| |
| const std::vector<std::string> records() { return records_; } |
| |
| private: |
| void WakeUp() { |
| if (loop_) |
| loop_->Quit(); |
| } |
| |
| std::unique_ptr<base::RunLoop> loop_; |
| std::vector<std::string> records_; |
| }; |
| |
| } // namespace |
| |
| // Test with BrowserSideNavigation enabled (aka PlzNavigate). |
| // If you don't need a custom embedded test server, please use the next class |
| // below (BrowserSideNavigationBrowserTest), it will automatically start the |
| // default server. |
| // TODO(clamy): Rename those NavigationBrowserTests. |
| class BrowserSideNavigationBaseBrowserTest : public ContentBrowserTest { |
| protected: |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| } |
| }; |
| |
| class BrowserSideNavigationBrowserTest |
| : public BrowserSideNavigationBaseBrowserTest { |
| protected: |
| void SetUpOnMainThread() override { |
| BrowserSideNavigationBaseBrowserTest::SetUpOnMainThread(); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| }; |
| |
| // Ensure that browser initiated basic navigations work with browser side |
| // navigation. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBrowserTest, |
| BrowserInitiatedNavigations) { |
| // Perform a navigation with no live renderer. |
| { |
| TestNavigationObserver observer(shell()->web_contents()); |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| NavigateToURL(shell(), url); |
| EXPECT_EQ(url, observer.last_navigation_url()); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| } |
| |
| RenderFrameHost* initial_rfh = |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree()->root()->current_frame_host(); |
| |
| // Perform a same site navigation. |
| { |
| TestNavigationObserver observer(shell()->web_contents()); |
| GURL url(embedded_test_server()->GetURL("/title2.html")); |
| NavigateToURL(shell(), url); |
| EXPECT_EQ(url, observer.last_navigation_url()); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| } |
| |
| // The RenderFrameHost should not have changed. |
| EXPECT_EQ(initial_rfh, static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree()->root()->current_frame_host()); |
| |
| // Perform a cross-site navigation. |
| { |
| TestNavigationObserver observer(shell()->web_contents()); |
| GURL url = embedded_test_server()->GetURL("foo.com", "/title3.html"); |
| NavigateToURL(shell(), url); |
| EXPECT_EQ(url, observer.last_navigation_url()); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| } |
| |
| // The RenderFrameHost should have changed. |
| EXPECT_NE(initial_rfh, static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree()->root()->current_frame_host()); |
| } |
| |
| // Ensure that renderer initiated same-site navigations work with browser side |
| // navigation. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBrowserTest, |
| RendererInitiatedSameSiteNavigation) { |
| // Perform a navigation with no live renderer. |
| { |
| TestNavigationObserver observer(shell()->web_contents()); |
| GURL url(embedded_test_server()->GetURL("/simple_links.html")); |
| NavigateToURL(shell(), url); |
| EXPECT_EQ(url, observer.last_navigation_url()); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| } |
| |
| RenderFrameHost* initial_rfh = |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree()->root()->current_frame_host(); |
| |
| // Simulate clicking on a same-site link. |
| { |
| TestNavigationObserver observer(shell()->web_contents()); |
| GURL url(embedded_test_server()->GetURL("/title2.html")); |
| bool success = false; |
| EXPECT_TRUE(ExecuteScriptAndExtractBool( |
| shell(), "window.domAutomationController.send(clickSameSiteLink());", |
| &success)); |
| EXPECT_TRUE(success); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_EQ(url, observer.last_navigation_url()); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| } |
| |
| // The RenderFrameHost should not have changed. |
| EXPECT_EQ(initial_rfh, static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree()->root()->current_frame_host()); |
| } |
| |
| // Ensure that renderer initiated cross-site navigations work with browser side |
| // navigation. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBrowserTest, |
| RendererInitiatedCrossSiteNavigation) { |
| // Perform a navigation with no live renderer. |
| { |
| TestNavigationObserver observer(shell()->web_contents()); |
| GURL url(embedded_test_server()->GetURL("/simple_links.html")); |
| NavigateToURL(shell(), url); |
| EXPECT_EQ(url, observer.last_navigation_url()); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| } |
| |
| RenderFrameHost* initial_rfh = |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree()->root()->current_frame_host(); |
| |
| // Simulate clicking on a cross-site link. |
| { |
| TestNavigationObserver observer(shell()->web_contents()); |
| const char kReplacePortNumber[] = |
| "window.domAutomationController.send(setPortNumber(%d));"; |
| uint16_t port_number = embedded_test_server()->port(); |
| GURL url = embedded_test_server()->GetURL("foo.com", "/title2.html"); |
| bool success = false; |
| EXPECT_TRUE(ExecuteScriptAndExtractBool( |
| shell(), base::StringPrintf(kReplacePortNumber, port_number), |
| &success)); |
| success = false; |
| EXPECT_TRUE(ExecuteScriptAndExtractBool( |
| shell(), "window.domAutomationController.send(clickCrossSiteLink());", |
| &success)); |
| EXPECT_TRUE(success); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_EQ(url, observer.last_navigation_url()); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| } |
| |
| // The RenderFrameHost should not have changed unless site-per-process is |
| // enabled. |
| if (AreAllSitesIsolatedForTesting()) { |
| EXPECT_NE(initial_rfh, |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root() |
| ->current_frame_host()); |
| } else { |
| EXPECT_EQ(initial_rfh, |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root() |
| ->current_frame_host()); |
| } |
| } |
| |
| // Ensure that browser side navigation handles navigation failures. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBrowserTest, FailedNavigation) { |
| // Perform a navigation with no live renderer. |
| { |
| TestNavigationObserver observer(shell()->web_contents()); |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| NavigateToURL(shell(), url); |
| EXPECT_EQ(url, observer.last_navigation_url()); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| } |
| |
| // Now navigate to an unreachable url. |
| { |
| TestNavigationObserver observer(shell()->web_contents()); |
| GURL error_url(embedded_test_server()->GetURL("/close-socket")); |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce(&net::URLRequestFailedJob::AddUrlHandler)); |
| NavigateToURL(shell(), error_url); |
| EXPECT_EQ(error_url, observer.last_navigation_url()); |
| NavigationEntry* entry = |
| shell()->web_contents()->GetController().GetLastCommittedEntry(); |
| EXPECT_EQ(PAGE_TYPE_ERROR, entry->GetPageType()); |
| } |
| } |
| |
| // Ensure that browser side navigation can load browser initiated navigations |
| // to view-source URLs. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBrowserTest, |
| ViewSourceNavigation_BrowserInitiated) { |
| TestNavigationObserver observer(shell()->web_contents()); |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| GURL view_source_url(content::kViewSourceScheme + std::string(":") + |
| url.spec()); |
| NavigateToURL(shell(), view_source_url); |
| EXPECT_EQ(url, observer.last_navigation_url()); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| } |
| |
| // Ensure that browser side navigation blocks content initiated navigations to |
| // view-source URLs. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBrowserTest, |
| ViewSourceNavigation_RendererInitiated) { |
| TestNavigationObserver observer(shell()->web_contents()); |
| GURL kUrl(embedded_test_server()->GetURL("/simple_links.html")); |
| NavigateToURL(shell(), kUrl); |
| EXPECT_EQ(kUrl, observer.last_navigation_url()); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| |
| std::unique_ptr<ConsoleObserverDelegate> console_delegate( |
| new ConsoleObserverDelegate( |
| shell()->web_contents(), |
| "Not allowed to load local resource: view-source:about:blank")); |
| shell()->web_contents()->SetDelegate(console_delegate.get()); |
| |
| bool success = false; |
| EXPECT_TRUE(ExecuteScriptAndExtractBool( |
| shell()->web_contents(), |
| "window.domAutomationController.send(clickViewSourceLink());", &success)); |
| EXPECT_TRUE(success); |
| console_delegate->Wait(); |
| // Original page shouldn't navigate away. |
| EXPECT_EQ(kUrl, shell()->web_contents()->GetURL()); |
| EXPECT_FALSE(shell() |
| ->web_contents() |
| ->GetController() |
| .GetLastCommittedEntry() |
| ->IsViewSourceMode()); |
| } |
| |
| // Ensure that closing a page by running its beforeunload handler doesn't hang |
| // if there's an ongoing navigation. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBrowserTest, |
| UnloadDuringNavigation) { |
| content::WindowedNotificationObserver close_observer( |
| content::NOTIFICATION_WEB_CONTENTS_DESTROYED, |
| content::Source<content::WebContents>(shell()->web_contents())); |
| GURL url("chrome://resources/css/tabs.css"); |
| NavigationHandleObserver handle_observer(shell()->web_contents(), url); |
| shell()->LoadURL(url); |
| shell()->web_contents()->DispatchBeforeUnload(); |
| close_observer.Wait(); |
| EXPECT_EQ(net::ERR_ABORTED, handle_observer.net_error_code()); |
| } |
| |
| // Ensure that the referrer of a navigation is properly sanitized. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBrowserTest, SanitizeReferrer) { |
| const GURL kInsecureUrl(embedded_test_server()->GetURL("/title1.html")); |
| const Referrer kSecureReferrer( |
| GURL("https://secure-url.com"), |
| blink::kWebReferrerPolicyNoReferrerWhenDowngrade); |
| ShellNetworkDelegate::SetCancelURLRequestWithPolicyViolatingReferrerHeader( |
| true); |
| |
| // Navigate to an insecure url with a secure referrer with a policy of no |
| // referrer on downgrades. The referrer url should be rewritten right away. |
| NavigationController::LoadURLParams load_params(kInsecureUrl); |
| load_params.referrer = kSecureReferrer; |
| TestNavigationManager manager(shell()->web_contents(), kInsecureUrl); |
| shell()->web_contents()->GetController().LoadURLWithParams(load_params); |
| EXPECT_TRUE(manager.WaitForRequestStart()); |
| |
| // The referrer should have been sanitized. |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetMainFrame() |
| ->frame_tree_node(); |
| ASSERT_TRUE(root->navigation_request()); |
| EXPECT_EQ(GURL(), |
| root->navigation_request()->navigation_handle()->GetReferrer().url); |
| |
| // The navigation should commit without being blocked. |
| EXPECT_TRUE(manager.WaitForResponse()); |
| manager.WaitForNavigationFinished(); |
| EXPECT_EQ(kInsecureUrl, shell()->web_contents()->GetLastCommittedURL()); |
| } |
| |
| // Test to verify that an exploited renderer process trying to upload a file |
| // it hasn't been explicitly granted permissions to is correctly terminated. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBrowserTest, |
| PostUploadIllegalFilePath) { |
| GURL form_url( |
| embedded_test_server()->GetURL("/form_that_posts_to_echoall.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), form_url)); |
| |
| RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>( |
| shell()->web_contents()->GetMainFrame()); |
| |
| // Prepare a file for the upload form. |
| base::ThreadRestrictions::ScopedAllowIO allow_io_for_temp_dir; |
| base::ScopedTempDir temp_dir; |
| base::FilePath file_path; |
| std::string file_content("test-file-content"); |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir.GetPath(), &file_path)); |
| ASSERT_LT( |
| 0, base::WriteFile(file_path, file_content.data(), file_content.size())); |
| |
| // Fill out the form to refer to the test file. |
| std::unique_ptr<FileChooserDelegate> delegate( |
| new FileChooserDelegate(file_path)); |
| shell()->web_contents()->SetDelegate(delegate.get()); |
| EXPECT_TRUE(ExecuteScript(shell()->web_contents(), |
| "document.getElementById('file').click();")); |
| EXPECT_TRUE(delegate->file_chosen()); |
| |
| // Ensure that the process is allowed to access to the chosen file and |
| // does not have access to the other file name. |
| EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile( |
| rfh->GetProcess()->GetID(), file_path)); |
| |
| // Revoke the access to the file and submit the form. The renderer process |
| // should be terminated. |
| RenderProcessHostKillWaiter process_kill_waiter(rfh->GetProcess()); |
| ChildProcessSecurityPolicyImpl* security_policy = |
| ChildProcessSecurityPolicyImpl::GetInstance(); |
| security_policy->RevokeAllPermissionsForFile(rfh->GetProcess()->GetID(), |
| file_path); |
| |
| // Use ExecuteScriptAndExtractBool and respond back to the browser process |
| // before doing the actual submission. This will ensure that the process |
| // termination is guaranteed to arrive after the response from the executed |
| // JavaScript. |
| bool result = false; |
| EXPECT_TRUE(ExecuteScriptAndExtractBool( |
| shell(), |
| "window.domAutomationController.send(true);" |
| "document.getElementById('file-form').submit();", |
| &result)); |
| EXPECT_TRUE(result); |
| EXPECT_EQ(bad_message::RFH_ILLEGAL_UPLOAD_PARAMS, process_kill_waiter.Wait()); |
| } |
| |
| // Test case to verify that redirects to data: URLs are properly disallowed, |
| // even when invoked through a reload. |
| // See https://crbug.com/723796. |
| // |
| // Note: This is PlzNavigate specific test, as the behavior of reloads in the |
| // non-PlzNavigate path differs. The WebURLRequest for the reload is generated |
| // based on Blink's state instead of the history state in the browser process, |
| // which ends up loading the originally blocked URL. With PlzNavigate, the |
| // reload uses the NavigationEntry state to create a navigation and commit it. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBrowserTest, |
| VerifyBlockedErrorPageURL_Reload) { |
| NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>( |
| shell()->web_contents()->GetController()); |
| |
| GURL start_url(embedded_test_server()->GetURL("/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), start_url)); |
| EXPECT_EQ(0, controller.GetLastCommittedEntryIndex()); |
| |
| // Navigate to an URL, which redirects to a data: URL, since it is an |
| // unsafe redirect and will result in a blocked navigation and error page. |
| GURL redirect_to_blank_url( |
| embedded_test_server()->GetURL("/server-redirect?data:text/html,Hello!")); |
| EXPECT_FALSE(NavigateToURL(shell(), redirect_to_blank_url)); |
| EXPECT_EQ(1, controller.GetLastCommittedEntryIndex()); |
| EXPECT_EQ(PAGE_TYPE_ERROR, controller.GetLastCommittedEntry()->GetPageType()); |
| |
| TestNavigationObserver reload_observer(shell()->web_contents()); |
| EXPECT_TRUE(ExecuteScript(shell(), "location.reload()")); |
| reload_observer.Wait(); |
| |
| // The expectation is that the blocked URL is present in the NavigationEntry, |
| // and shows up in both GetURL and GetVirtualURL. |
| EXPECT_EQ(1, controller.GetLastCommittedEntryIndex()); |
| EXPECT_FALSE( |
| controller.GetLastCommittedEntry()->GetURL().SchemeIs(url::kDataScheme)); |
| EXPECT_EQ(redirect_to_blank_url, |
| controller.GetLastCommittedEntry()->GetURL()); |
| EXPECT_EQ(redirect_to_blank_url, |
| controller.GetLastCommittedEntry()->GetVirtualURL()); |
| } |
| |
| class BrowserSideNavigationBrowserDisableWebSecurityTest |
| : public BrowserSideNavigationBrowserTest { |
| public: |
| BrowserSideNavigationBrowserDisableWebSecurityTest() {} |
| |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Simulate a compromised renderer, otherwise the cross-origin request to |
| // file: is blocked. |
| command_line->AppendSwitch(switches::kDisableWebSecurity); |
| BrowserSideNavigationBrowserTest::SetUpCommandLine(command_line); |
| } |
| }; |
| |
| // Test to verify that an exploited renderer process trying to specify a |
| // non-empty URL for base_url_for_data_url on navigation is correctly |
| // terminated. |
| // TODO(nasko): This test case belongs better in |
| // security_exploit_browsertest.cc, so move it there once PlzNavigate is on |
| // by default. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBrowserDisableWebSecurityTest, |
| ValidateBaseUrlForDataUrl) { |
| GURL start_url(embedded_test_server()->GetURL("/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), start_url)); |
| |
| RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>( |
| shell()->web_contents()->GetMainFrame()); |
| |
| GURL data_url("data:text/html,foo"); |
| base::FilePath file_path = GetTestFilePath("", "simple_page.html"); |
| GURL file_url = net::FilePathToFileURL(file_path); |
| |
| // To get around DataUrlNavigationThrottle. Other attempts at getting around |
| // it don't work, i.e.: |
| // -if the request is made in a child frame then the frame is torn down |
| // immediately on process killing so the navigation doesn't complete |
| // -if it's classified as same document, then a DCHECK in |
| // NavigationRequest::CreateRendererInitiated fires |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| features::kAllowContentInitiatedDataUrlNavigations); |
| // Setup a BeginNavigate IPC with non-empty base_url_for_data_url. |
| CommonNavigationParams common_params( |
| data_url, Referrer(), ui::PAGE_TRANSITION_LINK, |
| FrameMsg_Navigate_Type::DIFFERENT_DOCUMENT, true /* allow_download */, |
| false /* should_replace_current_entry */, |
| file_url, /* base_url_for_data_url */ |
| GURL() /* history_url_for_data_url */, PREVIEWS_UNSPECIFIED, |
| base::TimeTicks::Now() /* navigation_start */, "GET", |
| nullptr /* post_data */, base::Optional<SourceLocation>(), |
| CSPDisposition::CHECK, false /* started_from_context_menu */, |
| false /* has_user_gesture */, |
| std::vector<ContentSecurityPolicy>() /* initiator_csp */, |
| CSPSource() /* initiator_self_source */); |
| mojom::BeginNavigationParamsPtr begin_params = |
| mojom::BeginNavigationParams::New( |
| std::string() /* headers */, net::LOAD_NORMAL, |
| false /* skip_service_worker */, REQUEST_CONTEXT_TYPE_LOCATION, |
| blink::WebMixedContentContextType::kBlockable, |
| false /* is_form_submission */, GURL() /* searchable_form_url */, |
| std::string() /* searchable_form_encoding */, |
| url::Origin::Create(data_url), GURL() /* client_side_redirect_url */, |
| base::nullopt /* devtools_initiator_info */); |
| |
| // Receiving the invalid IPC message should lead to renderer process |
| // termination. |
| RenderProcessHostKillWaiter process_kill_waiter(rfh->GetProcess()); |
| |
| mojom::NavigationClientAssociatedPtr navigation_client; |
| if (IsPerNavigationMojoInterfaceEnabled()) { |
| auto navigation_client_request = |
| mojo::MakeRequestAssociatedWithDedicatedPipe(&navigation_client); |
| rfh->frame_host_binding_for_testing().impl()->BeginNavigation( |
| common_params, std::move(begin_params), nullptr, |
| navigation_client.PassInterface()); |
| } else { |
| rfh->frame_host_binding_for_testing().impl()->BeginNavigation( |
| common_params, std::move(begin_params), nullptr, nullptr); |
| } |
| EXPECT_EQ(bad_message::RFH_BASE_URL_FOR_DATA_URL_SPECIFIED, |
| process_kill_waiter.Wait()); |
| |
| EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile( |
| rfh->GetProcess()->GetID(), file_path)); |
| |
| // Reload the page to create another renderer process. |
| TestNavigationObserver tab_observer(shell()->web_contents(), 1); |
| shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false); |
| tab_observer.Wait(); |
| |
| // Make an XHR request to check if the page has access. |
| std::string script = base::StringPrintf( |
| "var xhr = new XMLHttpRequest()\n" |
| "xhr.open('GET', '%s', false);\n" |
| "xhr.send();\n" |
| "window.domAutomationController.send(xhr.responseText);", |
| file_url.spec().c_str()); |
| std::string result; |
| EXPECT_TRUE( |
| ExecuteScriptAndExtractString(shell()->web_contents(), script, &result)); |
| EXPECT_TRUE(result.empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBrowserTest, BackFollowedByReload) { |
| // First, make two history entries. |
| GURL url1(embedded_test_server()->GetURL("/title1.html")); |
| GURL url2(embedded_test_server()->GetURL("/title2.html")); |
| NavigateToURL(shell(), url1); |
| NavigateToURL(shell(), url2); |
| |
| // Then execute a back navigation in Javascript followed by a reload. |
| TestNavigationObserver navigation_observer(shell()->web_contents()); |
| EXPECT_TRUE(ExecuteScript(shell()->web_contents(), |
| "history.back(); location.reload();")); |
| navigation_observer.Wait(); |
| |
| // The reload should have cancelled the back navigation, and the last |
| // committed URL should still be the second URL. |
| EXPECT_EQ(url2, shell()->web_contents()->GetLastCommittedURL()); |
| } |
| |
| // Test that a navigation response can be entirely fetched, even after the |
| // NavigationURLLoader has been deleted. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBaseBrowserTest, |
| FetchResponseAfterNavigationURLLoaderDeleted) { |
| net::test_server::ControllableHttpResponse response(embedded_test_server(), |
| "/main_document"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Load a new document. |
| GURL url(embedded_test_server()->GetURL("/main_document")); |
| TestNavigationManager navigation_manager(shell()->web_contents(), url); |
| shell()->LoadURL(url); |
| |
| // The navigation starts. |
| EXPECT_TRUE(navigation_manager.WaitForRequestStart()); |
| navigation_manager.ResumeNavigation(); |
| |
| // A NavigationRequest exists at this point. |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetMainFrame() |
| ->frame_tree_node(); |
| EXPECT_TRUE(root->navigation_request()); |
| |
| // The response's headers are received. |
| response.WaitForRequest(); |
| response.Send( |
| "HTTP/1.1 200 OK\r\n" |
| "Content-Type: text/html; charset=utf-8\r\n" |
| "\r\n" |
| "..."); |
| EXPECT_TRUE(navigation_manager.WaitForResponse()); |
| navigation_manager.ResumeNavigation(); |
| |
| // The renderer commits the navigation and the browser deletes its |
| // NavigationRequest. |
| navigation_manager.WaitForNavigationFinished(); |
| EXPECT_FALSE(root->navigation_request()); |
| |
| // The NavigationURLLoader has been deleted by now. Check that the renderer |
| // can still receive more bytes. |
| DOMMessageQueue dom_message_queue(WebContents::FromRenderFrameHost( |
| shell()->web_contents()->GetMainFrame())); |
| response.Send( |
| "<script>window.domAutomationController.send('done');</script>"); |
| std::string done; |
| EXPECT_TRUE(dom_message_queue.WaitForMessage(&done)); |
| EXPECT_EQ("\"done\"", done); |
| } |
| |
| // Navigation are started in the browser process. After the headers are |
| // received, the URLLoaderClient is transfered from the browser process to the |
| // renderer process. This test ensures that when the the URLLoader is deleted |
| // (in the browser process), the URLLoaderClient (in the renderer process) stops |
| // properly. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBaseBrowserTest, |
| CancelRequestAfterReadyToCommit) { |
| // This test cancels the request using the ResourceDispatchHost. With the |
| // NetworkService, it is not used so the request is not canceled. |
| // TODO(arthursonzogni): Find a way to cancel a request from the browser |
| // with the NetworkService. |
| if (base::FeatureList::IsEnabled(network::features::kNetworkService)) |
| return; |
| |
| net::test_server::ControllableHttpResponse response(embedded_test_server(), |
| "/main_document"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // 1) Load a new document. Commit the navigation but do not send the full |
| // response's body. |
| GURL url(embedded_test_server()->GetURL("/main_document")); |
| TestNavigationManager navigation_manager(shell()->web_contents(), url); |
| shell()->LoadURL(url); |
| |
| // Let the navigation start. |
| EXPECT_TRUE(navigation_manager.WaitForRequestStart()); |
| navigation_manager.ResumeNavigation(); |
| |
| // The server sends the first part of the response and waits. |
| response.WaitForRequest(); |
| response.Send( |
| "HTTP/1.1 200 OK\r\n" |
| "Content-Type: text/html; charset=utf-8\r\n" |
| "\r\n" |
| "<html><body> ... "); |
| |
| EXPECT_TRUE(navigation_manager.WaitForResponse()); |
| GlobalRequestID global_id = |
| navigation_manager.GetNavigationHandle()->GetGlobalRequestID(); |
| navigation_manager.ResumeNavigation(); |
| |
| // The navigation commits successfully. The renderer is waiting for the |
| // response's body. |
| navigation_manager.WaitForNavigationFinished(); |
| |
| // 2) The ResourceDispatcherHost cancels the request. |
| auto cancel_request = [](GlobalRequestID global_id) { |
| ResourceDispatcherHostImpl* rdh = |
| static_cast<ResourceDispatcherHostImpl*>(ResourceDispatcherHost::Get()); |
| rdh->CancelRequest(global_id.child_id, global_id.request_id); |
| }; |
| BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
| base::BindOnce(cancel_request, global_id)); |
| |
| // 3) Check that the load stops properly. |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| } |
| |
| // Data URLs can have a reference fragment like any other URLs. This test makes |
| // sure it is taken into account. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBrowserTest, |
| DataURLWithReferenceFragment) { |
| GURL url("data:text/html,body#foo"); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| std::string body; |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| shell(), |
| "window.domAutomationController.send(document.body.textContent);", |
| &body)); |
| // TODO(arthursonzogni): This is wrong. The correct value for |body| is |
| // "body", not "body#foo". See https://crbug.com/123004. |
| EXPECT_EQ("body#foo", body); |
| |
| std::string reference_fragment; |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| shell(), "window.domAutomationController.send(location.hash);", |
| &reference_fragment)); |
| EXPECT_EQ("#foo", reference_fragment); |
| } |
| |
| // Regression test for https://crbug.com/796561. |
| // 1) Start on a document with history.length == 1. |
| // 2) Create an iframe and call history.pushState at the same time. |
| // 3) history.back() must work. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBrowserTest, |
| IframeAndPushStateSimultaneously) { |
| GURL main_url = embedded_test_server()->GetURL("/simple_page.html"); |
| GURL iframe_url = embedded_test_server()->GetURL("/hello.html"); |
| |
| // 1) Start on a new document such that history.length == 1. |
| { |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| int history_length; |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| shell(), "window.domAutomationController.send(history.length)", |
| &history_length)); |
| EXPECT_EQ(1, history_length); |
| } |
| |
| // 2) Create an iframe and call history.pushState at the same time. |
| { |
| TestNavigationManager iframe_navigation(shell()->web_contents(), |
| iframe_url); |
| ExecuteScriptAsync(shell(), |
| "let iframe = document.createElement('iframe');" |
| "iframe.src = '/hello.html';" |
| "document.body.appendChild(iframe);"); |
| EXPECT_TRUE(iframe_navigation.WaitForRequestStart()); |
| |
| // The iframe navigation is paused. In the meantime, a pushState navigation |
| // begins and ends. |
| TestNavigationManager push_state_navigation(shell()->web_contents(), |
| main_url); |
| ExecuteScriptAsync(shell(), "window.history.pushState({}, null);"); |
| push_state_navigation.WaitForNavigationFinished(); |
| |
| // The iframe navigation is resumed. |
| iframe_navigation.WaitForNavigationFinished(); |
| } |
| |
| // 3) history.back() must work. |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents()); |
| EXPECT_TRUE(ExecuteScript(shell()->web_contents(), "history.back();")); |
| navigation_observer.Wait(); |
| } |
| } |
| |
| // Regression test for https://crbug.com/260144 |
| // Back/Forward navigation in an iframe must not stop ongoing XHR. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBaseBrowserTest, |
| IframeNavigationsDoNotStopXHR) { |
| // A response for the XHR request. It will be delayed until the end of all the |
| // navigations. |
| net::test_server::ControllableHttpResponse xhr_response( |
| embedded_test_server(), "/xhr"); |
| EXPECT_TRUE(embedded_test_server()->Start()); |
| |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| NavigateToURL(shell(), url); |
| |
| DOMMessageQueue dom_message_queue(WebContents::FromRenderFrameHost( |
| shell()->web_contents()->GetMainFrame())); |
| std::string message; |
| |
| // 1) Send an XHR. |
| ExecuteScriptAsync( |
| shell(), |
| "let xhr = new XMLHttpRequest();" |
| "xhr.open('GET', './xhr', true);" |
| "xhr.onabort = () => window.domAutomationController.send('xhr.onabort');" |
| "xhr.onerror = () => window.domAutomationController.send('xhr.onerror');" |
| "xhr.onload = () => window.domAutomationController.send('xhr.onload');" |
| "xhr.send();"); |
| |
| // 2) Create an iframe and wait for the initial load. |
| { |
| ExecuteScriptAsync( |
| shell(), |
| "var iframe = document.createElement('iframe');" |
| "iframe.src = './title1.html';" |
| "iframe.onload = function() {" |
| " window.domAutomationController.send('iframe.onload');" |
| "};" |
| "document.body.appendChild(iframe);"); |
| |
| EXPECT_TRUE(dom_message_queue.WaitForMessage(&message)); |
| EXPECT_EQ("\"iframe.onload\"", message); |
| } |
| |
| // 3) Navigate the iframe elsewhere. |
| { |
| ExecuteScriptAsync(shell(), |
| "var iframe = document.querySelector('iframe');" |
| "iframe.src = './title2.html';"); |
| |
| EXPECT_TRUE(dom_message_queue.WaitForMessage(&message)); |
| EXPECT_EQ("\"iframe.onload\"", message); |
| } |
| |
| // 4) history.back() in the iframe. |
| { |
| ExecuteScriptAsync(shell(), |
| "var iframe = document.querySelector('iframe');" |
| "iframe.contentWindow.history.back()"); |
| |
| EXPECT_TRUE(dom_message_queue.WaitForMessage(&message)); |
| EXPECT_EQ("\"iframe.onload\"", message); |
| } |
| |
| // 5) history.forward() in the iframe. |
| { |
| ExecuteScriptAsync(shell(), |
| "var iframe = document.querySelector('iframe');" |
| "iframe.contentWindow.history.forward()"); |
| |
| EXPECT_TRUE(dom_message_queue.WaitForMessage(&message)); |
| EXPECT_EQ("\"iframe.onload\"", message); |
| } |
| |
| // 6) Wait for the XHR. |
| { |
| xhr_response.WaitForRequest(); |
| xhr_response.Send( |
| "HTTP/1.1 200 OK\r\n" |
| "Connection: close\r\n" |
| "Content-Length: 2\r\n" |
| "Content-Type: text/plain; charset=utf-8\r\n" |
| "\r\n" |
| "OK"); |
| xhr_response.Done(); |
| EXPECT_TRUE(dom_message_queue.WaitForMessage(&message)); |
| EXPECT_EQ("\"xhr.onload\"", message); |
| } |
| |
| EXPECT_FALSE(dom_message_queue.PopMessage(&message)); |
| } |
| |
| // Regression test for https://crbug.com/856396. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBaseBrowserTest, |
| ReplacingDocumentLoaderFiresLoadEvent) { |
| net::test_server::ControllableHttpResponse main_document_response( |
| embedded_test_server(), "/main_document"); |
| net::test_server::ControllableHttpResponse iframe_response( |
| embedded_test_server(), "/iframe"); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // 1) Load the main document. |
| shell()->LoadURL(embedded_test_server()->GetURL("/main_document")); |
| main_document_response.WaitForRequest(); |
| main_document_response.Send( |
| "HTTP/1.1 200 OK\r\n" |
| "Content-Type: text/html; charset=utf-8\r\n" |
| "\r\n" |
| "<script>" |
| " var detach_iframe = function() {" |
| " var iframe = document.querySelector('iframe');" |
| " iframe.parentNode.removeChild(iframe);" |
| " }" |
| "</script>" |
| "<body onload='detach_iframe()'>" |
| " <iframe src='/iframe'></iframe>" |
| "</body>"); |
| main_document_response.Done(); |
| |
| // 2) The iframe starts to load, but the server only have time to send the |
| // response's headers, not the response's body. A provisional DocumentLoader |
| // will be created in the renderer process, but it will never commit. |
| iframe_response.WaitForRequest(); |
| iframe_response.Send( |
| "HTTP/1.1 200 OK\r\n" |
| "Content-Type: text/html; charset=utf-8\r\n" |
| "\r\n"); |
| |
| // 3) In the meantime the iframe navigates elsewhere. It causes the previous |
| // provisional DocumentLoader to be replaced by the new one. Removing it may |
| // trigger the 'load' event and delete the iframe. |
| EXPECT_TRUE(ExecuteScript( |
| shell(), "document.querySelector('iframe').src = '/title1.html'")); |
| |
| // Wait for the iframe to be deleted and check the renderer process is still |
| // alive. |
| int iframe_count = 1; |
| while (iframe_count != 0) { |
| ASSERT_TRUE(ExecuteScriptAndExtractInt( |
| shell(), |
| "var iframe_count = document.getElementsByTagName('iframe').length;" |
| "window.domAutomationController.send(iframe_count);", |
| &iframe_count)); |
| } |
| } |
| |
| class NavigationDownloadBrowserTest |
| : public BrowserSideNavigationBaseBrowserTest { |
| protected: |
| void SetUpOnMainThread() override { |
| BrowserSideNavigationBaseBrowserTest::SetUpOnMainThread(); |
| |
| // Set up a test download directory, in order to prevent prompting for |
| // handling downloads. |
| ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir()); |
| ShellDownloadManagerDelegate* delegate = |
| static_cast<ShellDownloadManagerDelegate*>( |
| shell() |
| ->web_contents() |
| ->GetBrowserContext() |
| ->GetDownloadManagerDelegate()); |
| delegate->SetDownloadBehaviorForTesting(downloads_directory_.GetPath()); |
| } |
| |
| private: |
| base::ScopedTempDir downloads_directory_; |
| }; |
| |
| // Regression test for https://crbug.com/855033 |
| // 1) A page contains many scripts and DOM elements. It forces the parser to |
| // yield CPU to other tasks. That way the response body's data are not fully |
| // read when URLLoaderClient::OnComplete(..) is received. |
| // 2) A script makes the document navigates elsewhere while it is still loading. |
| // It cancels the parser of the current document. Due to a bug, the document |
| // loader was not marked to be 'loaded' at this step. |
| // 3) The request for the new navigation starts and it turns out it is a |
| // download. The navigation is dropped. |
| // 4) There are no more possibilities for DidStopLoading() to be sent. |
| IN_PROC_BROWSER_TEST_F(NavigationDownloadBrowserTest, |
| StopLoadingAfterDroppedNavigation) { |
| net::test_server::ControllableHttpResponse main_response( |
| embedded_test_server(), "/main"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL main_url(embedded_test_server()->GetURL("/main")); |
| GURL download_url(embedded_test_server()->GetURL("/download-test1.lib")); |
| |
| shell()->LoadURL(main_url); |
| main_response.WaitForRequest(); |
| std::string headers = |
| "HTTP/1.1 200 OK\r\n" |
| "Content-Type: text/html; charset=utf-8\r\n" |
| "\r\n"; |
| |
| // Craft special HTML to make the blink::DocumentParser yield CPU to other |
| // tasks. The goal is to ensure the response body datapipe is not fully read |
| // when URLLoaderClient::OnComplete() is called. |
| // This relies on the HTMLParserScheduler::ShouldYield() heuristics. |
| std::string mix_of_script_and_div = "<script></script><div></div>"; |
| for (size_t i = 0; i < 10; ++i) { |
| mix_of_script_and_div += mix_of_script_and_div; // Exponential growth. |
| } |
| |
| std::string navigate_to_download = |
| "<script>location.href='" + download_url.spec() + "'</script>"; |
| |
| main_response.Send(headers + navigate_to_download + mix_of_script_and_div); |
| main_response.Done(); |
| |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| } |
| |
| // This test reproduces the following race condition: |
| // 1) A first navigation starts, the headers are received, the navigation |
| // reaches ready-to-commit. It is sent to the renderer to be committed. |
| // 2) In the meantime, a second navigation reaches ready-to-commit in the |
| // browser. |
| // 3) Before the renderer gets notified of the new navigation, the |
| // first navigation is committed. |
| // 4) The browser gets notified of the commit of the first navigation. This |
| // should not destroy the NavigationRequest corresponding to the second |
| // navigation. |
| IN_PROC_BROWSER_TEST_F(BrowserSideNavigationBrowserTest, |
| RaceNewNavigationCommitWhileOldOneFinishesLoading) { |
| // Start the test with an initial document. |
| GURL main_url(embedded_test_server()->GetURL("/simple_page.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| RenderFrameHostImpl* render_frame_host = static_cast<RenderFrameHostImpl*>( |
| shell()->web_contents()->GetMainFrame()); |
| |
| NavigationRecorder recorder(shell()->web_contents()); |
| // Note: These two pages contain an image that will never load. The goal is to |
| // prevent RenderFrameHostImpl::DidStopLoading() to be called since it will |
| // cancel any pending navigation. |
| GURL page_1(embedded_test_server()->GetURL("/infinite_load_1.html")); |
| GURL page_2(embedded_test_server()->GetURL("/infinite_load_2.html")); |
| // Intercept and cancel any FrameMsgHost_DidCommitProvisionalLoad events. |
| InterceptAndCancelDidCommitProvisionalLoad interceptor( |
| shell()->web_contents()); |
| |
| // 1) Navigate to page_1. |
| shell()->LoadURL(page_1); |
| |
| // 2) The browser receives the response's headers. The navigation commits in |
| // the browser. |
| recorder.WaitForEvents(2); |
| EXPECT_EQ(2u, recorder.records().size()); |
| EXPECT_STREQ("start /infinite_load_1.html", recorder.records()[0].c_str()); |
| EXPECT_STREQ("ready-to-commit /infinite_load_1.html", |
| recorder.records()[1].c_str()); |
| |
| // 3) Wait for the renderer to receive the response's body, but do not notify |
| // the browser of it right now. It is delayed in 6). |
| interceptor.Wait(1); |
| EXPECT_EQ(1u, interceptor.intercepted_messages().size()); |
| |
| // 4) In the meantime, the browser starts a navigation to page_2. |
| shell()->LoadURL(page_2); |
| |
| // 5) The response's headers are received, the navigation reaches |
| // ready-to-commit in the browser. This should not delete the ongoing |
| // NavigationRequest. |
| recorder.WaitForEvents(4); |
| EXPECT_EQ(4u, recorder.records().size()); |
| EXPECT_STREQ("start /infinite_load_2.html", recorder.records()[2].c_str()); |
| EXPECT_STREQ("ready-to-commit /infinite_load_2.html", |
| recorder.records()[3].c_str()); |
| |
| // 6) The browser receives the first DidCommitProvisionalLoad message. This |
| // should not delete the second navigation. This is the end of the first |
| // navigation. |
| render_frame_host->DidCommitProvisionalLoadForTesting( |
| std::make_unique<::FrameHostMsg_DidCommitProvisionalLoad_Params>( |
| interceptor.intercepted_messages()[0]), |
| std::move(interceptor.intercepted_requests()[0])); |
| recorder.WaitForEvents(5); |
| EXPECT_EQ(5u, recorder.records().size()); |
| EXPECT_STREQ("did-commit /infinite_load_1.html", |
| recorder.records()[4].c_str()); |
| |
| // 7) Wait for the renderer to receive the second response's body. This is the |
| // end of the second navigation. |
| interceptor.Wait(2); |
| EXPECT_EQ(2u, interceptor.intercepted_messages().size()); |
| render_frame_host->DidCommitProvisionalLoadForTesting( |
| std::make_unique<::FrameHostMsg_DidCommitProvisionalLoad_Params>( |
| interceptor.intercepted_messages()[1]), |
| std::move(interceptor.intercepted_requests()[1])); |
| recorder.WaitForEvents(6); |
| EXPECT_EQ(6u, recorder.records().size()); |
| EXPECT_STREQ("did-commit /infinite_load_2.html", |
| recorder.records()[5].c_str()); |
| } |
| |
| } // namespace content |