| // Copyright 2016 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| |
| #include "base/command_line.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "build/build_config.h" |
| #include "content/browser/process_lock.h" |
| #include "content/browser/renderer_host/debug_urls.h" |
| #include "content/browser/renderer_host/navigation_controller_impl.h" |
| #include "content/browser/renderer_host/navigation_request.h" |
| #include "content/browser/renderer_host/render_frame_host_impl.h" |
| #include "content/browser/runtime_feature_state/runtime_feature_state_document_data.h" |
| #include "content/browser/site_instance_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/common/content_navigation_policy.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/navigation_throttle.h" |
| #include "content/public/browser/site_isolation_policy.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/common/bindings_policy.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/result_codes.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/test/back_forward_cache_util.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_content_browser_client.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/public/test/fenced_frame_test_util.h" |
| #include "content/public/test/mock_web_contents_observer.h" |
| #include "content/public/test/navigation_handle_observer.h" |
| #include "content/public/test/prerender_test_util.h" |
| #include "content/public/test/test_frame_navigation_observer.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/public/test/test_service.mojom.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_browser_context.h" |
| #include "content/shell/browser/shell_download_manager_delegate.h" |
| #include "content/test/content_browser_test_utils_internal.h" |
| #include "content/test/mock_commit_deferring_condition.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/controllable_http_response.h" |
| #include "net/test/embedded_test_server/default_handlers.h" |
| #include "net/test/url_request/url_request_failed_job.h" |
| #include "services/network/public/cpp/features.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/blink/public/common/chrome_debug_urls.h" |
| #include "third_party/blink/public/common/runtime_feature_state/runtime_feature_state_context.h" |
| #include "third_party/blink/public/common/runtime_feature_state/runtime_feature_state_read_context.h" |
| #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h" |
| #include "third_party/blink/public/mojom/runtime_feature_state/runtime_feature.mojom.h" |
| #include "ui/base/page_transition_types.h" |
| #include "url/scheme_host_port.h" |
| #include "url/url_constants.h" |
| |
| namespace { |
| |
| // Text to place in an HTML body. Should not contain any markup. |
| const char kBodyTextContent[] = "some plain text content"; |
| |
| } // namespace |
| |
| namespace content { |
| |
| namespace { |
| |
| // A test NavigationThrottle that will return pre-determined checks and run |
| // callbacks when the various NavigationThrottle methods are called. It is |
| // not instantiated directly but through a TestNavigationThrottleInstaller. |
| class TestNavigationThrottle : public NavigationThrottle { |
| public: |
| TestNavigationThrottle( |
| NavigationHandle* handle, |
| NavigationThrottle::ThrottleCheckResult will_start_result, |
| NavigationThrottle::ThrottleCheckResult will_redirect_result, |
| NavigationThrottle::ThrottleCheckResult will_fail_result, |
| NavigationThrottle::ThrottleCheckResult will_process_result, |
| NavigationThrottle::ThrottleCheckResult |
| will_commit_without_url_loader_result, |
| base::OnceClosure did_call_will_start, |
| base::OnceClosure did_call_will_redirect, |
| base::OnceClosure did_call_will_fail, |
| base::OnceClosure did_call_will_process, |
| base::OnceClosure did_call_will_commit_without_url_loader) |
| : NavigationThrottle(handle), |
| will_start_result_(will_start_result), |
| will_redirect_result_(will_redirect_result), |
| will_fail_result_(will_fail_result), |
| will_process_result_(will_process_result), |
| will_commit_without_url_loader_result_( |
| will_commit_without_url_loader_result), |
| did_call_will_start_(std::move(did_call_will_start)), |
| did_call_will_redirect_(std::move(did_call_will_redirect)), |
| did_call_will_fail_(std::move(did_call_will_fail)), |
| did_call_will_process_(std::move(did_call_will_process)), |
| did_call_will_commit_without_url_loader_( |
| std::move(did_call_will_commit_without_url_loader)) {} |
| ~TestNavigationThrottle() override = default; |
| |
| const char* GetNameForLogging() override { return "TestNavigationThrottle"; } |
| |
| blink::mojom::RequestContextType request_context_type() { |
| return request_context_type_; |
| } |
| |
| // Expose Resume and Cancel to the installer. |
| void ResumeNavigation() { Resume(); } |
| |
| void CancelNavigation(NavigationThrottle::ThrottleCheckResult result) { |
| CancelDeferredNavigation(result); |
| } |
| |
| private: |
| // NavigationThrottle implementation. |
| NavigationThrottle::ThrottleCheckResult WillStartRequest() override { |
| NavigationRequest* navigation_request = |
| NavigationRequest::From(navigation_handle()); |
| CHECK_NE(blink::mojom::RequestContextType::UNSPECIFIED, |
| navigation_request->request_context_type()); |
| request_context_type_ = navigation_request->request_context_type(); |
| |
| GetUIThreadTaskRunner({})->PostTask(FROM_HERE, |
| std::move(did_call_will_start_)); |
| return will_start_result_; |
| } |
| |
| NavigationThrottle::ThrottleCheckResult WillRedirectRequest() override { |
| NavigationRequest* navigation_request = |
| NavigationRequest::From(navigation_handle()); |
| CHECK_EQ(request_context_type_, navigation_request->request_context_type()); |
| |
| GetUIThreadTaskRunner({})->PostTask(FROM_HERE, |
| std::move(did_call_will_redirect_)); |
| return will_redirect_result_; |
| } |
| |
| NavigationThrottle::ThrottleCheckResult WillFailRequest() override { |
| NavigationRequest* navigation_request = |
| NavigationRequest::From(navigation_handle()); |
| CHECK_EQ(request_context_type_, navigation_request->request_context_type()); |
| |
| GetUIThreadTaskRunner({})->PostTask(FROM_HERE, |
| std::move(did_call_will_fail_)); |
| return will_fail_result_; |
| } |
| |
| NavigationThrottle::ThrottleCheckResult WillProcessResponse() override { |
| NavigationRequest* navigation_request = |
| NavigationRequest::From(navigation_handle()); |
| CHECK_EQ(request_context_type_, navigation_request->request_context_type()); |
| |
| GetUIThreadTaskRunner({})->PostTask(FROM_HERE, |
| std::move(did_call_will_process_)); |
| return will_process_result_; |
| } |
| |
| NavigationThrottle::ThrottleCheckResult WillCommitWithoutUrlLoader() |
| override { |
| NavigationRequest* navigation_request = |
| NavigationRequest::From(navigation_handle()); |
| CHECK_NE(blink::mojom::RequestContextType::UNSPECIFIED, |
| navigation_request->request_context_type()); |
| request_context_type_ = navigation_request->request_context_type(); |
| |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, std::move(did_call_will_commit_without_url_loader_)); |
| return will_commit_without_url_loader_result_; |
| } |
| |
| NavigationThrottle::ThrottleCheckResult will_start_result_; |
| NavigationThrottle::ThrottleCheckResult will_redirect_result_; |
| NavigationThrottle::ThrottleCheckResult will_fail_result_; |
| NavigationThrottle::ThrottleCheckResult will_process_result_; |
| NavigationThrottle::ThrottleCheckResult |
| will_commit_without_url_loader_result_; |
| base::OnceClosure did_call_will_start_; |
| base::OnceClosure did_call_will_redirect_; |
| base::OnceClosure did_call_will_fail_; |
| base::OnceClosure did_call_will_process_; |
| base::OnceClosure did_call_will_commit_without_url_loader_; |
| blink::mojom::RequestContextType request_context_type_ = |
| blink::mojom::RequestContextType::UNSPECIFIED; |
| }; |
| |
| // Installs a TestNavigationThrottle either on all following requests or on |
| // requests with an expected starting URL, and allows waiting for various |
| // NavigationThrottle related events. Waiting works only for the immediately |
| // next navigation. New instances are needed to wait for further navigations. |
| class TestNavigationThrottleInstaller : public WebContentsObserver { |
| public: |
| enum Method { |
| WILL_START_REQUEST, |
| WILL_REDIRECT_REQUEST, |
| WILL_FAIL_REQUEST, |
| WILL_PROCESS_RESPONSE, |
| WILL_COMMIT_WITHOUT_URL_LOADER, |
| }; |
| |
| TestNavigationThrottleInstaller( |
| WebContents* web_contents, |
| NavigationThrottle::ThrottleCheckResult will_start_result, |
| NavigationThrottle::ThrottleCheckResult will_redirect_result, |
| NavigationThrottle::ThrottleCheckResult will_fail_result, |
| NavigationThrottle::ThrottleCheckResult will_process_result, |
| NavigationThrottle::ThrottleCheckResult |
| will_commit_without_url_loader_result, |
| const GURL& expected_start_url = GURL()) |
| : WebContentsObserver(web_contents), |
| will_start_result_(will_start_result), |
| will_redirect_result_(will_redirect_result), |
| will_fail_result_(will_fail_result), |
| will_process_result_(will_process_result), |
| will_commit_without_url_loader_result_( |
| will_commit_without_url_loader_result), |
| expected_start_url_(expected_start_url) {} |
| ~TestNavigationThrottleInstaller() override = default; |
| |
| // Installs a TestNavigationThrottle whose |method| method will return |
| // |result|. All other methods will return NavigationThrottle::PROCEED. |
| static std::unique_ptr<TestNavigationThrottleInstaller> CreateForMethod( |
| WebContents* web_contents, |
| Method method, |
| NavigationThrottle::ThrottleCheckResult result) { |
| NavigationThrottle::ThrottleCheckResult will_start_result = |
| NavigationThrottle::PROCEED; |
| auto will_redirect_result = will_start_result; |
| auto will_fail_result = will_start_result; |
| auto will_process_result = will_start_result; |
| auto will_commit_without_url_loader_result = will_start_result; |
| |
| switch (method) { |
| case WILL_START_REQUEST: |
| will_start_result = result; |
| break; |
| case WILL_REDIRECT_REQUEST: |
| will_redirect_result = result; |
| break; |
| case WILL_FAIL_REQUEST: |
| will_fail_result = result; |
| break; |
| case WILL_PROCESS_RESPONSE: |
| will_process_result = result; |
| break; |
| case WILL_COMMIT_WITHOUT_URL_LOADER: |
| will_commit_without_url_loader_result = result; |
| break; |
| } |
| |
| return std::make_unique<TestNavigationThrottleInstaller>( |
| web_contents, will_start_result, will_redirect_result, will_fail_result, |
| will_process_result, will_commit_without_url_loader_result); |
| } |
| |
| TestNavigationThrottle* navigation_throttle() { return navigation_throttle_; } |
| |
| void WaitForThrottleWillStart() { |
| if (will_start_called_) |
| return; |
| will_start_loop_runner_ = new MessageLoopRunner(); |
| will_start_loop_runner_->Run(); |
| will_start_loop_runner_ = nullptr; |
| } |
| |
| void WaitForThrottleWillRedirect() { |
| if (will_redirect_called_) |
| return; |
| will_redirect_loop_runner_ = new MessageLoopRunner(); |
| will_redirect_loop_runner_->Run(); |
| will_redirect_loop_runner_ = nullptr; |
| } |
| |
| void WaitForThrottleWillFail() { |
| if (will_fail_called_) |
| return; |
| will_fail_loop_runner_ = new MessageLoopRunner(); |
| will_fail_loop_runner_->Run(); |
| will_fail_loop_runner_ = nullptr; |
| } |
| |
| void WaitForThrottleWillProcess() { |
| if (will_process_called_) |
| return; |
| will_process_loop_runner_ = new MessageLoopRunner(); |
| will_process_loop_runner_->Run(); |
| will_process_loop_runner_ = nullptr; |
| } |
| |
| void WaitForThrottleWillCommitWithoutUrlLoader() { |
| if (will_commit_without_url_loader_called_) { |
| return; |
| } |
| will_commit_without_url_loader_loop_runner_ = new MessageLoopRunner(); |
| will_commit_without_url_loader_loop_runner_->Run(); |
| will_commit_without_url_loader_loop_runner_ = nullptr; |
| } |
| |
| void Continue(NavigationThrottle::ThrottleCheckResult result) { |
| ASSERT_NE(NavigationThrottle::DEFER, result.action()); |
| if (result.action() == NavigationThrottle::PROCEED) |
| navigation_throttle()->ResumeNavigation(); |
| else |
| navigation_throttle()->CancelNavigation(result); |
| } |
| |
| int will_start_called() { return will_start_called_; } |
| int will_redirect_called() { return will_redirect_called_; } |
| int will_fail_called() { return will_fail_called_; } |
| int will_process_called() { return will_process_called_; } |
| int will_commit_without_url_loader_called() { |
| return will_commit_without_url_loader_called_; |
| } |
| |
| int install_count() { return install_count_; } |
| |
| protected: |
| virtual void DidCallWillStartRequest() { |
| will_start_called_++; |
| if (will_start_loop_runner_) |
| will_start_loop_runner_->Quit(); |
| } |
| |
| virtual void DidCallWillRedirectRequest() { |
| will_redirect_called_++; |
| if (will_redirect_loop_runner_) |
| will_redirect_loop_runner_->Quit(); |
| } |
| |
| virtual void DidCallWillFailRequest() { |
| will_fail_called_++; |
| if (will_fail_loop_runner_) |
| will_fail_loop_runner_->Quit(); |
| } |
| |
| virtual void DidCallWillProcessResponse() { |
| will_process_called_++; |
| if (will_process_loop_runner_) |
| will_process_loop_runner_->Quit(); |
| } |
| |
| virtual void DidCallWillCommitWithoutUrlLoader() { |
| will_commit_without_url_loader_called_++; |
| if (will_commit_without_url_loader_loop_runner_) { |
| will_commit_without_url_loader_loop_runner_->Quit(); |
| } |
| } |
| |
| private: |
| void DidStartNavigation(NavigationHandle* handle) override { |
| if (!expected_start_url_.is_empty() && |
| handle->GetURL() != expected_start_url_) |
| return; |
| |
| std::unique_ptr<NavigationThrottle> throttle(new TestNavigationThrottle( |
| handle, will_start_result_, will_redirect_result_, will_fail_result_, |
| will_process_result_, will_commit_without_url_loader_result_, |
| base::BindOnce( |
| &TestNavigationThrottleInstaller::DidCallWillStartRequest, |
| weak_factory_.GetWeakPtr()), |
| base::BindOnce( |
| &TestNavigationThrottleInstaller::DidCallWillRedirectRequest, |
| weak_factory_.GetWeakPtr()), |
| base::BindOnce(&TestNavigationThrottleInstaller::DidCallWillFailRequest, |
| weak_factory_.GetWeakPtr()), |
| base::BindOnce( |
| &TestNavigationThrottleInstaller::DidCallWillProcessResponse, |
| weak_factory_.GetWeakPtr()), |
| base::BindOnce( |
| &TestNavigationThrottleInstaller::DidCallWillCommitWithoutUrlLoader, |
| weak_factory_.GetWeakPtr()))); |
| navigation_throttle_ = static_cast<TestNavigationThrottle*>(throttle.get()); |
| handle->RegisterThrottleForTesting(std::move(throttle)); |
| ++install_count_; |
| } |
| |
| void DidFinishNavigation(NavigationHandle* handle) override { |
| if (!navigation_throttle_) |
| return; |
| |
| if (handle == navigation_throttle_->navigation_handle()) |
| navigation_throttle_ = nullptr; |
| } |
| |
| NavigationThrottle::ThrottleCheckResult will_start_result_; |
| NavigationThrottle::ThrottleCheckResult will_redirect_result_; |
| NavigationThrottle::ThrottleCheckResult will_fail_result_; |
| NavigationThrottle::ThrottleCheckResult will_process_result_; |
| NavigationThrottle::ThrottleCheckResult |
| will_commit_without_url_loader_result_; |
| int will_start_called_ = 0; |
| int will_redirect_called_ = 0; |
| int will_fail_called_ = 0; |
| int will_process_called_ = 0; |
| int will_commit_without_url_loader_called_ = 0; |
| raw_ptr<TestNavigationThrottle> navigation_throttle_ = nullptr; |
| int install_count_ = 0; |
| scoped_refptr<MessageLoopRunner> will_start_loop_runner_; |
| scoped_refptr<MessageLoopRunner> will_redirect_loop_runner_; |
| scoped_refptr<MessageLoopRunner> will_fail_loop_runner_; |
| scoped_refptr<MessageLoopRunner> will_process_loop_runner_; |
| scoped_refptr<MessageLoopRunner> will_commit_without_url_loader_loop_runner_; |
| GURL expected_start_url_; |
| |
| // The throttle installer can be deleted before all tasks posted by its |
| // throttles are run, so it must be referenced via weak pointers. |
| base::WeakPtrFactory<TestNavigationThrottleInstaller> weak_factory_{this}; |
| }; |
| |
| // Same as above, but installs NavigationThrottles that do not directly return |
| // the pre-programmed check results, but first DEFER the navigation at each |
| // stage and then resume/cancel asynchronously. |
| class TestDeferringNavigationThrottleInstaller |
| : public TestNavigationThrottleInstaller { |
| public: |
| TestDeferringNavigationThrottleInstaller( |
| WebContents* web_contents, |
| NavigationThrottle::ThrottleCheckResult will_start_result, |
| NavigationThrottle::ThrottleCheckResult will_redirect_result, |
| NavigationThrottle::ThrottleCheckResult will_fail_result, |
| NavigationThrottle::ThrottleCheckResult will_process_result, |
| NavigationThrottle::ThrottleCheckResult |
| will_commit_without_url_loader_result, |
| GURL expected_start_url = GURL()) |
| : TestNavigationThrottleInstaller(web_contents, |
| NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, |
| expected_start_url), |
| will_start_deferred_result_(will_start_result), |
| will_redirect_deferred_result_(will_redirect_result), |
| will_fail_deferred_result_(will_fail_result), |
| will_process_deferred_result_(will_process_result), |
| will_commit_without_url_loader_result_( |
| will_commit_without_url_loader_result) {} |
| |
| protected: |
| void DidCallWillStartRequest() override { |
| TestNavigationThrottleInstaller::DidCallWillStartRequest(); |
| Continue(will_start_deferred_result_); |
| } |
| |
| void DidCallWillRedirectRequest() override { |
| TestNavigationThrottleInstaller::DidCallWillStartRequest(); |
| Continue(will_redirect_deferred_result_); |
| } |
| |
| void DidCallWillFailRequest() override { |
| TestNavigationThrottleInstaller::DidCallWillStartRequest(); |
| Continue(will_fail_deferred_result_); |
| } |
| |
| void DidCallWillProcessResponse() override { |
| TestNavigationThrottleInstaller::DidCallWillStartRequest(); |
| Continue(will_process_deferred_result_); |
| } |
| |
| void DidCallWillCommitWithoutUrlLoader() override { |
| TestNavigationThrottleInstaller::DidCallWillCommitWithoutUrlLoader(); |
| Continue(will_commit_without_url_loader_result_); |
| } |
| |
| private: |
| NavigationThrottle::ThrottleCheckResult will_start_deferred_result_; |
| NavigationThrottle::ThrottleCheckResult will_redirect_deferred_result_; |
| NavigationThrottle::ThrottleCheckResult will_fail_deferred_result_; |
| NavigationThrottle::ThrottleCheckResult will_process_deferred_result_; |
| NavigationThrottle::ThrottleCheckResult |
| will_commit_without_url_loader_result_; |
| }; |
| |
| // Records all navigation start URLs from the WebContents. |
| class NavigationStartUrlRecorder : public WebContentsObserver { |
| public: |
| explicit NavigationStartUrlRecorder(WebContents* web_contents) |
| : WebContentsObserver(web_contents) {} |
| |
| void DidStartNavigation(NavigationHandle* navigation_handle) override { |
| urls_.push_back(navigation_handle->GetURL()); |
| } |
| |
| const std::vector<GURL>& urls() const { return urls_; } |
| |
| private: |
| std::vector<GURL> urls_; |
| }; |
| |
| void ExpectChildFrameSetAsCollapsedInFTN(Shell* shell, bool expect_collapsed) { |
| // Check if the frame should be collapsed in theory as per FTN. |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| ASSERT_EQ(1u, root->child_count()); |
| FrameTreeNode* child = root->child_at(0u); |
| EXPECT_EQ(expect_collapsed, child->is_collapsed()); |
| } |
| |
| void ExpectChildFrameCollapsedInLayout(Shell* shell, |
| const std::string& frame_id, |
| bool expect_collapsed) { |
| // Check if the frame is collapsed in practice. |
| const char kScript[] = "document.getElementById(\"%s\").clientWidth;"; |
| int client_width = |
| EvalJs(shell, base::StringPrintf(kScript, frame_id.c_str())).ExtractInt(); |
| EXPECT_EQ(expect_collapsed, !client_width) << client_width; |
| } |
| |
| void ExpectChildFrameCollapsed(Shell* shell, |
| const std::string& frame_id, |
| bool expect_collapsed) { |
| ExpectChildFrameSetAsCollapsedInFTN(shell, expect_collapsed); |
| ExpectChildFrameCollapsedInLayout(shell, frame_id, expect_collapsed); |
| } |
| |
| } // namespace |
| |
| class NavigationRequestBrowserTest : public ContentBrowserTest { |
| protected: |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| SetupCrossSiteRedirector(embedded_test_server()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| // Installs a NavigationThrottle whose |method| method will return a |
| // ThrottleCheckResult with |action|, net::ERR_BLOCKED_BY_CLIENT and |
| // a custom error page. Navigates to |url|, checks that the navigation |
| // committed and that the custom error page is being shown. |
| void InstallThrottleAndTestNavigationCommittedWithErrorPage( |
| const GURL& url, |
| NavigationThrottle::ThrottleAction action, |
| TestNavigationThrottleInstaller::Method method) { |
| auto installer = TestNavigationThrottleInstaller::CreateForMethod( |
| shell()->web_contents(), method, |
| NavigationThrottle::ThrottleCheckResult( |
| action, net::ERR_BLOCKED_BY_CLIENT, |
| base::StringPrintf("<html><body>%s</body><html>", |
| kBodyTextContent))); |
| |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| |
| EXPECT_FALSE(NavigateToURL(shell(), url)); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_TRUE(observer.is_error()); |
| |
| content::RenderFrameHost* rfh = |
| shell()->web_contents()->GetPrimaryMainFrame(); |
| EXPECT_EQ(kBodyTextContent, EvalJs(rfh, "document.body.textContent")); |
| } |
| }; |
| |
| class NavigationRequestDownloadBrowserTest |
| : public NavigationRequestBrowserTest { |
| protected: |
| void SetUpOnMainThread() override { |
| NavigationRequestBrowserTest::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_; |
| }; |
| |
| // Ensure that PageTransition is properly set on the NavigationHandle. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, VerifyPageTransition) { |
| { |
| // Test browser initiated navigation, which should have a PageTransition as |
| // if it came from the omnibox. |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| ui::PageTransition expected_transition = ui::PageTransitionFromInt( |
| ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| EXPECT_EQ(url, observer.last_committed_url()); |
| EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs( |
| observer.page_transition(), expected_transition)); |
| } |
| |
| { |
| // Test navigating to a page with subframe. The subframe will have |
| // PageTransition of type AUTO_SUBFRAME. |
| GURL url( |
| embedded_test_server()->GetURL("/frame_tree/page_with_one_frame.html")); |
| NavigationHandleObserver observer( |
| shell()->web_contents(), |
| embedded_test_server()->GetURL("/cross-site/baz.com/title1.html")); |
| ui::PageTransition expected_transition = |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_AUTO_SUBFRAME); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| EXPECT_EQ(embedded_test_server()->GetURL("baz.com", "/title1.html"), |
| observer.last_committed_url()); |
| EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs( |
| observer.page_transition(), expected_transition)); |
| EXPECT_FALSE(observer.is_main_frame()); |
| } |
| } |
| |
| // Ensure that the following methods on NavigationHandle behave correctly: |
| // * IsInMainFrame |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, VerifyFrameTree) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b(c))")); |
| GURL b_url(embedded_test_server()->GetURL( |
| "b.com", "/cross_site_iframe_factory.html?b(c())")); |
| GURL c_url(embedded_test_server()->GetURL( |
| "c.com", "/cross_site_iframe_factory.html?c()")); |
| |
| NavigationHandleObserver main_observer(shell()->web_contents(), main_url); |
| NavigationHandleObserver b_observer(shell()->web_contents(), b_url); |
| NavigationHandleObserver c_observer(shell()->web_contents(), c_url); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| |
| // Verify the main frame. |
| EXPECT_TRUE(main_observer.has_committed()); |
| EXPECT_FALSE(main_observer.is_error()); |
| EXPECT_EQ(main_url, main_observer.last_committed_url()); |
| EXPECT_TRUE(main_observer.is_main_frame()); |
| EXPECT_EQ(root->frame_tree_node_id(), main_observer.frame_tree_node_id()); |
| |
| // Verify the b.com frame. |
| EXPECT_TRUE(b_observer.has_committed()); |
| EXPECT_FALSE(b_observer.is_error()); |
| EXPECT_EQ(b_url, b_observer.last_committed_url()); |
| EXPECT_FALSE(b_observer.is_main_frame()); |
| EXPECT_EQ(root->child_at(0)->frame_tree_node_id(), |
| b_observer.frame_tree_node_id()); |
| |
| // Verify the c.com frame. |
| EXPECT_TRUE(c_observer.has_committed()); |
| EXPECT_FALSE(c_observer.is_error()); |
| EXPECT_EQ(c_url, c_observer.last_committed_url()); |
| EXPECT_FALSE(c_observer.is_main_frame()); |
| EXPECT_EQ(root->child_at(0)->child_at(0)->frame_tree_node_id(), |
| c_observer.frame_tree_node_id()); |
| } |
| |
| // Ensure that the WasRedirected() method on NavigationHandle behaves correctly. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, VerifyRedirect) { |
| { |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| EXPECT_FALSE(observer.was_redirected()); |
| } |
| |
| { |
| GURL url(embedded_test_server()->GetURL("/cross-site/baz.com/title1.html")); |
| GURL final_url(embedded_test_server()->GetURL("baz.com", "/title1.html")); |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| |
| EXPECT_TRUE( |
| NavigateToURL(shell(), url, final_url /* expected_commit_url */)); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| EXPECT_TRUE(observer.was_redirected()); |
| } |
| } |
| |
| // Ensure that we can read and write to the browser process's copy of the blink |
| // runtime-enabled features via NavigationRequest's method |
| // GetMutableRuntimeFeatureStateContext() during commit. Then ensure that its |
| // values are persisted to the RenderFrameHostImpl's read-only context. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| RuntimeFeatureStatePersisted) { |
| // Sets up the NavigationRequest where we can begin accessing the |
| // RuntimeFeatureStateContext class. |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| TestNavigationManager manager(shell()->web_contents(), url); |
| |
| // Begin loading our url and wait for the request to start. |
| shell()->LoadURL(url); |
| EXPECT_TRUE(manager.WaitForRequestStart()); |
| |
| // Ensure we can get and set values in RuntimeFeatureStateContext. |
| blink::RuntimeFeatureStateContext& context = |
| NavigationRequest::From(manager.GetNavigationHandle()) |
| ->GetMutableRuntimeFeatureStateContext(); |
| bool is_test_feature_enabled(context.IsTestFeatureEnabled()); |
| context.SetTestFeatureEnabled(!is_test_feature_enabled); |
| EXPECT_EQ(context.IsTestFeatureEnabled(), !is_test_feature_enabled); |
| |
| // Check the override value map as well. |
| base::flat_map<::blink::mojom::RuntimeFeature, bool> |
| expected_feature_overrides = context.GetFeatureOverrides(); |
| EXPECT_EQ( |
| expected_feature_overrides[blink::mojom::RuntimeFeature::kTestFeature], |
| !is_test_feature_enabled); |
| |
| // Continue with the navigation until completion. |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| EXPECT_TRUE(manager.was_successful()); |
| |
| // Check that the changes were saved to the RenderFrameHost. |
| RuntimeFeatureStateDocumentData* document_data = |
| RuntimeFeatureStateDocumentData::GetForCurrentDocument( |
| shell()->web_contents()->GetPrimaryMainFrame()); |
| EXPECT_TRUE(document_data); |
| blink::RuntimeFeatureStateReadContext read_context = |
| document_data->runtime_feature_state_read_context(); |
| EXPECT_EQ(expected_feature_overrides, read_context.GetFeatureOverrides()); |
| } |
| |
| // Similar to RuntimeFeatureStatePersisted but ensures that even for |
| // runtime-enabled feature values that are equivalent to the previous state. |
| // This is important to persist as it helps the renderer determine force-enabled |
| // and force-disabled values. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| RuntimeFeatureStatePersistedForSameValue) { |
| // Sets up the NavigationRequest where we can begin accessing the |
| // RuntimeFeatureStateContext class. |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| TestNavigationManager manager(shell()->web_contents(), url); |
| |
| // Begin loading our url and wait for the request to start. |
| shell()->LoadURL(url); |
| EXPECT_TRUE(manager.WaitForRequestStart()); |
| |
| // Ensure we can set values in RuntimeFeatureStateContext that are |
| // equivalent to the feature's previous state. |
| blink::RuntimeFeatureStateContext& context = |
| NavigationRequest::From(manager.GetNavigationHandle()) |
| ->GetMutableRuntimeFeatureStateContext(); |
| bool is_test_feature_enabled(context.IsTestFeatureEnabled()); |
| context.SetTestFeatureEnabled(is_test_feature_enabled); |
| EXPECT_EQ(context.IsTestFeatureEnabled(), is_test_feature_enabled); |
| |
| // Check the override value map as well. |
| base::flat_map<::blink::mojom::RuntimeFeature, bool> |
| expected_feature_overrides = context.GetFeatureOverrides(); |
| EXPECT_EQ( |
| expected_feature_overrides[blink::mojom::RuntimeFeature::kTestFeature], |
| is_test_feature_enabled); |
| |
| // Continue with the navigation until completion. |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| EXPECT_TRUE(manager.was_successful()); |
| |
| // Check that the changes were saved to the RenderFrameHost's feature |
| // overrides. |
| RuntimeFeatureStateDocumentData* document_data = |
| RuntimeFeatureStateDocumentData::GetForCurrentDocument( |
| shell()->web_contents()->GetPrimaryMainFrame()); |
| EXPECT_TRUE(document_data); |
| blink::RuntimeFeatureStateReadContext read_context = |
| document_data->runtime_feature_state_read_context(); |
| EXPECT_EQ(expected_feature_overrides, read_context.GetFeatureOverrides()); |
| } |
| |
| // Similar to RuntimeFeatureStatePersisted but ensures that even for |
| // runtime-enabled feature values are cleared across redirects. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| RuntimeFeatureStateClearOnRedirect) { |
| // Sets up the NavigationRequest where we can begin accessing the |
| // RuntimeFeatureStateContext class. |
| GURL redirect_url( |
| embedded_test_server()->GetURL("/cross-site/bar.com/title2.html")); |
| TestNavigationManager redirect_manager(shell()->web_contents(), redirect_url); |
| shell()->LoadURL(redirect_url); |
| |
| EXPECT_TRUE(redirect_manager.WaitForRequestStart()); |
| |
| // Set a feature value we expect to be cleared upon redirect. |
| blink::RuntimeFeatureStateContext& context = |
| NavigationRequest::From(redirect_manager.GetNavigationHandle()) |
| ->GetMutableRuntimeFeatureStateContext(); |
| bool is_test_feature_enabled(context.IsTestFeatureEnabled()); |
| context.SetTestFeatureEnabled(!is_test_feature_enabled); |
| EXPECT_FALSE(context.GetFeatureOverrides().empty()); |
| |
| redirect_manager.ResumeNavigation(); |
| EXPECT_TRUE(redirect_manager.WaitForResponse()); |
| |
| // Ensure NavigationRequest's new RuntimeFeatureStateContext is clear |
| const blink::RuntimeFeatureStateContext& new_context = |
| NavigationRequest::From(redirect_manager.GetNavigationHandle()) |
| ->GetRuntimeFeatureStateContext(); |
| EXPECT_TRUE(new_context.GetFeatureOverrides().empty()); |
| |
| // Continue with the navigation until completion. |
| ASSERT_TRUE(redirect_manager.WaitForNavigationFinished()); |
| EXPECT_TRUE(redirect_manager.was_successful()); |
| |
| // Ensure that the changes made to the features before redirect do not |
| // persist to the read context. |
| RuntimeFeatureStateDocumentData* document_data = |
| RuntimeFeatureStateDocumentData::GetForCurrentDocument( |
| shell()->web_contents()->GetPrimaryMainFrame()); |
| EXPECT_TRUE(document_data); |
| blink::RuntimeFeatureStateReadContext read_context = |
| document_data->runtime_feature_state_read_context(); |
| EXPECT_TRUE(read_context.GetFeatureOverrides().empty()); |
| } |
| |
| // Ensure that a certificate error results in a committed navigation with |
| // the appropriate error code on the handle. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, VerifyCertErrorFailure) { |
| net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); |
| https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_MISMATCHED_NAME); |
| ASSERT_TRUE(https_server.Start()); |
| GURL url(https_server.GetURL("/title1.html")); |
| |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| |
| EXPECT_FALSE(NavigateToURL(shell(), url)); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_TRUE(observer.is_error()); |
| EXPECT_EQ(net::ERR_CERT_COMMON_NAME_INVALID, observer.net_error_code()); |
| } |
| |
| // Ensure that the IsRendererInitiated() method on NavigationHandle behaves |
| // correctly. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, VerifyRendererInitiated) { |
| { |
| // Test browser initiated navigation. |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| EXPECT_FALSE(observer.is_renderer_initiated()); |
| } |
| |
| { |
| // Test a main frame + subframes navigation. |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b(c))")); |
| GURL b_url(embedded_test_server()->GetURL( |
| "b.com", "/cross_site_iframe_factory.html?b(c())")); |
| GURL c_url(embedded_test_server()->GetURL( |
| "c.com", "/cross_site_iframe_factory.html?c()")); |
| |
| NavigationHandleObserver main_observer(shell()->web_contents(), main_url); |
| NavigationHandleObserver b_observer(shell()->web_contents(), b_url); |
| NavigationHandleObserver c_observer(shell()->web_contents(), c_url); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| // Verify that the main frame navigation is not renderer initiated. |
| EXPECT_TRUE(main_observer.has_committed()); |
| EXPECT_FALSE(main_observer.is_error()); |
| EXPECT_FALSE(main_observer.is_renderer_initiated()); |
| |
| // Verify that the subframe navigations are renderer initiated. |
| EXPECT_TRUE(b_observer.has_committed()); |
| EXPECT_FALSE(b_observer.is_error()); |
| EXPECT_TRUE(b_observer.is_renderer_initiated()); |
| EXPECT_TRUE(c_observer.has_committed()); |
| EXPECT_FALSE(c_observer.is_error()); |
| EXPECT_TRUE(c_observer.is_renderer_initiated()); |
| } |
| |
| { |
| // Test a pushState navigation. |
| GURL url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(a())")); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| |
| NavigationHandleObserver observer( |
| shell()->web_contents(), |
| embedded_test_server()->GetURL("a.com", "/bar")); |
| EXPECT_TRUE( |
| ExecJs(root->child_at(0), "window.history.pushState({}, '', 'bar');")); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| EXPECT_TRUE(observer.is_renderer_initiated()); |
| } |
| } |
| |
| // Ensure that methods on NavigationHandle behave correctly with an iframe that |
| // navigates to its srcdoc attribute. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, VerifySrcdoc) { |
| GURL url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_srcdoc_frame.html")); |
| NavigationHandleObserver observer(shell()->web_contents(), |
| GURL("about:srcdoc")); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| EXPECT_TRUE(observer.last_committed_url().IsAboutSrcdoc()); |
| } |
| |
| // Ensure that the IsSameDocument() method on NavigationHandle behaves |
| // correctly. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, VerifySameDocument) { |
| GURL url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(a())")); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| { |
| NavigationHandleObserver observer( |
| shell()->web_contents(), |
| embedded_test_server()->GetURL("a.com", "/foo")); |
| EXPECT_TRUE( |
| ExecJs(root->child_at(0), "window.history.pushState({}, '', 'foo');")); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| EXPECT_TRUE(observer.is_same_document()); |
| } |
| { |
| NavigationHandleObserver observer( |
| shell()->web_contents(), |
| embedded_test_server()->GetURL("a.com", "/bar")); |
| EXPECT_TRUE(ExecJs(root->child_at(0), |
| "window.history.replaceState({}, '', 'bar');")); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| EXPECT_TRUE(observer.is_same_document()); |
| } |
| { |
| NavigationHandleObserver observer( |
| shell()->web_contents(), |
| embedded_test_server()->GetURL("a.com", "/bar#frag")); |
| EXPECT_TRUE(ExecJs(root->child_at(0), "window.location.replace('#frag');")); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| EXPECT_TRUE(observer.is_same_document()); |
| } |
| |
| GURL about_blank_url(url::kAboutBlankURL); |
| { |
| NavigationHandleObserver observer(shell()->web_contents(), about_blank_url); |
| EXPECT_TRUE(ExecJs( |
| root, "document.body.appendChild(document.createElement('iframe'));")); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| EXPECT_FALSE(observer.is_same_document()); |
| EXPECT_EQ(about_blank_url, observer.last_committed_url()); |
| } |
| { |
| NavigationHandleObserver observer(shell()->web_contents(), about_blank_url); |
| EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), about_blank_url)); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| EXPECT_FALSE(observer.is_same_document()); |
| EXPECT_EQ(about_blank_url, observer.last_committed_url()); |
| } |
| } |
| |
| // Ensure that a NavigationThrottle can cancel the navigation at navigation |
| // start. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, ThrottleCancelStart) { |
| GURL start_url(embedded_test_server()->GetURL("/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), start_url)); |
| |
| GURL redirect_url( |
| embedded_test_server()->GetURL("/cross-site/bar.com/title2.html")); |
| NavigationHandleObserver observer(shell()->web_contents(), redirect_url); |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::CANCEL, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| |
| EXPECT_FALSE(NavigateToURL(shell(), redirect_url)); |
| |
| EXPECT_FALSE(observer.has_committed()); |
| EXPECT_TRUE(observer.is_error()); |
| |
| // The navigation should have been canceled before being redirected. |
| EXPECT_FALSE(observer.was_redirected()); |
| EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), start_url); |
| } |
| |
| // Ensure that a NavigationThrottle can cancel the navigation when a navigation |
| // is redirected. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, ThrottleCancelRedirect) { |
| GURL start_url(embedded_test_server()->GetURL("/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), start_url)); |
| |
| // A navigation with a redirect should be canceled. |
| { |
| GURL redirect_url( |
| embedded_test_server()->GetURL("/cross-site/bar.com/title2.html")); |
| NavigationHandleObserver observer(shell()->web_contents(), redirect_url); |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::CANCEL, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| |
| EXPECT_FALSE(NavigateToURL(shell(), redirect_url)); |
| |
| EXPECT_FALSE(observer.has_committed()); |
| EXPECT_TRUE(observer.is_error()); |
| EXPECT_TRUE(observer.was_redirected()); |
| EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), start_url); |
| } |
| |
| // A navigation without redirects should be successful. |
| { |
| GURL no_redirect_url(embedded_test_server()->GetURL("/title2.html")); |
| NavigationHandleObserver observer(shell()->web_contents(), no_redirect_url); |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::CANCEL, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), no_redirect_url)); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| EXPECT_FALSE(observer.was_redirected()); |
| EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), no_redirect_url); |
| } |
| } |
| |
| // Ensure that a NavigationThrottle can respond CANCEL when a navigation fails. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, ThrottleCancelFailure) { |
| net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); |
| https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_MISMATCHED_NAME); |
| ASSERT_TRUE(https_server.Start()); |
| GURL url(https_server.GetURL("/title1.html")); |
| |
| // A navigation with a cert error failure should be canceled. CANCEL has a |
| // default net error of ERR_ABORTED, which should prevent the navigation from |
| // committing. |
| { |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::CANCEL, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| |
| EXPECT_FALSE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| |
| EXPECT_FALSE(NavigateToURL(shell(), url)); |
| |
| EXPECT_FALSE(installer.will_fail_called()); |
| EXPECT_FALSE(observer.has_committed()); |
| EXPECT_TRUE(observer.is_error()); |
| EXPECT_EQ(net::ERR_ABORTED, observer.net_error_code()); |
| } |
| |
| // A navigation with a cert error failure should be canceled. |
| // A custom net error should result in a committed error page. |
| { |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, |
| NavigationThrottle::ThrottleCheckResult(NavigationThrottle::CANCEL, |
| net::ERR_CERT_DATE_INVALID), |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| |
| EXPECT_FALSE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| |
| EXPECT_FALSE(NavigateToURL(shell(), url)); |
| |
| EXPECT_TRUE(installer.will_fail_called()); |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_TRUE(observer.is_error()); |
| EXPECT_EQ(net::ERR_CERT_DATE_INVALID, observer.net_error_code()); |
| } |
| |
| // A navigation without a cert error should be successful, without calling |
| // WillFailRequest() on the throttle. (We set the failure method response to |
| // CANCEL, but that shouldn't matter if the test passes.) |
| { |
| url = embedded_test_server()->GetURL("/title2.html"); |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::CANCEL, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| EXPECT_FALSE(installer.will_fail_called()); |
| EXPECT_FALSE(observer.is_error()); |
| } |
| } |
| |
| // Ensure that a NavigationThrottle can cancel the navigation when the response |
| // is received. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, ThrottleCancelResponse) { |
| GURL start_url(embedded_test_server()->GetURL("/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), start_url)); |
| |
| GURL redirect_url( |
| embedded_test_server()->GetURL("/cross-site/bar.com/title2.html")); |
| NavigationHandleObserver observer(shell()->web_contents(), redirect_url); |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::CANCEL, NavigationThrottle::PROCEED); |
| |
| EXPECT_FALSE(NavigateToURL(shell(), redirect_url)); |
| |
| EXPECT_FALSE(observer.has_committed()); |
| EXPECT_TRUE(observer.is_error()); |
| // The navigation should have been redirected first, and then canceled when |
| // the response arrived. |
| EXPECT_TRUE(observer.was_redirected()); |
| EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), start_url); |
| } |
| |
| // Ensure that a NavigationThrottle can cancel the navigation when committing |
| // without a URLLoader. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| ThrottleCancelCommitWithoutUrlLoader) { |
| GURL start_url(embedded_test_server()->GetURL("/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), start_url)); |
| |
| GURL about_blank_url(url::kAboutBlankURL); |
| NavigationHandleObserver observer(shell()->web_contents(), about_blank_url); |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::CANCEL_AND_IGNORE); |
| |
| EXPECT_FALSE(NavigateToURL(shell(), about_blank_url)); |
| |
| EXPECT_FALSE(observer.has_committed()); |
| EXPECT_TRUE(observer.is_error()); |
| EXPECT_FALSE(observer.was_redirected()); |
| EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), start_url); |
| } |
| |
| // Ensure that a NavigationThrottle can defer and resume the navigation at |
| // navigation start, navigation redirect and response received. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, ThrottleDefer) { |
| GURL start_url(embedded_test_server()->GetURL("/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), start_url)); |
| |
| GURL redirect_url( |
| embedded_test_server()->GetURL("/cross-site/bar.com/title2.html")); |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| NavigationHandleObserver observer(shell()->web_contents(), redirect_url); |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, NavigationThrottle::DEFER); |
| |
| shell()->LoadURL(redirect_url); |
| |
| // Wait for WillStartRequest. |
| installer.WaitForThrottleWillStart(); |
| EXPECT_EQ(1, installer.will_start_called()); |
| EXPECT_EQ(0, installer.will_redirect_called()); |
| EXPECT_EQ(0, installer.will_fail_called()); |
| EXPECT_EQ(0, installer.will_process_called()); |
| EXPECT_EQ(0, installer.will_commit_without_url_loader_called()); |
| installer.navigation_throttle()->ResumeNavigation(); |
| |
| // Wait for WillRedirectRequest. |
| installer.WaitForThrottleWillRedirect(); |
| EXPECT_EQ(1, installer.will_start_called()); |
| EXPECT_EQ(1, installer.will_redirect_called()); |
| EXPECT_EQ(0, installer.will_fail_called()); |
| EXPECT_EQ(0, installer.will_process_called()); |
| EXPECT_EQ(0, installer.will_commit_without_url_loader_called()); |
| installer.navigation_throttle()->ResumeNavigation(); |
| |
| // Wait for WillProcessResponse. |
| installer.WaitForThrottleWillProcess(); |
| EXPECT_EQ(1, installer.will_start_called()); |
| EXPECT_EQ(1, installer.will_redirect_called()); |
| EXPECT_EQ(0, installer.will_fail_called()); |
| EXPECT_EQ(1, installer.will_process_called()); |
| EXPECT_EQ(0, installer.will_commit_without_url_loader_called()); |
| installer.navigation_throttle()->ResumeNavigation(); |
| |
| // Wait for the end of the navigation. |
| navigation_observer.Wait(); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_TRUE(observer.was_redirected()); |
| EXPECT_FALSE(observer.is_error()); |
| EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), |
| GURL(embedded_test_server()->GetURL("bar.com", "/title2.html"))); |
| } |
| |
| // Ensure that a NavigationThrottle can defer and resume the navigation at |
| // navigation start and navigation failure. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, ThrottleDeferFailure) { |
| net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); |
| https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_MISMATCHED_NAME); |
| ASSERT_TRUE(https_server.Start()); |
| GURL failure_url(https_server.GetURL("/title1.html")); |
| |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| NavigationHandleObserver observer(shell()->web_contents(), failure_url); |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, NavigationThrottle::DEFER); |
| |
| shell()->LoadURL(failure_url); |
| |
| // Wait for WillStartRequest. |
| installer.WaitForThrottleWillStart(); |
| EXPECT_EQ(1, installer.will_start_called()); |
| EXPECT_EQ(0, installer.will_redirect_called()); |
| EXPECT_EQ(0, installer.will_fail_called()); |
| EXPECT_EQ(0, installer.will_process_called()); |
| EXPECT_EQ(0, installer.will_commit_without_url_loader_called()); |
| installer.navigation_throttle()->ResumeNavigation(); |
| |
| // Wait for WillFailRequest. |
| installer.WaitForThrottleWillFail(); |
| EXPECT_EQ(1, installer.will_start_called()); |
| EXPECT_EQ(0, installer.will_redirect_called()); |
| EXPECT_EQ(1, installer.will_fail_called()); |
| EXPECT_EQ(0, installer.will_process_called()); |
| EXPECT_EQ(0, installer.will_commit_without_url_loader_called()); |
| installer.navigation_throttle()->ResumeNavigation(); |
| |
| // Wait for the end of the navigation. |
| navigation_observer.Wait(); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_TRUE(observer.is_error()); |
| EXPECT_EQ(net::ERR_CERT_COMMON_NAME_INVALID, observer.net_error_code()); |
| } |
| |
| // Ensure that a NavigationThrottle can defer and resume the navigation when |
| // navigating without a URLLoader. This test covers multiple types of |
| // navigations that do not require a URLLoader (same-document navigations and |
| // about:blank). |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| ThrottleDeferCommitWithoutUrlLoader) { |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| GURL url_fragment(embedded_test_server()->GetURL("/title1.html#id_1")); |
| GURL about_blank_url(url::kAboutBlankURL); |
| |
| // Perform a new-document navigation (setup). |
| { |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| EXPECT_EQ(1, installer.will_start_called()); |
| EXPECT_EQ(1, installer.will_process_called()); |
| EXPECT_EQ(0, installer.will_commit_without_url_loader_called()); |
| EXPECT_FALSE(observer.is_same_document()); |
| } |
| |
| // Same-document navigation |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| NavigationHandleObserver observer(shell()->web_contents(), url_fragment); |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, NavigationThrottle::DEFER); |
| |
| shell()->LoadURL(url_fragment); |
| |
| // Wait for WillCommitWithoutUrlLoader. |
| installer.WaitForThrottleWillCommitWithoutUrlLoader(); |
| EXPECT_EQ(0, installer.will_start_called()); |
| EXPECT_EQ(0, installer.will_redirect_called()); |
| EXPECT_EQ(0, installer.will_fail_called()); |
| EXPECT_EQ(0, installer.will_process_called()); |
| EXPECT_EQ(1, installer.will_commit_without_url_loader_called()); |
| installer.navigation_throttle()->ResumeNavigation(); |
| |
| // Wait for the end of the navigation. |
| navigation_observer.Wait(); |
| |
| EXPECT_TRUE(observer.is_same_document()); |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.was_redirected()); |
| EXPECT_FALSE(observer.is_error()); |
| EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_fragment); |
| } |
| |
| // about:blank |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| NavigationHandleObserver observer(shell()->web_contents(), about_blank_url); |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, NavigationThrottle::DEFER); |
| |
| shell()->LoadURL(about_blank_url); |
| |
| // Wait for WillCommitWithoutUrlLoader. |
| installer.WaitForThrottleWillCommitWithoutUrlLoader(); |
| EXPECT_EQ(0, installer.will_start_called()); |
| EXPECT_EQ(0, installer.will_redirect_called()); |
| EXPECT_EQ(0, installer.will_fail_called()); |
| EXPECT_EQ(0, installer.will_process_called()); |
| EXPECT_EQ(1, installer.will_commit_without_url_loader_called()); |
| installer.navigation_throttle()->ResumeNavigation(); |
| |
| // Wait for the end of the navigation. |
| navigation_observer.Wait(); |
| |
| EXPECT_FALSE(observer.is_same_document()); |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.was_redirected()); |
| EXPECT_FALSE(observer.is_error()); |
| EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), about_blank_url); |
| } |
| } |
| |
| // Ensure that a NavigationThrottle can defer and cancel the navigation when |
| // navigating without a URLLoader. This test covers multiple types of |
| // navigations that do not require a URLLoader (same-document navigations and |
| // about:blank). |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| ThrottleDeferAndCancelCommitWithoutUrlLoader) { |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| GURL url_fragment(embedded_test_server()->GetURL("/title1.html#id_1")); |
| GURL about_blank_url(url::kAboutBlankURL); |
| |
| // Perform a new-document navigation (setup). |
| { |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| EXPECT_EQ(1, installer.will_start_called()); |
| EXPECT_EQ(1, installer.will_process_called()); |
| EXPECT_EQ(0, installer.will_commit_without_url_loader_called()); |
| EXPECT_FALSE(observer.is_same_document()); |
| } |
| |
| // Same-document navigation |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| NavigationHandleObserver observer(shell()->web_contents(), url_fragment); |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, NavigationThrottle::DEFER); |
| |
| shell()->LoadURL(url_fragment); |
| |
| // Wait for WillCommitWithoutUrlLoader. |
| installer.WaitForThrottleWillCommitWithoutUrlLoader(); |
| EXPECT_EQ(0, installer.will_start_called()); |
| EXPECT_EQ(0, installer.will_redirect_called()); |
| EXPECT_EQ(0, installer.will_fail_called()); |
| EXPECT_EQ(0, installer.will_process_called()); |
| EXPECT_EQ(1, installer.will_commit_without_url_loader_called()); |
| |
| // Cancel the deferred navigation. |
| installer.navigation_throttle()->CancelNavigation( |
| NavigationThrottle::CANCEL_AND_IGNORE); |
| |
| // Wait for the end of the navigation. |
| navigation_observer.Wait(); |
| |
| EXPECT_TRUE(observer.is_same_document()); |
| EXPECT_FALSE(observer.has_committed()); |
| EXPECT_FALSE(observer.was_redirected()); |
| EXPECT_TRUE(observer.is_error()); |
| } |
| |
| // about:blank |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| NavigationHandleObserver observer(shell()->web_contents(), about_blank_url); |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, NavigationThrottle::DEFER); |
| |
| shell()->LoadURL(about_blank_url); |
| |
| // Wait for WillCommitWithoutUrlLoader. |
| installer.WaitForThrottleWillCommitWithoutUrlLoader(); |
| EXPECT_EQ(0, installer.will_start_called()); |
| EXPECT_EQ(0, installer.will_redirect_called()); |
| EXPECT_EQ(0, installer.will_fail_called()); |
| EXPECT_EQ(0, installer.will_process_called()); |
| EXPECT_EQ(1, installer.will_commit_without_url_loader_called()); |
| |
| // Cancel the deferred navigation. |
| installer.navigation_throttle()->CancelNavigation( |
| NavigationThrottle::CANCEL_AND_IGNORE); |
| |
| // Wait for the end of the navigation. |
| navigation_observer.Wait(); |
| |
| EXPECT_FALSE(observer.is_same_document()); |
| EXPECT_FALSE(observer.has_committed()); |
| EXPECT_FALSE(observer.was_redirected()); |
| EXPECT_TRUE(observer.is_error()); |
| } |
| } |
| |
| // Ensure that a NavigationThrottle can block the navigation and collapse the |
| // frame owner both on request start as well as after a redirect. Plus, ensure |
| // that the frame is restored on the subsequent non-error-page navigation. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, ThrottleBlockAndCollapse) { |
| const char kChildFrameId[] = "child0"; |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/frame_tree/page_with_one_frame.html")); |
| GURL blocked_subframe_url(embedded_test_server()->GetURL( |
| "a.com", "/cross-site/baz.com/title1.html")); |
| GURL allowed_subframe_url(embedded_test_server()->GetURL( |
| "a.com", "/cross-site/baz.com/title2.html")); |
| GURL allowed_subframe_final_url( |
| embedded_test_server()->GetURL("baz.com", "/title2.html")); |
| |
| // Exercise both synchronous and deferred throttle check results, and both on |
| // WillStartRequest and on WillRedirectRequest. |
| const struct { |
| NavigationThrottle::ThrottleAction will_start_result; |
| NavigationThrottle::ThrottleAction will_redirect_result; |
| bool deferred_block; |
| } kTestCases[] = { |
| {NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE, |
| NavigationThrottle::PROCEED, false}, |
| {NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE, |
| NavigationThrottle::PROCEED, true}, |
| {NavigationThrottle::PROCEED, |
| NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE, false}, |
| {NavigationThrottle::PROCEED, |
| NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE, true}, |
| }; |
| |
| for (const auto& test_case : kTestCases) { |
| SCOPED_TRACE(::testing::Message() << test_case.will_start_result << ", " |
| << test_case.will_redirect_result << ", " |
| << test_case.deferred_block); |
| |
| std::unique_ptr<TestNavigationThrottleInstaller> |
| subframe_throttle_installer; |
| if (test_case.deferred_block) { |
| subframe_throttle_installer = |
| std::make_unique<TestDeferringNavigationThrottleInstaller>( |
| shell()->web_contents(), test_case.will_start_result, |
| test_case.will_redirect_result, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| blocked_subframe_url); |
| } else { |
| subframe_throttle_installer = |
| std::make_unique<TestNavigationThrottleInstaller>( |
| shell()->web_contents(), test_case.will_start_result, |
| test_case.will_redirect_result, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| blocked_subframe_url); |
| } |
| |
| { |
| SCOPED_TRACE("Initial navigation blocked on main frame load."); |
| NavigationHandleObserver subframe_observer(shell()->web_contents(), |
| blocked_subframe_url); |
| |
| ASSERT_TRUE(NavigateToURL(shell(), main_url)); |
| EXPECT_TRUE(subframe_observer.is_error()); |
| EXPECT_TRUE(subframe_observer.has_committed()); |
| EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, subframe_observer.net_error_code()); |
| ExpectChildFrameCollapsed(shell(), kChildFrameId, |
| true /* expect_collapsed */); |
| } |
| |
| { |
| SCOPED_TRACE("Subsequent subframe navigation is allowed."); |
| NavigationHandleObserver subframe_observer(shell()->web_contents(), |
| allowed_subframe_url); |
| |
| ASSERT_TRUE(NavigateIframeToURL(shell()->web_contents(), kChildFrameId, |
| allowed_subframe_url)); |
| EXPECT_TRUE(subframe_observer.has_committed()); |
| EXPECT_FALSE(subframe_observer.is_error()); |
| EXPECT_EQ(allowed_subframe_final_url, |
| subframe_observer.last_committed_url()); |
| ExpectChildFrameCollapsed(shell(), kChildFrameId, |
| false /* expect_collapsed */); |
| } |
| |
| { |
| SCOPED_TRACE("Subsequent subframe navigation is blocked."); |
| NavigationHandleObserver subframe_observer(shell()->web_contents(), |
| blocked_subframe_url); |
| |
| ASSERT_TRUE(NavigateIframeToURL(shell()->web_contents(), kChildFrameId, |
| blocked_subframe_url)); |
| |
| EXPECT_TRUE(subframe_observer.has_committed()); |
| EXPECT_TRUE(subframe_observer.is_error()); |
| EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, subframe_observer.net_error_code()); |
| ExpectChildFrameCollapsed(shell(), kChildFrameId, |
| true /* expect_collapsed */); |
| } |
| } |
| } |
| |
| // BLOCK_REQUEST_AND_COLLAPSE should block the navigation in legacy <frame>'s, |
| // but should not collapse the <frame> element itself for legacy reasons. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| ThrottleBlockAndCollapse_LegacyFrameNotCollapsed) { |
| const char kChildFrameId[] = "child0"; |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/frame_tree/legacy_frameset.html")); |
| GURL blocked_subframe_url( |
| embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL allowed_subframe_url( |
| embedded_test_server()->GetURL("a.com", "/title2.html")); |
| |
| TestNavigationThrottleInstaller subframe_throttle_installer( |
| shell()->web_contents(), NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| blocked_subframe_url); |
| |
| { |
| SCOPED_TRACE("Initial navigation blocked on main frame load."); |
| NavigationHandleObserver subframe_observer(shell()->web_contents(), |
| blocked_subframe_url); |
| |
| ASSERT_TRUE(NavigateToURL(shell(), main_url)); |
| EXPECT_TRUE(subframe_observer.is_error()); |
| EXPECT_TRUE(subframe_observer.has_committed()); |
| EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, subframe_observer.net_error_code()); |
| ExpectChildFrameSetAsCollapsedInFTN(shell(), true /* expect_collapsed */); |
| ExpectChildFrameCollapsedInLayout(shell(), kChildFrameId, |
| false /* expect_collapsed */); |
| } |
| |
| { |
| SCOPED_TRACE("Subsequent subframe navigation is allowed."); |
| NavigationHandleObserver subframe_observer(shell()->web_contents(), |
| allowed_subframe_url); |
| |
| ASSERT_TRUE(NavigateIframeToURL(shell()->web_contents(), kChildFrameId, |
| allowed_subframe_url)); |
| EXPECT_TRUE(subframe_observer.has_committed()); |
| EXPECT_FALSE(subframe_observer.is_error()); |
| EXPECT_EQ(allowed_subframe_url, subframe_observer.last_committed_url()); |
| ExpectChildFrameCollapsed(shell(), kChildFrameId, |
| false /* expect_collapsed */); |
| } |
| } |
| |
| // Checks that the RequestContextType value is properly set. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| VerifyRequestContextTypeForFrameTree) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b(c))")); |
| GURL b_url(embedded_test_server()->GetURL( |
| "b.com", "/cross_site_iframe_factory.html?b(c())")); |
| GURL c_url(embedded_test_server()->GetURL( |
| "c.com", "/cross_site_iframe_factory.html?c()")); |
| |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| TestNavigationManager main_manager(shell()->web_contents(), main_url); |
| TestNavigationManager b_manager(shell()->web_contents(), b_url); |
| TestNavigationManager c_manager(shell()->web_contents(), c_url); |
| NavigationStartUrlRecorder url_recorder(shell()->web_contents()); |
| |
| // Starts and verifies the main frame navigation. |
| shell()->LoadURL(main_url); |
| EXPECT_TRUE(main_manager.WaitForRequestStart()); |
| // For each navigation a new throttle should have been installed. |
| EXPECT_EQ(1, installer.install_count()); |
| // Checks the only URL recorded so far is the one expected for the main frame. |
| EXPECT_EQ(main_url, url_recorder.urls().back()); |
| EXPECT_EQ(1ul, url_recorder.urls().size()); |
| // Checks the main frame RequestContextType. |
| EXPECT_EQ(blink::mojom::RequestContextType::LOCATION, |
| installer.navigation_throttle()->request_context_type()); |
| |
| // Ditto for frame b navigation. |
| ASSERT_TRUE(main_manager.WaitForNavigationFinished()); |
| EXPECT_TRUE(b_manager.WaitForRequestStart()); |
| EXPECT_EQ(2, installer.install_count()); |
| EXPECT_EQ(b_url, url_recorder.urls().back()); |
| EXPECT_EQ(2ul, url_recorder.urls().size()); |
| EXPECT_EQ(blink::mojom::RequestContextType::IFRAME, |
| installer.navigation_throttle()->request_context_type()); |
| |
| // Ditto for frame c navigation. |
| ASSERT_TRUE(b_manager.WaitForNavigationFinished()); |
| EXPECT_TRUE(c_manager.WaitForRequestStart()); |
| EXPECT_EQ(3, installer.install_count()); |
| EXPECT_EQ(c_url, url_recorder.urls().back()); |
| EXPECT_EQ(3ul, url_recorder.urls().size()); |
| EXPECT_EQ(blink::mojom::RequestContextType::IFRAME, |
| installer.navigation_throttle()->request_context_type()); |
| |
| // Lets the final navigation finish so that we conclude running the |
| // RequestContextType checks that happen in TestNavigationThrottle. |
| ASSERT_TRUE(c_manager.WaitForNavigationFinished()); |
| // Confirms the last navigation did finish. |
| EXPECT_FALSE(installer.navigation_throttle()); |
| } |
| |
| // Checks that the RequestContextType value is properly set for an hyper-link. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| VerifyHyperlinkRequestContextType) { |
| GURL link_url(embedded_test_server()->GetURL("/title2.html")); |
| GURL document_url(embedded_test_server()->GetURL("/simple_links.html")); |
| |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| TestNavigationManager link_manager(shell()->web_contents(), link_url); |
| NavigationStartUrlRecorder url_recorder(shell()->web_contents()); |
| |
| // Navigate to a page with a link. |
| EXPECT_TRUE(NavigateToURL(shell(), document_url)); |
| EXPECT_EQ(document_url, url_recorder.urls().back()); |
| EXPECT_EQ(1ul, url_recorder.urls().size()); |
| |
| // Starts the navigation from a link click and then check it. |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_EQ(true, EvalJs(shell(), "clickSameSiteLink();")); |
| EXPECT_TRUE(link_manager.WaitForRequestStart()); |
| EXPECT_EQ(link_url, url_recorder.urls().back()); |
| EXPECT_EQ(2ul, url_recorder.urls().size()); |
| EXPECT_EQ(blink::mojom::RequestContextType::HYPERLINK, |
| installer.navigation_throttle()->request_context_type()); |
| |
| // Finishes the last navigation. |
| ASSERT_TRUE(link_manager.WaitForNavigationFinished()); |
| EXPECT_FALSE(installer.navigation_throttle()); |
| } |
| |
| // Checks that the RequestContextType value is properly set for an form (POST). |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| VerifyFormRequestContextType) { |
| GURL document_url( |
| embedded_test_server()->GetURL("/session_history/form.html")); |
| GURL post_url(embedded_test_server()->GetURL("/echotitle")); |
| |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| TestNavigationManager post_manager(shell()->web_contents(), post_url); |
| NavigationStartUrlRecorder url_recorder(shell()->web_contents()); |
| |
| // Navigate to a page with a form. |
| EXPECT_TRUE(NavigateToURL(shell(), document_url)); |
| EXPECT_EQ(document_url, url_recorder.urls().back()); |
| EXPECT_EQ(1ul, url_recorder.urls().size()); |
| |
| // Executes the form POST navigation and then check it. |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| GURL submit_url("javascript:submitForm('isubmit')"); |
| shell()->LoadURL(submit_url); |
| EXPECT_TRUE(post_manager.WaitForRequestStart()); |
| EXPECT_EQ(post_url, url_recorder.urls().back()); |
| EXPECT_EQ(2ul, url_recorder.urls().size()); |
| EXPECT_EQ(blink::mojom::RequestContextType::FORM, |
| installer.navigation_throttle()->request_context_type()); |
| |
| // Finishes the last navigation. |
| ASSERT_TRUE(post_manager.WaitForNavigationFinished()); |
| EXPECT_FALSE(installer.navigation_throttle()); |
| } |
| |
| // Checks that the error code is properly set on the NavigationHandle when a |
| // NavigationThrottle cancels. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| ErrorCodeOnThrottleCancelNavigation) { |
| const GURL kUrl = embedded_test_server()->GetURL("/title1.html"); |
| const GURL kRedirectingUrl = |
| embedded_test_server()->GetURL("/server-redirect?" + kUrl.spec()); |
| |
| { |
| // Set up a NavigationThrottle that will cancel the navigation in |
| // WillStartRequest. |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::CANCEL_AND_IGNORE, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| NavigationHandleObserver observer(shell()->web_contents(), kUrl); |
| |
| // Try to navigate to the url. The navigation should be canceled and the |
| // NavigationHandle should have the right error code. |
| EXPECT_FALSE(NavigateToURL(shell(), kUrl)); |
| EXPECT_EQ(net::ERR_ABORTED, observer.net_error_code()); |
| } |
| |
| { |
| // Set up a NavigationThrottle that will cancel the navigation in |
| // WillRedirectRequest. |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::CANCEL_AND_IGNORE, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| NavigationHandleObserver observer(shell()->web_contents(), kRedirectingUrl); |
| |
| // Try to navigate to the url. The navigation should be canceled and the |
| // NavigationHandle should have the right error code. |
| EXPECT_FALSE(NavigateToURL(shell(), kRedirectingUrl)); |
| EXPECT_EQ(net::ERR_ABORTED, observer.net_error_code()); |
| } |
| |
| { |
| // Set up a NavigationThrottle that will cancel the navigation in |
| // WillProcessResponse. |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::CANCEL_AND_IGNORE, NavigationThrottle::PROCEED); |
| NavigationHandleObserver observer(shell()->web_contents(), kUrl); |
| |
| // Try to navigate to the url. The navigation should be canceled and the |
| // NavigationHandle should have the right error code. |
| EXPECT_FALSE(NavigateToURL(shell(), kUrl)); |
| EXPECT_EQ(net::ERR_ABORTED, observer.net_error_code()); |
| } |
| |
| { |
| // Set up a NavigationThrottle that will block the navigation in |
| // WillStartRequest. |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::BLOCK_REQUEST, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| NavigationHandleObserver observer(shell()->web_contents(), kUrl); |
| |
| // Try to navigate to the url. The navigation should be canceled and the |
| // NavigationHandle should have the right error code. |
| EXPECT_FALSE(NavigateToURL(shell(), kUrl)); |
| EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, observer.net_error_code()); |
| } |
| |
| // Set up a NavigationThrottle that will block the navigation in |
| // WillRedirectRequest. |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::BLOCK_REQUEST, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| NavigationHandleObserver observer(shell()->web_contents(), kRedirectingUrl); |
| |
| // Try to navigate to the url. The navigation should be canceled and the |
| // NavigationHandle should have the right error code. |
| EXPECT_FALSE(NavigateToURL(shell(), kRedirectingUrl)); |
| EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, observer.net_error_code()); |
| } |
| |
| // Checks that there's no UAF if NavigationRequest::WillStartRequest cancels the |
| // navigation. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| CancelNavigationInWillStartRequest) { |
| const GURL kUrl1 = embedded_test_server()->GetURL("/title1.html"); |
| const GURL kUrl2 = embedded_test_server()->GetURL("/title2.html"); |
| // First make a successful commit, as this issue only reproduces when there |
| // are existing entries (i.e. when NavigationControllerImpl::GetVisibleEntry |
| // has safe_to_show_pending=false). |
| EXPECT_TRUE(NavigateToURL(shell(), kUrl1)); |
| |
| // To take the path that doesn't run beforeunload, so that |
| // NavigationControllerImpl::NavigateToPendingEntry is on the botttom of the |
| // stack when NavigationRequest::WillStartRequest is called. |
| CrashTab(shell()->web_contents()); |
| |
| // Set up a NavigationThrottle that will cancel the navigation in |
| // WillStartRequest. |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::CANCEL_AND_IGNORE, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| |
| EXPECT_FALSE(NavigateToURL(shell(), kUrl2)); |
| } |
| |
| // Verify that a cross-process navigation in a frame for which the current |
| // renderer process is not live will not result in leaking a |
| // RenderProcessHost. See https://crbug.com/949977. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| NoLeakFromStartingSiteInstance) { |
| IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(), |
| {"b.com"}); |
| |
| GURL url_a = embedded_test_server()->GetURL("a.com", "/title1.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| |
| // Kill the a.com process, to test what happens with the next navigation. |
| scoped_refptr<SiteInstance> site_instance_a = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| EXPECT_TRUE(site_instance_a->HasProcess()); |
| RenderProcessHost* process_1 = site_instance_a->GetProcess(); |
| RenderProcessHostWatcher process_exit_observer_1( |
| process_1, content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| RenderProcessHostWatcher rph_gone_observer_1( |
| process_1, content::RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION); |
| process_1->Shutdown(RESULT_CODE_KILLED); |
| process_exit_observer_1.Wait(); |
| |
| // Start to navigate the sad tab to another site. |
| GURL url_b = embedded_test_server()->GetURL("b.com", "/title2.html"); |
| TestNavigationManager navigation_b(shell()->web_contents(), url_b); |
| shell()->web_contents()->GetController().LoadURL( |
| url_b, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); |
| EXPECT_TRUE(navigation_b.WaitForRequestStart()); |
| |
| // The starting SiteInstance should be the SiteInstance of the current |
| // RenderFrameHost. |
| scoped_refptr<SiteInstance> starting_site_instance = |
| navigation_b.GetNavigationHandle()->GetStartingSiteInstance(); |
| EXPECT_EQ(shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(), |
| starting_site_instance); |
| if (ShouldSkipEarlyCommitPendingForCrashedFrame()) { |
| EXPECT_EQ(GURL("http://a.com"), starting_site_instance->GetSiteURL()); |
| } else { |
| // Because of the sad tab, this is actually the b.com SiteInstance, which |
| // commits immediately after starting the navigation and has a process. |
| EXPECT_EQ(GURL("http://b.com"), starting_site_instance->GetSiteURL()); |
| } |
| EXPECT_TRUE(starting_site_instance->HasProcess()); |
| |
| // In https://crbug.com/949977, we used the a.com SiteInstance here and didn't |
| // have a process, and an observer called GetProcess, creating a process. This |
| // RPH never went away, even after the SiteInstance was gone. Simulate this |
| // by creating a new RPH for site_instance_a directly. Note that the actual |
| // process may not get created (only if the spare process is in use), so wait |
| // for RPH destruction rather than process exit. |
| RenderProcessHost* rph_2 = site_instance_a->GetProcess(); |
| RenderProcessHostWatcher process_exit_observer_2( |
| rph_2, content::RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION); |
| ASSERT_TRUE(navigation_b.WaitForNavigationFinished()); |
| |
| // Ensure RPH 1 is destroyed, which happens at commit time even before the fix |
| // for the bug. |
| rph_gone_observer_1.Wait(); |
| |
| // Navigate to another process. This isn't necessary to trigger the original |
| // leak (when the starting SiteInstance was a.com), but it lets the test |
| // finish in the case that the starting SiteInstance is b.com, since b.com's |
| // process goes away with this navigation. |
| // TODO(creis): There's still a slight risk that other buggy code could find |
| // site_instance_a and call GetProcess() on it, causing a leak. We'll add a |
| // backup fix and test for that in a followup CL. |
| GURL url_c = embedded_test_server()->GetURL("c.com", "/title1.html"); |
| EXPECT_TRUE(NavigateToURL(shell()->web_contents(), url_c)); |
| |
| // Remove all references to site_instance_a so that we can be sure its process |
| // gets cleaned up. Pruning the NavigationEntry for url_a on the tab simulates |
| // closing the tab (from that SiteInstance's perspective). |
| site_instance_a = nullptr; |
| starting_site_instance = nullptr; |
| shell()->web_contents()->GetController().PruneAllButLastCommitted(); |
| |
| // Wait for rph_2 to exit when it's not used. This wouldn't happen when the |
| // bug was present. |
| process_exit_observer_2.Wait(); |
| } |
| |
| // Specialized test that verifies the NavigationHandle gets the HTTPS upgraded |
| // URL from the very beginning of the navigation. |
| class NavigationRequestHttpsUpgradeBrowserTest |
| : public NavigationRequestBrowserTest { |
| public: |
| void CheckHttpsUpgradedIframeNavigation(const GURL& start_url, |
| const GURL& iframe_secure_url) { |
| ASSERT_TRUE(start_url.SchemeIs(url::kHttpScheme)); |
| ASSERT_TRUE(iframe_secure_url.SchemeIs(url::kHttpsScheme)); |
| |
| NavigationStartUrlRecorder url_recorder(shell()->web_contents()); |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| TestNavigationManager navigation_manager(shell()->web_contents(), |
| iframe_secure_url); |
| |
| // Load the page and wait for the frame load with the expected URL. |
| // Note: if the test times out while waiting then a navigation to |
| // iframe_secure_url never happened and the expected upgrade may not be |
| // working. |
| shell()->LoadURL(start_url); |
| EXPECT_TRUE(navigation_manager.WaitForRequestStart()); |
| |
| // The main frame should have finished navigating while the iframe should |
| // have just started. |
| EXPECT_EQ(2, installer.will_start_called()); |
| EXPECT_EQ(0, installer.will_redirect_called()); |
| EXPECT_EQ(1, installer.will_process_called()); |
| |
| // Check the correct start URLs have been registered. |
| EXPECT_EQ(iframe_secure_url, url_recorder.urls().back()); |
| EXPECT_EQ(start_url, url_recorder.urls().front()); |
| EXPECT_EQ(2ul, url_recorder.urls().size()); |
| } |
| }; |
| |
| // Tests that the start URL is HTTPS upgraded for a same site navigation. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestHttpsUpgradeBrowserTest, |
| StartUrlIsHttpsUpgradedSameSite) { |
| GURL start_url(embedded_test_server()->GetURL( |
| "example.com", "/https_upgrade_same_site.html")); |
| |
| // Builds the expected upgraded same site URL. |
| GURL::Replacements replace_scheme; |
| replace_scheme.SetSchemeStr("https"); |
| GURL cross_site_iframe_secure_url = |
| embedded_test_server() |
| ->GetURL("example.com", "/title1.html") |
| .ReplaceComponents(replace_scheme); |
| |
| CheckHttpsUpgradedIframeNavigation(start_url, cross_site_iframe_secure_url); |
| } |
| |
| // Tests that the start URL is HTTPS upgraded for a cross site navigation. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestHttpsUpgradeBrowserTest, |
| StartUrlIsHttpsUpgradedCrossSite) { |
| GURL start_url( |
| embedded_test_server()->GetURL("/https_upgrade_cross_site.html")); |
| GURL cross_site_iframe_secure_url("https://other.com/title1.html"); |
| |
| CheckHttpsUpgradedIframeNavigation(start_url, cross_site_iframe_secure_url); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| BrowserInitiatedMainFrameReload) { |
| GURL url = embedded_test_server()->GetURL("/hello.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| NavigationHandleObserver handle_observer(shell()->web_contents(), url); |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->Reload(); |
| navigation_observer.Wait(); |
| |
| EXPECT_EQ(handle_observer.reload_type(), ReloadType::NORMAL); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| BrowserInitiatedSubFrameReload) { |
| GURL url = embedded_test_server()->GetURL("/page_with_iframe.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| NavigationHandleObserver handle_observer( |
| shell()->web_contents(), embedded_test_server()->GetURL("/title1.html")); |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| |
| RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>( |
| shell()->web_contents()->GetPrimaryMainFrame()); |
| ASSERT_EQ(1u, main_frame->child_count()); |
| FrameTreeNode* child = main_frame->child_at(0u); |
| auto* navigation_controller = static_cast<NavigationControllerImpl*>( |
| &shell()->web_contents()->GetController()); |
| navigation_controller->ReloadFrame(child); |
| navigation_observer.Wait(); |
| |
| EXPECT_EQ(handle_observer.reload_type(), ReloadType::NORMAL); |
| EXPECT_FALSE(handle_observer.is_main_frame()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| RendererInitiatedMainFrameReload) { |
| GURL url = embedded_test_server()->GetURL("/hello.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| EXPECT_TRUE(ExecJs(shell(), "location.reload();")); |
| EXPECT_EQ(observer.reload_type(), ReloadType::NORMAL); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| RendererInitiatedSubFrameReload) { |
| GURL url = embedded_test_server()->GetURL("/page_with_iframe.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| NavigationHandleObserver handle_observer( |
| shell()->web_contents(), embedded_test_server()->GetURL("/title1.html")); |
| EXPECT_TRUE(ExecJs(shell(), |
| "document.getElementById('test_iframe')." |
| "contentWindow.location.reload();")); |
| EXPECT_EQ(handle_observer.reload_type(), ReloadType::NORMAL); |
| EXPECT_FALSE(handle_observer.is_main_frame()); |
| } |
| |
| // Ensure that browser-initiated same-document navigations are detected and |
| // don't issue network requests. See crbug.com/663777. |
| // Browser-initiated same-document navigations should trigger a |
| // WillCommitWithoutUrlLoader() callback, instead of the WillStartRequest() |
| // and WillProcessResponse() callbacks used when there is a network request. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| SameDocumentBrowserInitiatedNoReload) { |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| GURL url_fragment_1(embedded_test_server()->GetURL("/title1.html#id_1")); |
| GURL url_fragment_2(embedded_test_server()->GetURL("/title1.html#id_2")); |
| |
| // 1) Perform a new-document navigation. |
| { |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| EXPECT_EQ(1, installer.will_start_called()); |
| EXPECT_EQ(1, installer.will_process_called()); |
| EXPECT_EQ(0, installer.will_commit_without_url_loader_called()); |
| EXPECT_FALSE(observer.is_same_document()); |
| } |
| |
| // 2) Perform a same-document navigation by adding a fragment. |
| { |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| NavigationHandleObserver observer(shell()->web_contents(), url_fragment_1); |
| EXPECT_TRUE(NavigateToURL(shell(), url_fragment_1)); |
| EXPECT_EQ(0, installer.will_start_called()); |
| EXPECT_EQ(0, installer.will_process_called()); |
| EXPECT_EQ(1, installer.will_commit_without_url_loader_called()); |
| EXPECT_TRUE(observer.is_same_document()); |
| } |
| |
| // 3) Perform a same-document navigation by modifying the fragment. |
| { |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| NavigationHandleObserver observer(shell()->web_contents(), url_fragment_2); |
| EXPECT_TRUE(NavigateToURL(shell(), url_fragment_2)); |
| EXPECT_EQ(0, installer.will_start_called()); |
| EXPECT_EQ(0, installer.will_process_called()); |
| EXPECT_EQ(1, installer.will_commit_without_url_loader_called()); |
| EXPECT_TRUE(observer.is_same_document()); |
| } |
| |
| // 4) Redo the last navigation, but this time it should trigger a reload. |
| { |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| NavigationHandleObserver observer(shell()->web_contents(), url_fragment_2); |
| EXPECT_TRUE(NavigateToURL(shell(), url_fragment_2)); |
| EXPECT_EQ(1, installer.will_start_called()); |
| EXPECT_EQ(1, installer.will_process_called()); |
| EXPECT_EQ(0, installer.will_commit_without_url_loader_called()); |
| EXPECT_FALSE(observer.is_same_document()); |
| } |
| |
| // 5) Perform a new-document navigation by removing the fragment. |
| { |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| EXPECT_EQ(1, installer.will_start_called()); |
| EXPECT_EQ(1, installer.will_process_called()); |
| EXPECT_EQ(0, installer.will_commit_without_url_loader_called()); |
| EXPECT_FALSE(observer.is_same_document()); |
| } |
| } |
| |
| class NavigationRequestHostResolutionFailureTest : public ContentBrowserTest { |
| protected: |
| void SetUpOnMainThread() override { |
| host_resolver()->AddSimulatedTimeoutFailure("*"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestHostResolutionFailureTest, |
| HostResolutionFailure) { |
| GURL url(embedded_test_server()->GetURL("example.com", "/title1.html")); |
| |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| |
| EXPECT_FALSE(NavigateToURL(shell(), url)); |
| |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_TRUE(observer.is_error()); |
| EXPECT_EQ(net::ERR_NAME_NOT_RESOLVED, observer.net_error_code()); |
| EXPECT_EQ(net::ERR_DNS_TIMED_OUT, observer.resolve_error_info().error); |
| } |
| |
| // Record and list the navigations that are started and finished. |
| class NavigationLogger : public WebContentsObserver { |
| public: |
| explicit NavigationLogger(WebContents* web_contents) |
| : WebContentsObserver(web_contents) {} |
| |
| void DidStartNavigation(NavigationHandle* navigation_handle) override { |
| started_navigation_urls_.push_back(navigation_handle->GetURL()); |
| } |
| |
| void DidRedirectNavigation(NavigationHandle* navigation_handle) override { |
| redirected_navigation_urls_.push_back(navigation_handle->GetURL()); |
| } |
| |
| void DidFinishNavigation(NavigationHandle* navigation_handle) override { |
| finished_navigation_urls_.push_back(navigation_handle->GetURL()); |
| } |
| |
| const std::vector<GURL>& started_navigation_urls() const { |
| return started_navigation_urls_; |
| } |
| const std::vector<GURL>& redirected_navigation_urls() const { |
| return redirected_navigation_urls_; |
| } |
| const std::vector<GURL>& finished_navigation_urls() const { |
| return finished_navigation_urls_; |
| } |
| |
| private: |
| std::vector<GURL> started_navigation_urls_; |
| std::vector<GURL> redirected_navigation_urls_; |
| std::vector<GURL> finished_navigation_urls_; |
| }; |
| |
| // Verifies that when a navigation is blocked after a redirect, the renderer |
| // doesn't try to commit an error page to the pre-redirect URL. This would cause |
| // a NavigationHandle mismatch and a new NavigationHandle creation to commit |
| // the error page. This test makes sure that only one NavigationHandle is used |
| // for committing the error page. See https://crbug.com/695421 |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, BlockedOnRedirect) { |
| const GURL kUrl = embedded_test_server()->GetURL("/title1.html"); |
| const GURL kRedirectingUrl = |
| embedded_test_server()->GetURL("/server-redirect?" + kUrl.spec()); |
| |
| // Set up a NavigationThrottle that will block the navigation in |
| // WillRedirectRequest. |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::BLOCK_REQUEST, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| NavigationHandleObserver observer(shell()->web_contents(), kRedirectingUrl); |
| NavigationLogger logger(shell()->web_contents()); |
| |
| // Try to navigate to the url. The navigation should be canceled and the |
| // NavigationHandle should have the right error code. |
| EXPECT_FALSE(NavigateToURL(shell(), kRedirectingUrl)); |
| // EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, observer.net_error_code()); |
| |
| // Only one navigation is expected to happen. |
| std::vector<GURL> started_navigation = {kRedirectingUrl}; |
| EXPECT_EQ(started_navigation, logger.started_navigation_urls()); |
| |
| std::vector<GURL> finished_navigation = {kUrl}; |
| EXPECT_EQ(finished_navigation, logger.finished_navigation_urls()); |
| } |
| |
| // Tests that when a navigation starts while there's an existing one, the first |
| // one has the right error code set on its navigation handle. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, ErrorCodeOnCancel) { |
| GURL slow_url = embedded_test_server()->GetURL("/slow?60"); |
| NavigationHandleObserver observer(shell()->web_contents(), slow_url); |
| shell()->LoadURL(slow_url); |
| |
| GURL url2(embedded_test_server()->GetURL("/title1.html")); |
| TestNavigationObserver same_tab_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(url2); |
| same_tab_observer.Wait(); |
| |
| EXPECT_EQ(net::ERR_ABORTED, observer.net_error_code()); |
| } |
| |
| // Tests that when a renderer-initiated request redirects to a URL that the |
| // renderer can't access, the right error code is set on the NavigationHandle. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, ErrorCodeOnRedirect) { |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL redirect_url = |
| embedded_test_server()->GetURL(std::string("/server-redirect?") + |
| blink::kChromeUINetworkErrorsListingURL); |
| NavigationHandleObserver observer(shell()->web_contents(), redirect_url); |
| TestNavigationObserver same_tab_observer(shell()->web_contents(), 1); |
| EXPECT_TRUE(ExecJs(shell(), base::StringPrintf("location.href = '%s';", |
| redirect_url.spec().c_str()))); |
| same_tab_observer.Wait(); |
| EXPECT_EQ(net::ERR_UNSAFE_REDIRECT, observer.net_error_code()); |
| } |
| |
| // Test to verify that error pages caused by NavigationThrottle blocking a |
| // request in the main frame from being made are properly committed in a |
| // separate error page process. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| ErrorPageBlockedNavigation) { |
| GURL start_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); |
| GURL blocked_url(embedded_test_server()->GetURL("bar.com", "/title2.html")); |
| |
| { |
| NavigationHandleObserver observer(shell()->web_contents(), start_url); |
| EXPECT_TRUE(NavigateToURL(shell(), start_url)); |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| } |
| |
| scoped_refptr<SiteInstance> site_instance = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| |
| auto installer = std::make_unique<TestNavigationThrottleInstaller>( |
| shell()->web_contents(), NavigationThrottle::BLOCK_REQUEST, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| |
| { |
| // A blocked, renderer-initiated navigation in the main frame should commit |
| // an error page in a new process. |
| NavigationHandleObserver observer(shell()->web_contents(), blocked_url); |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| EXPECT_TRUE( |
| ExecJs(shell(), base::StringPrintf("location.href = '%s'", |
| blocked_url.spec().c_str()))); |
| navigation_observer.Wait(); |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_TRUE(observer.is_error()); |
| if (SiteIsolationPolicy::IsErrorPageIsolationEnabled(true)) { |
| EXPECT_NE( |
| site_instance, |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); |
| EXPECT_EQ(kUnreachableWebDataURL, shell() |
| ->web_contents() |
| ->GetPrimaryMainFrame() |
| ->GetSiteInstance() |
| ->GetSiteURL()); |
| } else { |
| EXPECT_EQ( |
| site_instance, |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); |
| } |
| } |
| |
| { |
| // Reloading the blocked document from the browser process still ends up |
| // in the error page process. |
| int process_id = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| NavigationHandleObserver observer(shell()->web_contents(), blocked_url); |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| |
| shell()->Reload(); |
| navigation_observer.Wait(); |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_TRUE(observer.is_error()); |
| if (SiteIsolationPolicy::IsErrorPageIsolationEnabled(true)) { |
| EXPECT_EQ(kUnreachableWebDataURL, shell() |
| ->web_contents() |
| ->GetPrimaryMainFrame() |
| ->GetSiteInstance() |
| ->GetSiteURL()); |
| EXPECT_EQ(process_id, shell() |
| ->web_contents() |
| ->GetPrimaryMainFrame() |
| ->GetProcess() |
| ->GetID()); |
| } else if (AreAllSitesIsolatedForTesting()) { |
| EXPECT_NE( |
| site_instance, |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); |
| } else { |
| EXPECT_EQ( |
| site_instance, |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); |
| } |
| } |
| |
| installer.reset(); |
| |
| { |
| // With the throttle uninstalled, going back should return to |start_url| in |
| // the same process, and clear the error page. |
| NavigationHandleObserver observer(shell()->web_contents(), start_url); |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| |
| shell()->GoBackOrForward(-1); |
| navigation_observer.Wait(); |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| EXPECT_EQ( |
| site_instance, |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); |
| } |
| |
| installer = std::make_unique<TestNavigationThrottleInstaller>( |
| shell()->web_contents(), NavigationThrottle::BLOCK_REQUEST, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| |
| { |
| // A blocked, browser-initiated navigation should commit an error page in a |
| // different process. |
| NavigationHandleObserver observer(shell()->web_contents(), blocked_url); |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| |
| EXPECT_FALSE(NavigateToURL(shell(), blocked_url)); |
| |
| navigation_observer.Wait(); |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_TRUE(observer.is_error()); |
| EXPECT_NE( |
| site_instance, |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); |
| if (SiteIsolationPolicy::IsErrorPageIsolationEnabled(true)) { |
| EXPECT_EQ(kUnreachableWebDataURL, shell() |
| ->web_contents() |
| ->GetPrimaryMainFrame() |
| ->GetSiteInstance() |
| ->GetSiteURL()); |
| } |
| } |
| |
| installer.reset(); |
| |
| { |
| // A blocked subframe navigation should commit an error page in the error |
| // page process or stay in the same process, based on the isolation policy. |
| EXPECT_TRUE(NavigateToURL(shell(), start_url)); |
| const std::string javascript = |
| "var i = document.createElement('iframe');" |
| "i.src = '" + |
| blocked_url.spec() + |
| "';" |
| "document.body.appendChild(i);"; |
| |
| installer = std::make_unique<TestNavigationThrottleInstaller>( |
| shell()->web_contents(), NavigationThrottle::BLOCK_REQUEST, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| |
| content::RenderFrameHost* rfh = |
| shell()->web_contents()->GetPrimaryMainFrame(); |
| scoped_refptr<SiteInstance> initial_site_instance = rfh->GetSiteInstance(); |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| ASSERT_TRUE(content::ExecJs(rfh, javascript)); |
| navigation_observer.Wait(); |
| |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| ASSERT_EQ(1u, root->child_count()); |
| FrameTreeNode* child = root->child_at(0u); |
| |
| EXPECT_TRUE(IsExpectedSubframeErrorTransition( |
| initial_site_instance.get(), |
| child->current_frame_host()->GetSiteInstance())); |
| } |
| } |
| |
| // Test to verify that error pages caused by network error or other |
| // recoverable error are properly committed in the process for the |
| // destination URL. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, ErrorPageNetworkError) { |
| GURL start_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); |
| GURL error_url(embedded_test_server()->GetURL("/close-socket")); |
| EXPECT_NE(start_url.host(), error_url.host()); |
| GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(&net::URLRequestFailedJob::AddUrlHandler)); |
| |
| { |
| NavigationHandleObserver observer(shell()->web_contents(), start_url); |
| EXPECT_TRUE(NavigateToURL(shell(), start_url)); |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_error()); |
| } |
| |
| scoped_refptr<SiteInstance> site_instance = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| { |
| NavigationHandleObserver observer(shell()->web_contents(), error_url); |
| EXPECT_FALSE(NavigateToURL(shell(), error_url)); |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_TRUE(observer.is_error()); |
| EXPECT_NE( |
| site_instance, |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); |
| if (SiteIsolationPolicy::IsErrorPageIsolationEnabled(true)) { |
| EXPECT_EQ(kUnreachableWebDataURL, shell() |
| ->web_contents() |
| ->GetPrimaryMainFrame() |
| ->GetSiteInstance() |
| ->GetSiteURL()); |
| } |
| } |
| } |
| |
| class ReadyToCommitObserver : public WebContentsObserver { |
| public: |
| explicit ReadyToCommitObserver(WebContents* web_contents) { |
| WebContentsObserver::Observe(web_contents); |
| } |
| |
| bool ReadyToCommitNavigationWasCalled() const { |
| return ready_to_commit_navigation_called_; |
| } |
| |
| protected: |
| void ReadyToCommitNavigation(NavigationHandle* navigation_handle) override { |
| ready_to_commit_navigation_called_ = true; |
| } |
| |
| bool ready_to_commit_navigation_called_ = false; |
| }; |
| |
| // Ensure that adding a deferring condition that's already satisfied when |
| // checked (i.e. can return synchronously) doesn't block commit. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| SynchronouslyCompleteCommitDeferringCondition) { |
| GURL simple_url(embedded_test_server()->GetURL("/simple_page.html")); |
| |
| TestNavigationManager manager(shell()->web_contents(), simple_url); |
| WebContents* web_contents = shell()->web_contents(); |
| ReadyToCommitObserver observer(web_contents); |
| |
| MockCommitDeferringConditionInstaller installer( |
| simple_url, CommitDeferringCondition::Result::kProceed); |
| |
| shell()->LoadURL(simple_url); |
| ASSERT_TRUE(manager.WaitForResponse()); |
| manager.ResumeNavigation(); |
| |
| // Ready to commit should be reached synchronously after a response. |
| EXPECT_TRUE(installer.condition().WasInvoked()); |
| EXPECT_TRUE(observer.ReadyToCommitNavigationWasCalled()); |
| EXPECT_TRUE(manager.GetNavigationHandle()->IsWaitingToCommit()); |
| |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| } |
| |
| // Ensure asynchronously deferring conditions block the navigation when it's |
| // ready to commit. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| AsyncCommitDeferringCondition) { |
| GURL simple_url(embedded_test_server()->GetURL("/simple_page.html")); |
| |
| TestNavigationManager manager(shell()->web_contents(), simple_url); |
| WebContents* web_contents = shell()->web_contents(); |
| |
| MockCommitDeferringConditionInstaller installer1( |
| simple_url, CommitDeferringCondition::Result::kDefer); |
| MockCommitDeferringConditionInstaller installer2( |
| simple_url, CommitDeferringCondition::Result::kDefer); |
| |
| ReadyToCommitObserver observer(web_contents); |
| |
| shell()->LoadURL(simple_url); |
| ASSERT_TRUE(manager.WaitForResponse()); |
| manager.ResumeNavigation(); |
| |
| NavigationRequest* request = |
| static_cast<NavigationRequest*>(manager.GetNavigationHandle()); |
| |
| // The navigation should not have proceeded through to ReadyToCommit because |
| // the first condition is deferring it. The second condition should not be |
| // checked until the first is resolved. |
| EXPECT_LT(request->state(), NavigationRequest::READY_TO_COMMIT); |
| EXPECT_FALSE(observer.ReadyToCommitNavigationWasCalled()); |
| EXPECT_TRUE(installer1.condition().WasInvoked()); |
| EXPECT_FALSE(installer2.condition().WasInvoked()); |
| EXPECT_TRUE(request->IsCommitDeferringConditionDeferredForTesting()); |
| |
| // Resume from the first condition. This should now block on the second |
| // condition. |
| installer1.condition().CallResumeClosure(); |
| EXPECT_LT(request->state(), NavigationRequest::READY_TO_COMMIT); |
| EXPECT_FALSE(observer.ReadyToCommitNavigationWasCalled()); |
| EXPECT_TRUE(installer2.condition().WasInvoked()); |
| |
| // Resuming from the second condition should now resume the navigaiton. This |
| // should call ReadyToCommit and commit the navigation. |
| installer2.condition().CallResumeClosure(); |
| EXPECT_TRUE(observer.ReadyToCommitNavigationWasCalled()); |
| EXPECT_EQ(request->state(), NavigationRequest::READY_TO_COMMIT); |
| EXPECT_FALSE(request->IsCommitDeferringConditionDeferredForTesting()); |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| } |
| |
| // Ensure a navigation can be cancelled while an asynchronously deferring |
| // condition is blocking commit. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| CancelWhileCommitDeferred) { |
| GURL simple_url(embedded_test_server()->GetURL("/simple_page.html")); |
| |
| TestNavigationManager manager(shell()->web_contents(), simple_url); |
| WebContents* web_contents = shell()->web_contents(); |
| |
| MockCommitDeferringConditionInstaller installer1( |
| simple_url, CommitDeferringCondition::Result::kDefer); |
| |
| // We'll cancel the navigation while the first condition is deferred so this |
| // is added only to make sure it's never invoked. |
| MockCommitDeferringConditionInstaller installer2( |
| simple_url, CommitDeferringCondition::Result::kDefer); |
| |
| shell()->LoadURL(simple_url); |
| ASSERT_TRUE(manager.WaitForResponse()); |
| manager.ResumeNavigation(); |
| |
| NavigationRequest* request = |
| static_cast<NavigationRequest*>(manager.GetNavigationHandle()); |
| |
| // The navigation should have passed all checks but is now deferred from |
| // committing by |condition|. |
| EXPECT_LT(request->state(), NavigationRequest::READY_TO_COMMIT); |
| EXPECT_TRUE(installer1.condition().WasInvoked()); |
| EXPECT_TRUE(request->IsCommitDeferringConditionDeferredForTesting()); |
| |
| // While the commit is deferred, cancel the navigation. This should delete |
| // the navigation request. |
| EXPECT_FALSE(installer1.condition().IsDestroyed()); |
| web_contents->Stop(); |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| EXPECT_EQ(manager.GetNavigationHandle(), nullptr); |
| EXPECT_TRUE(installer1.condition().IsDestroyed()); |
| EXPECT_TRUE(installer2.condition().IsDestroyed()); |
| |
| // Call resume on `installer1`'s condition, as could happen when e.g. the |
| // renderer responds after the navigation is stopped. Make sure we don't |
| // crash. |
| installer1.condition().CallResumeClosure(); |
| |
| EXPECT_FALSE(installer2.condition().WasInvoked()); |
| } |
| |
| // Ensure throttles registered by tests using RegisterThrottleForTesting() are |
| // executed after those registered by the WebContents' browser client (i.e. how |
| // non-test throttles are normally registered). |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| RegisterThrottleForTestingIsLast) { |
| WebContents* web_contents = shell()->web_contents(); |
| GURL simple_url(embedded_test_server()->GetURL("/simple_page.html")); |
| |
| TestNavigationThrottle* client_throttle = nullptr; |
| |
| // Set the client to register a TestNavigationThrottle that defers in |
| // WillStartRequest. We'll save a pointer to this throttle in |
| // |client_throttle| when its registered. |
| content::ShellContentBrowserClient::Get() |
| ->set_create_throttles_for_navigation_callback(base::BindLambdaForTesting( |
| [&client_throttle](content::NavigationHandle* handle) |
| -> std::vector<std::unique_ptr<content::NavigationThrottle>> { |
| std::vector<std::unique_ptr<content::NavigationThrottle>> throttles; |
| std::unique_ptr<TestNavigationThrottle> throttle( |
| new TestNavigationThrottle( |
| handle, NavigationThrottle::DEFER, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| base::DoNothing(), base::DoNothing(), base::DoNothing(), |
| base::DoNothing(), base::DoNothing())); |
| client_throttle = throttle.get(); |
| throttles.push_back(std::move(throttle)); |
| return throttles; |
| })); |
| |
| // Add another similar throttle using the installer which will use |
| // RegisterThrottleForTesting and registers throttles in DidStartNavigation, |
| // before browser client throttles are registered. |
| TestNavigationThrottleInstaller test_throttle_installer( |
| web_contents, NavigationThrottle::DEFER, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED); |
| |
| // Start navigating. |
| TestNavigationManager manager(shell()->web_contents(), simple_url); |
| shell()->LoadURL(simple_url); |
| auto* handle = manager.GetNavigationHandle(); |
| auto* runner = |
| NavigationRequest::From(handle)->GetNavigationThrottleRunnerForTesting(); |
| |
| // The navigation should have been deferred by one of our throttles. Ensure |
| // it's the client throttle since we explicitly want test throttles to |
| // execute after all others. |
| ASSERT_TRUE(handle->IsDeferredForTesting()); |
| ASSERT_NE(client_throttle, nullptr); |
| EXPECT_EQ(runner->GetDeferringThrottle(), client_throttle); |
| |
| // Now when we resume we should get deferred by the other throttle. This |
| // should be the throttle installed via RegisterThrottleForTesting. |
| client_throttle->ResumeNavigation(); |
| ASSERT_TRUE(handle->IsDeferredForTesting()); |
| EXPECT_EQ(runner->GetDeferringThrottle(), |
| test_throttle_installer.navigation_throttle()); |
| |
| // Finish the navigation. |
| test_throttle_installer.navigation_throttle()->ResumeNavigation(); |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| } |
| |
| // Tests the case where a browser-initiated navigation to a normal webpage is |
| // blocked (net::ERR_BLOCKED_BY_CLIENT) while departing from a privileged WebUI |
| // page (chrome://gpu). It is a security risk for the error page to commit in |
| // the privileged process. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, BlockedRequestAfterWebUI) { |
| GURL web_ui_url(GetWebUIURL("gpu")); |
| WebContents* web_contents = shell()->web_contents(); |
| |
| // Navigate to the initial page. |
| EXPECT_FALSE(web_contents->GetPrimaryMainFrame()->GetEnabledBindings() & |
| BINDINGS_POLICY_WEB_UI); |
| EXPECT_TRUE(NavigateToURL(shell(), web_ui_url)); |
| EXPECT_TRUE(web_contents->GetPrimaryMainFrame()->GetEnabledBindings() & |
| BINDINGS_POLICY_WEB_UI); |
| scoped_refptr<SiteInstance> web_ui_process = web_contents->GetSiteInstance(); |
| |
| // Start a new, non-webUI navigation that will be blocked by a |
| // NavigationThrottle. |
| GURL blocked_url("http://blocked-by-throttle.example.cc"); |
| TestNavigationThrottleInstaller installer( |
| web_contents, NavigationThrottle::BLOCK_REQUEST, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| NavigationHandleObserver commit_observer(web_contents, blocked_url); |
| EXPECT_FALSE(NavigateToURL(shell(), blocked_url)); |
| NavigationEntry* last_committed = |
| web_contents->GetController().GetLastCommittedEntry(); |
| EXPECT_TRUE(last_committed); |
| EXPECT_EQ(blocked_url, last_committed->GetVirtualURL()); |
| EXPECT_EQ(PAGE_TYPE_ERROR, last_committed->GetPageType()); |
| EXPECT_NE(web_ui_process.get(), web_contents->GetSiteInstance()); |
| EXPECT_TRUE(commit_observer.has_committed()); |
| EXPECT_TRUE(commit_observer.is_error()); |
| EXPECT_FALSE(commit_observer.is_renderer_initiated()); |
| } |
| |
| // Redirects to renderer debug URLs caused problems. |
| // See https://crbug.com/728398. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| RedirectToRendererDebugUrl) { |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| const GURL kTestUrls[] = {GURL("javascript:window.alert('hello')"), |
| GURL(blink::kChromeUIBadCastCrashURL), |
| GURL(blink::kChromeUICrashURL), |
| GURL(blink::kChromeUIDumpURL), |
| GURL(blink::kChromeUIKillURL), |
| GURL(blink::kChromeUIHangURL), |
| GURL(blink::kChromeUIShorthangURL), |
| GURL(blink::kChromeUIMemoryExhaustURL)}; |
| |
| for (const auto& test_url : kTestUrls) { |
| SCOPED_TRACE(testing::Message() << "renderer_debug_url = " << test_url); |
| |
| GURL redirecting_url = |
| embedded_test_server()->GetURL("/server-redirect?" + test_url.spec()); |
| |
| NavigationHandleObserver observer(shell()->web_contents(), redirecting_url); |
| NavigationLogger logger(shell()->web_contents()); |
| |
| // Try to navigate to the url. The navigation should be canceled and the |
| // NavigationHandle should have the right error code. Note that javascript |
| // URLS use ERR_ABORTED rather than ERR_UNSAFE_REDIRECT due to |
| // https://crbug.com/941653. |
| EXPECT_FALSE(NavigateToURL(shell(), redirecting_url)); |
| int expected_err_code = test_url.SchemeIs("javascript") |
| ? net::ERR_ABORTED |
| : net::ERR_UNSAFE_REDIRECT; |
| EXPECT_EQ(expected_err_code, observer.net_error_code()); |
| |
| // Both WebContentsObserver::{DidStartNavigation, DidFinishNavigation} |
| // are called, but no WebContentsObserver::DidRedirectNavigation. |
| std::vector<GURL> started_navigation = {redirecting_url}; |
| std::vector<GURL> redirected_navigation; // Empty. |
| std::vector<GURL> finished_navigation = {redirecting_url}; |
| EXPECT_EQ(started_navigation, logger.started_navigation_urls()); |
| EXPECT_EQ(redirected_navigation, logger.redirected_navigation_urls()); |
| EXPECT_EQ(finished_navigation, logger.finished_navigation_urls()); |
| } |
| } |
| |
| // Check that iframe with embedded credentials are blocked. |
| // See https://crbug.com/755892. |
| // TODO(crbug.com/1262910): Enable the test again. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| DISABLED_BlockCredentialedSubresources) { |
| const struct { |
| GURL main_url; |
| GURL iframe_url; |
| bool blocked; |
| } kTestCases[] = { |
| // Normal case with no credential in neither of the urls. |
| {GURL("http://a.com/frame_tree/page_with_one_frame.html"), |
| GURL("http://a.com/title1.html"), false}, |
| |
| // Username in the iframe, but nothing in the main frame. |
| {GURL("http://a.com/frame_tree/page_with_one_frame.html"), |
| GURL("http://[email protected]/title1.html"), true}, |
| |
| // Username and password in the iframe, but none in the main frame. |
| {GURL("http://a.com/frame_tree/page_with_one_frame.html"), |
| GURL("http://user:[email protected]/title1.html"), true}, |
| |
| // Username and password in the main frame, but none in the iframe. |
| {GURL("http://user:[email protected]/frame_tree/page_with_one_frame.html"), |
| GURL("http://a.com/title1.html"), false}, |
| |
| // Same usernames in both frames. |
| // Relative URLs on top-level pages that were loaded with embedded |
| // credentials should load correctly. |
| {GURL("http://[email protected]/frame_tree/page_with_one_frame.html"), |
| GURL("http://[email protected]/title1.html"), false}, |
| |
| // Same usernames and passwords in both frames. |
| // Relative URLs on top-level pages that were loaded with embedded |
| // credentials should load correctly. |
| {GURL("http://user:[email protected]/frame_tree/page_with_one_frame.html"), |
| GURL("http://user:[email protected]/title1.html"), false}, |
| |
| // Different usernames. |
| {GURL("http://[email protected]/frame_tree/page_with_one_frame.html"), |
| GURL("http://[email protected]/title1.html"), true}, |
| |
| // Different passwords. |
| {GURL("http://user:[email protected]/frame_tree/page_with_one_frame.html"), |
| GURL("http://user:[email protected]/title1.html"), true}, |
| |
| // Different usernames and same passwords. |
| {GURL("http://user:[email protected]/frame_tree/page_with_one_frame.html"), |
| GURL("http://wrong:[email protected]/title1.html"), true}, |
| |
| // Different origins. |
| {GURL("http://user:[email protected]/frame_tree/page_with_one_frame.html"), |
| GURL("http://user:[email protected]/title1.html"), true}, |
| }; |
| for (const auto& test_case : kTestCases) { |
| // Modify the URLs port to use the embedded test server's port. |
| std::string port_str(base::NumberToString(embedded_test_server()->port())); |
| GURL::Replacements set_port; |
| set_port.SetPortStr(port_str); |
| GURL main_url(test_case.main_url.ReplaceComponents(set_port)); |
| GURL iframe_url_final(test_case.iframe_url.ReplaceComponents(set_port)); |
| GURL iframe_url_with_redirect = GURL(embedded_test_server()->GetURL( |
| "/server-redirect?" + iframe_url_final.spec())); |
| |
| ASSERT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| // Blocking the request must work, even after a redirect. |
| for (bool redirect : {false, true}) { |
| const GURL& iframe_url = |
| redirect ? iframe_url_with_redirect : iframe_url_final; |
| SCOPED_TRACE(::testing::Message() |
| << std::endl |
| << "- main_url = " << main_url << std::endl |
| << "- iframe_url = " << iframe_url << std::endl); |
| NavigationHandleObserver subframe_observer(shell()->web_contents(), |
| iframe_url); |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| |
| NavigateIframeToURL(shell()->web_contents(), "child0", iframe_url); |
| |
| FrameTreeNode* root = |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| ASSERT_EQ(1u, root->child_count()); |
| if (test_case.blocked) { |
| EXPECT_EQ(redirect, !!installer.will_start_called()); |
| EXPECT_FALSE(installer.will_process_called()); |
| EXPECT_FALSE(subframe_observer.has_committed()); |
| EXPECT_TRUE(subframe_observer.last_committed_url().is_empty()); |
| EXPECT_NE(iframe_url_final, root->child_at(0u)->current_url()); |
| } else { |
| EXPECT_TRUE(installer.will_start_called()); |
| EXPECT_TRUE(installer.will_process_called()); |
| EXPECT_TRUE(subframe_observer.has_committed()); |
| EXPECT_EQ(iframe_url_final, subframe_observer.last_committed_url()); |
| EXPECT_EQ(iframe_url_final, root->child_at(0u)->current_url()); |
| } |
| } |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, StartToCommitMetrics) { |
| enum class FrameType { |
| kMain, |
| kSub, |
| }; |
| enum class ProcessType { |
| kCross, |
| kSame, |
| }; |
| enum class TransitionType { |
| kNew, |
| kReload, |
| kBackForward, |
| }; |
| |
| // Uses the provided ProcessType, FrameType, and TransitionType expected for |
| // this navigation to generate all combinations of Navigation.StartToCommit |
| // metrics. |
| auto check_navigation = [](const base::HistogramTester& histograms, |
| ProcessType process_type, FrameType frame_type, |
| TransitionType transition_type) { |
| const std::map<FrameType, std::string> kFrameSuffixes = { |
| {FrameType::kMain, ".MainFrame"}, {FrameType::kSub, ".Subframe"}}; |
| const std::map<ProcessType, std::string> kProcessSuffixes = { |
| {ProcessType::kCross, ".CrossProcess"}, |
| {ProcessType::kSame, ".SameProcess"}}; |
| const std::map<TransitionType, std::string> kTransitionSuffixes = { |
| {TransitionType::kNew, ".NewNavigation"}, |
| {TransitionType::kReload, ".Reload"}, |
| {TransitionType::kBackForward, ".BackForward"}, |
| }; |
| |
| // Add the suffix to all existing histogram names, and append the results to |
| // |names|. |
| std::vector<std::string> names{"Navigation.StartToCommit"}; |
| auto add_suffix = [&names](std::vector<std::string> suffixes) { |
| size_t original_size = names.size(); |
| for (size_t i = 0; i < original_size; i++) { |
| for (const std::string& suffix : suffixes) |
| names.push_back(names[i] + suffix); |
| } |
| }; |
| add_suffix({kProcessSuffixes.at(process_type)}); |
| add_suffix({kFrameSuffixes.at(frame_type)}); |
| add_suffix({kTransitionSuffixes.at(transition_type), |
| ".ForegroundProcessPriority"}); |
| |
| // Check that all generated histogram names are logged exactly once. |
| for (const auto& name : names) { |
| histograms.ExpectTotalCount(name, 1); |
| } |
| |
| // Check that no additional histograms with the StartToCommit prefix were |
| // logged. |
| base::HistogramTester::CountsMap counts = |
| histograms.GetTotalCountsForPrefix("Navigation.StartToCommit"); |
| int32_t total_counts = 0; |
| for (const auto& it : counts) { |
| total_counts += it.second; |
| } |
| EXPECT_EQ(static_cast<int32_t>(names.size()), total_counts); |
| }; |
| |
| // Main frame tests. |
| IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess()); |
| EXPECT_TRUE( |
| NavigateToURL(shell(), embedded_test_server()->GetURL("/hello.html"))); |
| { |
| base::HistogramTester histograms; |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| check_navigation(histograms, ProcessType::kSame, FrameType::kMain, |
| TransitionType::kNew); |
| } |
| { |
| base::HistogramTester histograms; |
| GURL url(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| check_navigation(histograms, ProcessType::kCross, FrameType::kMain, |
| TransitionType::kNew); |
| } |
| { |
| base::HistogramTester histograms; |
| ReloadBlockUntilNavigationsComplete(shell(), 1); |
| check_navigation(histograms, ProcessType::kSame, FrameType::kMain, |
| TransitionType::kReload); |
| } |
| { |
| base::HistogramTester histograms; |
| TestNavigationObserver nav_observer(shell()->web_contents()); |
| shell()->GoBackOrForward(-1); |
| nav_observer.Wait(); |
| check_navigation(histograms, ProcessType::kCross, FrameType::kMain, |
| TransitionType::kBackForward); |
| } |
| { |
| base::HistogramTester histograms; |
| TestNavigationObserver nav_observer(shell()->web_contents()); |
| shell()->GoBackOrForward(-1); |
| nav_observer.Wait(); |
| check_navigation(histograms, ProcessType::kSame, FrameType::kMain, |
| TransitionType::kBackForward); |
| } |
| { |
| base::HistogramTester histograms; |
| int previous_process_id = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| bool process_changed = |
| (previous_process_id != |
| shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID()); |
| check_navigation(histograms, |
| process_changed ? ProcessType::kCross : ProcessType::kSame, |
| FrameType::kMain, TransitionType::kNew); |
| } |
| |
| // Subframe tests. All of these tests just navigate a frame within |
| // page_with_iframe.html. |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("/page_with_iframe.html"))); |
| FrameTreeNode* first_child = |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root() |
| ->child_at(0); |
| { |
| base::HistogramTester histograms; |
| EXPECT_TRUE(NavigateToURLFromRenderer( |
| first_child, embedded_test_server()->GetURL("c.com", "/title1.html"))); |
| check_navigation(histograms, ProcessType::kCross, FrameType::kSub, |
| TransitionType::kNew); |
| } |
| { |
| base::HistogramTester histograms; |
| TestFrameNavigationObserver nav_observer(first_child); |
| EXPECT_TRUE(ExecJs(first_child, "location.reload();")); |
| nav_observer.Wait(); |
| // location.reload triggers the PAGE_TRANSITION_AUTO_SUBFRAME which |
| // corresponds to NewNavigation. |
| check_navigation(histograms, ProcessType::kSame, FrameType::kSub, |
| TransitionType::kNew); |
| } |
| { |
| base::HistogramTester histograms; |
| shell()->GoBackOrForward(-1); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| // History back triggers the PAGE_TRANSITION_AUTO_SUBFRAME which corresponds |
| // to NewNavigation. |
| check_navigation(histograms, ProcessType::kCross, FrameType::kSub, |
| TransitionType::kNew); |
| } |
| EXPECT_TRUE(NavigateToURLFromRenderer( |
| first_child, embedded_test_server()->GetURL("/simple_links.html"))); |
| { |
| base::HistogramTester histograms; |
| TestFrameNavigationObserver nav_observer(first_child); |
| EXPECT_TRUE(ExecJs(first_child, "clickSameSiteLink();")); |
| nav_observer.Wait(); |
| // Link clicking will trigger PAGE_TRANSITION_MANUAL_SUBFRAME which |
| // corresponds to NewNavigation. |
| check_navigation(histograms, ProcessType::kSame, FrameType::kSub, |
| TransitionType::kNew); |
| } |
| } |
| |
| // Verify that the TimeToReadyToCommit2 metrics are correctly logged for |
| // SameProcess vs CrossProcess as well as MainFrame vs Subframe cases. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| TimeToReadyToCommitMetrics) { |
| EXPECT_TRUE( |
| NavigateToURL(shell(), embedded_test_server()->GetURL("/hello.html"))); |
| |
| // Check that only SameProcess version is logged and not CrossProcess. |
| { |
| base::HistogramTester histograms; |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| base::HistogramTester::CountsMap expected_counts = { |
| {"Navigation.TimeToReadyToCommit2.MainFrame", 1}, |
| {"Navigation.TimeToReadyToCommit2.MainFrame.NewNavigation", 1}, |
| {"Navigation.TimeToReadyToCommit2.NewNavigation", 1}, |
| {"Navigation.TimeToReadyToCommit2.SameProcess", 1}, |
| {"Navigation.TimeToReadyToCommit2.SameProcess.NewNavigation", 1}}; |
| EXPECT_THAT( |
| histograms.GetTotalCountsForPrefix("Navigation.TimeToReadyToCommit2."), |
| testing::ContainerEq(expected_counts)); |
| } |
| |
| // Navigate cross-process and ensure that only CrossProcess is logged. |
| { |
| base::HistogramTester histograms; |
| GURL url(embedded_test_server()->GetURL("a.com", "/title2.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| base::HistogramTester::CountsMap expected_counts = { |
| {"Navigation.TimeToReadyToCommit2.MainFrame", 1}, |
| {"Navigation.TimeToReadyToCommit2.MainFrame.NewNavigation", 1}, |
| {"Navigation.TimeToReadyToCommit2.NewNavigation", 1}, |
| {"Navigation.TimeToReadyToCommit2.CrossProcess", 1}, |
| {"Navigation.TimeToReadyToCommit2.CrossProcess.NewNavigation", 1}}; |
| EXPECT_THAT( |
| histograms.GetTotalCountsForPrefix("Navigation.TimeToReadyToCommit2."), |
| testing::ContainerEq(expected_counts)); |
| } |
| |
| // Add a new subframe. |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| EXPECT_TRUE(ExecJs( |
| root, "document.body.appendChild(document.createElement('iframe'));")); |
| |
| // Navigate subframe cross-site and ensure Subframe metrics are logged. |
| { |
| base::HistogramTester histograms; |
| GURL url(embedded_test_server()->GetURL("b.com", "/title3.html")); |
| EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), url)); |
| |
| std::string navigation_type = |
| AreAllSitesIsolatedForTesting() ? "CrossProcess" : "SameProcess"; |
| base::HistogramTester::CountsMap expected_counts = { |
| {"Navigation.TimeToReadyToCommit2.Subframe", 1}, |
| {"Navigation.TimeToReadyToCommit2.Subframe.NewNavigation", 1}, |
| {"Navigation.TimeToReadyToCommit2.NewNavigation", 1}, |
| {base::StringPrintf("Navigation.TimeToReadyToCommit2.%s", |
| navigation_type.c_str()), |
| 1}, |
| {base::StringPrintf("Navigation.TimeToReadyToCommit2.%s.NewNavigation", |
| navigation_type.c_str()), |
| 1}}; |
| EXPECT_THAT( |
| histograms.GetTotalCountsForPrefix("Navigation.TimeToReadyToCommit2."), |
| testing::ContainerEq(expected_counts)); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestDownloadBrowserTest, IsDownload) { |
| GURL url(embedded_test_server()->GetURL("/download-test1.lib")); |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| EXPECT_TRUE(NavigateToURLAndExpectNoCommit(shell(), url)); |
| EXPECT_FALSE(observer.has_committed()); |
| EXPECT_TRUE(observer.is_download()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestDownloadBrowserTest, |
| DownloadFalseForHtmlResponse) { |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_download()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestDownloadBrowserTest, |
| DownloadFalseFor404) { |
| GURL url(embedded_test_server()->GetURL("/page404.html")); |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_download()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestDownloadBrowserTest, |
| DownloadFalseForFailedNavigation) { |
| GURL url(embedded_test_server()->GetURL("/download-test1.lib")); |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::CANCEL, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| |
| EXPECT_TRUE(NavigateToURLAndExpectNoCommit(shell(), url)); |
| EXPECT_FALSE(observer.has_committed()); |
| EXPECT_TRUE(observer.is_error()); |
| EXPECT_FALSE(observer.is_download()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestDownloadBrowserTest, |
| RedirectToDownload) { |
| GURL redirect_url( |
| embedded_test_server()->GetURL("/cross-site/bar.com/download-test1.lib")); |
| NavigationHandleObserver observer(shell()->web_contents(), redirect_url); |
| EXPECT_TRUE(NavigateToURLAndExpectNoCommit(shell(), redirect_url)); |
| EXPECT_FALSE(observer.has_committed()); |
| EXPECT_TRUE(observer.was_redirected()); |
| EXPECT_TRUE(observer.is_download()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestDownloadBrowserTest, |
| RedirectToDownloadFails) { |
| GURL redirect_url( |
| embedded_test_server()->GetURL("/cross-site/bar.com/download-test1.lib")); |
| NavigationHandleObserver observer(shell()->web_contents(), redirect_url); |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::PROCEED, |
| NavigationThrottle::CANCEL, NavigationThrottle::PROCEED, |
| NavigationThrottle::PROCEED, NavigationThrottle::PROCEED); |
| |
| EXPECT_FALSE(NavigateToURL(shell(), redirect_url)); |
| |
| EXPECT_FALSE(observer.has_committed()); |
| EXPECT_FALSE(observer.is_download()); |
| EXPECT_TRUE(observer.is_error()); |
| EXPECT_TRUE(observer.was_redirected()); |
| } |
| |
| // Set of tests that check the various NavigationThrottle events can be used |
| // with custom error pages. |
| class NavigationRequestThrottleResultWithErrorPageBrowserTest |
| : public NavigationRequestBrowserTest, |
| public ::testing::WithParamInterface<NavigationThrottle::ThrottleAction> { |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(NavigationRequestThrottleResultWithErrorPageBrowserTest, |
| WillStartRequest) { |
| NavigationThrottle::ThrottleAction action = GetParam(); |
| |
| if (action == NavigationThrottle::CANCEL_AND_IGNORE) { |
| // There is no support for CANCEL_AND_IGNORE and a custom error page. |
| return; |
| } |
| |
| if (action == NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE) { |
| // This can only be returned from sub frame navigations. |
| return; |
| } |
| |
| if (action == NavigationThrottle::PROCEED || |
| action == NavigationThrottle::DEFER) { |
| // Neither is relevant for what we want to test i.e. error pages. |
| return; |
| } |
| |
| if (action == NavigationThrottle::BLOCK_RESPONSE) { |
| // BLOCK_RESPONSE can't be used with WillStartRequest. |
| return; |
| } |
| |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| InstallThrottleAndTestNavigationCommittedWithErrorPage( |
| url, action, TestNavigationThrottleInstaller::WILL_START_REQUEST); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(NavigationRequestThrottleResultWithErrorPageBrowserTest, |
| WillRedirectRequest) { |
| NavigationThrottle::ThrottleAction action = GetParam(); |
| |
| if (action == NavigationThrottle::CANCEL_AND_IGNORE) { |
| // There is no support for CANCEL_AND_IGNORE and a custom error page. |
| return; |
| } |
| |
| if (action == NavigationThrottle::PROCEED || |
| action == NavigationThrottle::DEFER) { |
| // Neither is relevant for what we want to test i.e. error pages. |
| return; |
| } |
| |
| if (action == NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE) { |
| // This can only be returned from sub frame navigations. |
| return; |
| } |
| |
| if (action == NavigationThrottle::BLOCK_RESPONSE) { |
| // BLOCK_RESPONSE can't be used with WillRedirectRequest. |
| return; |
| } |
| |
| GURL url(embedded_test_server()->GetURL("/cross-site/foo.com/title1.html")); |
| InstallThrottleAndTestNavigationCommittedWithErrorPage( |
| url, action, TestNavigationThrottleInstaller::WILL_REDIRECT_REQUEST); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(NavigationRequestThrottleResultWithErrorPageBrowserTest, |
| WillFailRequest) { |
| NavigationThrottle::ThrottleAction action = GetParam(); |
| |
| if (action == NavigationThrottle::PROCEED || |
| action == NavigationThrottle::DEFER) { |
| // Neither is relevant for what we want to test i.e. error pages. |
| return; |
| } |
| |
| if (action == NavigationThrottle::BLOCK_REQUEST || |
| action == NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE) { |
| // BLOCK_REQUEST, BLOCK_REQUEST_AND_COLLAPSE, can't be used with |
| // WillFailRequest. |
| return; |
| } |
| |
| net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); |
| https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_MISMATCHED_NAME); |
| ASSERT_TRUE(https_server.Start()); |
| |
| const GURL url = https_server.GetURL("/title1.html"); |
| InstallThrottleAndTestNavigationCommittedWithErrorPage( |
| url, action, TestNavigationThrottleInstaller::WILL_FAIL_REQUEST); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(NavigationRequestThrottleResultWithErrorPageBrowserTest, |
| WillProcessResponse) { |
| NavigationThrottle::ThrottleAction action = GetParam(); |
| |
| if (action == NavigationThrottle::CANCEL_AND_IGNORE) { |
| // There is no support for CANCEL_AND_IGNORE and a custom error page. |
| return; |
| } |
| |
| if (action == NavigationThrottle::PROCEED || |
| action == NavigationThrottle::DEFER) { |
| // Neither is relevant for what we want to test i.e. error pages. |
| return; |
| } |
| |
| if (action == NavigationThrottle::BLOCK_REQUEST || |
| action == NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE) { |
| // BLOCK_REQUEST and BLOCK_REQUEST_AND_COLLAPSE can't be used with |
| // WillProcessResponse. |
| return; |
| } |
| |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| InstallThrottleAndTestNavigationCommittedWithErrorPage( |
| url, action, TestNavigationThrottleInstaller::WILL_PROCESS_RESPONSE); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| NavigationRequestThrottleResultWithErrorPageBrowserTest, |
| testing::Range(NavigationThrottle::ThrottleAction::FIRST, |
| NavigationThrottle::ThrottleAction::LAST)); |
| |
| // The set of tests... |
| // * NavigationRequestDownloadBrowserTest.AllowedResourceDownloaded |
| // * NavigationRequestDownloadBrowserTest.AllowedResourceNotDownloaded |
| // * NavigationRequestDownloadBrowserTest.Disallowed |
| // |
| // ...covers every combination of possible states for: |
| // * CommonNavigationParams::download_policy (allow vs disallow) |
| // * NavigationHandle::IsDownload() |
| // |
| // Download policies that enumerate allowed / disallowed options are not tested |
| // here. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestDownloadBrowserTest, |
| AllowedResourceDownloaded) { |
| GURL simple_url(embedded_test_server()->GetURL("/simple_page.html")); |
| |
| TestNavigationManager manager(shell()->web_contents(), simple_url); |
| NavigationHandleObserver handle_observer(shell()->web_contents(), simple_url); |
| |
| // Download is allowed. |
| shell()->LoadURL(simple_url); |
| EXPECT_TRUE(manager.WaitForRequestStart()); |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryMainFrame() |
| ->frame_tree_node(); |
| EXPECT_TRUE(root->navigation_request() |
| ->common_params() |
| .download_policy.IsDownloadAllowed()); |
| |
| // The response is not handled as a download. |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| EXPECT_FALSE(handle_observer.is_download()); |
| } |
| |
| // See NavigationRequestDownloadBrowserTest.AllowedResourceNotDownloaded |
| IN_PROC_BROWSER_TEST_F(NavigationRequestDownloadBrowserTest, |
| AllowedResourceNotDownloaded) { |
| GURL download_url(embedded_test_server()->GetURL("/download-test1.lib")); |
| |
| TestNavigationManager manager(shell()->web_contents(), download_url); |
| NavigationHandleObserver handle_observer(shell()->web_contents(), |
| download_url); |
| |
| // Download is allowed. |
| shell()->LoadURL(download_url); |
| EXPECT_TRUE(manager.WaitForRequestStart()); |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryMainFrame() |
| ->frame_tree_node(); |
| EXPECT_TRUE(root->navigation_request() |
| ->common_params() |
| .download_policy.IsDownloadAllowed()); |
| |
| // The response is handled as a download. |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| EXPECT_TRUE(handle_observer.is_download()); |
| } |
| |
| // See NavigationRequestDownloadBrowserTest.AllowedResourceNotDownloaded |
| IN_PROC_BROWSER_TEST_F(NavigationRequestDownloadBrowserTest, Disallowed) { |
| GURL download_url(embedded_test_server()->GetURL("/download-test1.lib")); |
| |
| // An URL is allowed to be a download iff it is not a view-source URL. |
| GURL view_source_url = |
| GURL(content::kViewSourceScheme + std::string(":") + download_url.spec()); |
| |
| NavigationHandleObserver handle_observer(shell()->web_contents(), |
| download_url); |
| TestNavigationManager manager(shell()->web_contents(), download_url); |
| |
| // Download is not allowed. |
| shell()->LoadURL(view_source_url); |
| EXPECT_TRUE(manager.WaitForRequestStart()); |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryMainFrame() |
| ->frame_tree_node(); |
| EXPECT_TRUE( |
| root->navigation_request()->common_params().download_policy.IsType( |
| blink::NavigationDownloadType::kViewSource)); |
| EXPECT_FALSE(root->navigation_request() |
| ->common_params() |
| .download_policy.IsDownloadAllowed()); |
| |
| // The response is not handled as a download. |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| EXPECT_FALSE(handle_observer.is_download()); |
| } |
| |
| class NavigationRequestBackForwardBrowserTest |
| : public NavigationRequestBrowserTest, |
| public WebContentsObserver { |
| protected: |
| void SetUpOnMainThread() override { |
| NavigationRequestBrowserTest::SetUpOnMainThread(); |
| |
| WebContentsObserver::Observe(shell()->web_contents()); |
| } |
| |
| void DidFinishNavigation(NavigationHandle* navigation_handle) override { |
| if (navigation_handle->HasCommitted()) { |
| offsets_.push_back(navigation_handle->GetNavigationEntryOffset()); |
| } |
| } |
| |
| std::vector<int64_t> offsets_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBackForwardBrowserTest, |
| NavigationEntryOffsets) { |
| const GURL url1(embedded_test_server()->GetURL("/title1.html")); |
| const GURL url2(embedded_test_server()->GetURL("/title2.html")); |
| const GURL url3(embedded_test_server()->GetURL("/title3.html")); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url1)); |
| // The navigation entries are: |
| // [*url1]. |
| |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents()); |
| shell()->Reload(); |
| navigation_observer.WaitForNavigationFinished(); |
| } |
| // The navigation entries are: |
| // [*url1]. |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url2)); |
| // The navigation entries are: |
| // [url1, *url2]. |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url3)); |
| // The navigation entries are: |
| // [url1, url2, *url3]. |
| |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents()); |
| shell()->GoBackOrForward(-1); |
| navigation_observer.WaitForNavigationFinished(); |
| } |
| // The navigation entries are: |
| // [url1, *url2, url3]. |
| |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents()); |
| shell()->GoBackOrForward(-1); |
| navigation_observer.WaitForNavigationFinished(); |
| } |
| // The navigation entries are: |
| // [*url1, url2, url3]. |
| |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents()); |
| shell()->GoBackOrForward(1); |
| navigation_observer.WaitForNavigationFinished(); |
| } |
| // The navigation entries are: |
| // [url1, *url2, url3]. |
| |
| // Navigations 1, 3, 4 are regular navigations. |
| // Navigation 2 is a reload. |
| // Navigaations 5 and 6 are back navigations. |
| // Navigation 7 is a forward navigation. |
| EXPECT_THAT(offsets_, testing::ElementsAre(1, 0, 1, 1, -1, -1, 1)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBackForwardBrowserTest, |
| NavigationEntryOffsetsForSubframes) { |
| const GURL url1(embedded_test_server()->GetURL("/title1.html")); |
| const GURL url1_fragment1( |
| embedded_test_server()->GetURL("/title1.html#id_1")); |
| const GURL url2(embedded_test_server()->GetURL( |
| "b.com", "/frame_tree/page_with_one_frame.html")); |
| const char kChildFrameId[] = "child0"; |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url1)); |
| // The navigation entries are: |
| // [*url1]. |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url1_fragment1)); |
| // The navigation entries are: |
| // [url1, *url1_fragment1]. |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url2)); |
| // The navigation entries are: |
| // [url1, url1_fragment1, *url2(subframe)]. |
| |
| EXPECT_TRUE( |
| NavigateIframeToURL(shell()->web_contents(), kChildFrameId, url1)); |
| // The navigation entries are: |
| // [url1, url1_fragment1, url2(subframe), *url2(url1)]. |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url1)); |
| // The navigation entries are: |
| // [url1, url1_fragment1, url2(subframe), url2(url1), *url1]. |
| |
| { |
| // We are waiting for two navigations here: main frame and subframe. |
| // However, when back/forward cache is enabled, back navigation to a page |
| // with subframes will not trigger a subframe navigation (since the |
| // subframe is cached with the page). |
| TestNavigationObserver navigation_observer( |
| shell()->web_contents(), IsBackForwardCacheEnabled() ? 1 : 2); |
| shell()->GoBackOrForward(-1); |
| navigation_observer.WaitForNavigationFinished(); |
| } |
| // The navigation entries are: |
| // [url1, url1_fragment1, url2(subframe), *url2(url1), url1]. |
| |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents()); |
| shell()->GoBackOrForward(-1); |
| navigation_observer.WaitForNavigationFinished(); |
| } |
| // The navigation entries are: |
| // [url1, url1_fragment1, *url2(subframe), url2(url1), url1]. |
| |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents()); |
| shell()->GoBackOrForward(-1); |
| navigation_observer.WaitForNavigationFinished(); |
| } |
| // The navigation entries are: |
| // [url1, *url1_fragment1, url2(subframe), url2(url1), url1]. |
| |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents()); |
| shell()->GoBackOrForward(-1); |
| navigation_observer.WaitForNavigationFinished(); |
| } |
| // The navigation entries are: |
| // [*url1, url1_fragment1, url2(subframe), url2(url1), url1]. |
| |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents()); |
| shell()->GoBackOrForward(4); |
| navigation_observer.WaitForNavigationFinished(); |
| } |
| // The navigation entries are: |
| // [url1, url1_fragment1, url2(subframe), url2(url1), *url1]. |
| |
| // New navigations have offset 1, back navigations have offset -1 and the last |
| // navigations have offset 3 as requested. |
| // Note that all subframe navigations have offset 1 regardless of whether they |
| // result in a new entry being generated or not. |
| if (IsBackForwardCacheEnabled()) { |
| // When back/forward cache is enabled, back navigation to a page with |
| // subframes will not trigger a subframe navigation (since the subframe is |
| // cached with the page and won't need to be reconstructed/navigated). |
| EXPECT_THAT(offsets_, |
| testing::ElementsAre(1, 1, 1, 0, 1, 1, -1, -1, -1, -1, 4)); |
| } else { |
| EXPECT_THAT(offsets_, |
| testing::ElementsAre(1, 1, 1, 0, 1, 1, -1, 1, -1, -1, -1, 4)); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBackForwardBrowserTest, |
| NavigationEntryLimit) { |
| const GURL url1(embedded_test_server()->GetURL("/title1.html")); |
| |
| static_cast<NavigationControllerImpl*>( |
| &shell()->web_contents()->GetController()) |
| ->set_max_entry_count_for_testing(3); |
| |
| for (int i = 0; i < 5; ++i) { |
| EXPECT_TRUE(NavigateToURL(shell(), url1)); |
| } |
| |
| // Expect that the offsets are still 1 even when we hit the entry count limit. |
| EXPECT_THAT(offsets_, testing::ElementsAre(1, 1, 1, 1, 1)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBackForwardBrowserTest, |
| LocationReplace) { |
| const GURL url1(embedded_test_server()->GetURL("/title1.html")); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url1)); |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents()); |
| EXPECT_TRUE(ExecJs(root, "window.location.replace('#frag');")); |
| navigation_observer.WaitForNavigationFinished(); |
| } |
| |
| // The second navigation replaces the current navigation entry and should have |
| // offset of zero. |
| EXPECT_THAT(offsets_, testing::ElementsAre(1, 0)); |
| } |
| |
| // Tests that the correct net::AuthChallengeInfo is exposed from the |
| // NavigationHandle when the page requests authentication. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, AuthChallengeInfo) { |
| GURL url(embedded_test_server()->GetURL("/auth-basic")); |
| NavigationHandleObserver observer(shell()->web_contents(), url); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| EXPECT_TRUE(observer.has_committed()); |
| ASSERT_TRUE(observer.auth_challenge_info().has_value()); |
| EXPECT_FALSE(observer.auth_challenge_info()->is_proxy); |
| EXPECT_EQ(url::SchemeHostPort(url), |
| observer.auth_challenge_info()->challenger); |
| EXPECT_EQ("basic", observer.auth_challenge_info()->scheme); |
| EXPECT_EQ("testrealm", observer.auth_challenge_info()->realm); |
| EXPECT_EQ("Basic realm=\"testrealm\"", |
| observer.auth_challenge_info()->challenge); |
| EXPECT_EQ("/auth-basic", observer.auth_challenge_info()->path); |
| } |
| |
| class TestMixedContentWebContentsDelegate : public WebContentsDelegate { |
| public: |
| TestMixedContentWebContentsDelegate() = default; |
| TestMixedContentWebContentsDelegate( |
| const TestMixedContentWebContentsDelegate&) = delete; |
| TestMixedContentWebContentsDelegate& operator=( |
| const TestMixedContentWebContentsDelegate&) = delete; |
| |
| bool passive_insecure_content_found() { |
| return passive_insecure_content_found_; |
| } |
| |
| // WebContentsDelegate: |
| void PassiveInsecureContentFound(const GURL& resource_url) override { |
| passive_insecure_content_found_ = true; |
| } |
| |
| private: |
| bool passive_insecure_content_found_ = false; |
| }; |
| |
| // Tests that an iframe with a non-webby scheme is not treated as mixed |
| // content. See https://crbug.com/621131. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| NonWebbyIframeIsNotMixedContent) { |
| net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); |
| https_server.ServeFilesFromSourceDirectory(GetTestDataFilePath()); |
| ASSERT_TRUE(https_server.Start()); |
| |
| GURL url(https_server.GetURL("/title1.html")); |
| ASSERT_TRUE(NavigateToURL(shell(), url)); |
| |
| // Inject a test delegate to observe when mixed content is detected. |
| WebContents* contents = shell()->web_contents(); |
| TestMixedContentWebContentsDelegate test_delegate; |
| contents->SetDelegate(&test_delegate); |
| |
| // Insert an iframe and navigate it to a non-webby scheme. It shouldn't be |
| // treated as mixed content. |
| GURL non_webby_url("foo://bar"); |
| TestNavigationObserver observer(contents); |
| ASSERT_NE(false, |
| EvalJs(contents, |
| JsReplace("var iframe = document.createElement('iframe');" |
| "iframe.src = $1;" |
| "document.body.appendChild(iframe);", |
| non_webby_url))); |
| observer.Wait(); |
| EXPECT_FALSE(test_delegate.passive_insecure_content_found()); |
| } |
| |
| // Tests that a NavigationRequest's RFH can be retrieved during a synchronous |
| // renderer commit same-document navigation (regardless of whether the |
| // navigation commits or not). |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| GetRFHDuringSyncRendererCommitSameDocumentNavigation) { |
| const GURL url(embedded_test_server()->GetURL("/title1.html")); |
| const GURL same_doc_url(embedded_test_server()->GetURL("/title1.html#foo")); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| WebContents* web_contents = shell()->web_contents(); |
| |
| // Test sync-renderer-commit same-document navigation that commits. |
| { |
| TestNavigationManager navigation_manager(web_contents, same_doc_url); |
| testing::NiceMock<MockWebContentsObserver> observer(web_contents); |
| EXPECT_CALL(observer, DidFinishNavigation(testing::_)) |
| .WillOnce(testing::Invoke([](NavigationHandle* navigation_handle) { |
| NavigationRequest* request = |
| NavigationRequest::From(navigation_handle); |
| EXPECT_TRUE(request->is_synchronous_renderer_commit()); |
| EXPECT_TRUE(navigation_handle->GetRenderFrameHost()); |
| })); |
| EXPECT_TRUE(ExecJs(web_contents, "location.href = '#foo';")); |
| ASSERT_TRUE(navigation_manager.WaitForNavigationFinished()); |
| } |
| |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| WebContents* popup = nullptr; |
| { |
| WebContentsAddedObserver popup_observer; |
| ASSERT_TRUE( |
| ExecJs(web_contents, |
| JsReplace("var w = window.open($1, 'my-popup')", GURL()))); |
| popup = popup_observer.GetWebContents(); |
| } |
| // Test sync-renderer-commit same-document navigation that doesn't commit. |
| { |
| testing::NiceMock<MockWebContentsObserver> observer(popup); |
| EXPECT_CALL(observer, DidFinishNavigation(testing::_)) |
| .WillOnce(testing::Invoke([](NavigationHandle* navigation_handle) { |
| NavigationRequest* request = |
| NavigationRequest::From(navigation_handle); |
| EXPECT_TRUE(request->is_synchronous_renderer_commit()); |
| EXPECT_TRUE(navigation_handle->GetRenderFrameHost()); |
| })); |
| TestNavigationManager navigation_manager(popup, GURL("about:blank#foo")); |
| EXPECT_TRUE( |
| ExecJs(web_contents, "w.history.replaceState({}, '', '#foo');")); |
| ASSERT_TRUE(navigation_manager.WaitForNavigationFinished()); |
| } |
| } |
| |
| // Tests that a NavigationRequest's RFH can be retrieved during a synchronous |
| // renderer commit initial-about-blank navigation (regardless of whether the |
| // navigation commits or not). |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| GetRFHDuringInitialAboutBlankNavigation) { |
| EXPECT_TRUE( |
| NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"))); |
| WebContentsImpl* web_contents = |
| static_cast<WebContentsImpl*>(shell()->web_contents()); |
| |
| // Test initial-about-blank navigation that commits. |
| { |
| testing::NiceMock<MockWebContentsObserver> observer(web_contents); |
| EXPECT_CALL(observer, DidFinishNavigation(testing::_)) |
| .WillOnce(testing::Invoke([](NavigationHandle* navigation_handle) { |
| NavigationRequest* request = |
| NavigationRequest::From(navigation_handle); |
| EXPECT_TRUE(request->is_synchronous_renderer_commit()); |
| EXPECT_TRUE(navigation_handle->GetRenderFrameHost()); |
| })); |
| CreateSubframe(web_contents, "subframe", GURL(), |
| /*wait_for_navigation*/ true); |
| } |
| |
| WebContentsImpl* popup = nullptr; |
| { |
| WebContentsAddedObserver popup_observer; |
| ASSERT_TRUE( |
| ExecJs(web_contents, |
| JsReplace("var w = window.open($1, 'my-popup')", GURL()))); |
| popup = static_cast<WebContentsImpl*>(popup_observer.GetWebContents()); |
| } |
| // Test initial-about-blank navigation that doesn't commit. |
| { |
| testing::NiceMock<MockWebContentsObserver> observer(popup); |
| EXPECT_CALL(observer, DidFinishNavigation(testing::_)) |
| .WillOnce(testing::Invoke([](NavigationHandle* navigation_handle) { |
| NavigationRequest* request = |
| NavigationRequest::From(navigation_handle); |
| EXPECT_TRUE(request->is_synchronous_renderer_commit()); |
| EXPECT_TRUE(navigation_handle->GetRenderFrameHost()); |
| })); |
| CreateSubframe(popup, "popup_subframe", GURL(), |
| /*wait_for_navigation*/ true); |
| } |
| } |
| |
| // Verify that when navigating to a site that doesn't require a dedicated |
| // process from a initial siteless SiteInstance, the SiteInstance sets its site |
| // at ready-to-commit time (rather than at DidCommitNavigation time). |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| SiteIsSetAtResponseTimeWithoutSiteIsolation) { |
| // A custom ContentBrowserClient to turn off strict site isolation. |
| class NoSiteIsolationContentBrowserClient |
| : public ContentBrowserTestContentBrowserClient { |
| public: |
| bool ShouldEnableStrictSiteIsolation() override { return false; } |
| } no_site_isolation_client; |
| |
| // The test should start in a blank shell with a siteless SiteInstance. |
| EXPECT_FALSE( |
| static_cast<SiteInstanceImpl*>(shell()->web_contents()->GetSiteInstance()) |
| ->HasSite()); |
| |
| // Start a navigation and wait for response. Note that this won't require a |
| // dedicated process due to the custom ContentBrowserClient. |
| GURL main_url(embedded_test_server()->GetURL("bar.com", "/title1.html")); |
| TestNavigationManager manager(shell()->web_contents(), main_url); |
| shell()->web_contents()->GetController().LoadURL( |
| main_url, Referrer(), ui::PAGE_TRANSITION_LINK, std::string()); |
| EXPECT_TRUE(manager.WaitForResponse()); |
| |
| // At this point, the navigation should be processing the response but not |
| // committed yet. It should have already determined the final |
| // RenderFrameHost, which should just be the initial RenderFrameHost. |
| NavigationRequest* request = |
| static_cast<NavigationRequest*>(manager.GetNavigationHandle()); |
| EXPECT_EQ(request->state(), NavigationRequest::WILL_PROCESS_RESPONSE); |
| EXPECT_EQ(shell()->web_contents()->GetPrimaryMainFrame(), |
| request->GetRenderFrameHost()); |
| |
| // The navigation will stay in the initial SiteInstance, and that |
| // SiteInstance's site should now be set. |
| EXPECT_TRUE( |
| static_cast<SiteInstanceImpl*>(shell()->web_contents()->GetSiteInstance()) |
| ->HasSite()); |
| |
| // The process should also be considered used at this point. |
| EXPECT_FALSE( |
| shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->IsUnused()); |
| |
| // Ensure the navigation finishes before we restore the ContentBrowserClient |
| // which would turn strict site isolation back on. Otherwise, the navigation |
| // commit may fail citadel protection checks at test teardown. |
| EXPECT_TRUE(manager.WaitForNavigationFinished()); |
| EXPECT_TRUE(manager.was_successful()); |
| } |
| |
| // Check that a subframe can load an error page with an about:srcdoc URL, and |
| // that the origin does not inherit the parent's origin (i.e., behaves like all |
| // error pages) in this case. In practice, this path may be taken by the heavy |
| // ads intervention (for an example, see the |
| // HeavyAdInterventionEnabled_ErrorPageLoaded test). |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| OriginForSrcdocErrorPageInSubframe) { |
| // Start on a page with a blank subframe. |
| GURL start_url = |
| embedded_test_server()->GetURL("a.test", "/page_with_blank_iframe.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), start_url)); |
| |
| // Do a srcdoc navigation in the subframe. |
| EXPECT_TRUE( |
| ExecJs(shell(), "document.querySelector('iframe').srcdoc='foo';")); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| RenderFrameHostImpl* subframe_rfh = root->child_at(0)->current_frame_host(); |
| EXPECT_EQ(GURL("about:srcdoc"), subframe_rfh->GetLastCommittedURL()); |
| |
| // Navigate the subframe to a post-commit error page, reusing its current |
| // srcdoc URL. A post-commit error page provides a way to reach an error |
| // page for a srcdoc subframe; note that it isn't possible to use |
| // NavigationThrottles to block srcdoc navigations, since throttles don't |
| // currently run in that case. |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->web_contents()->GetController().LoadPostCommitErrorPage( |
| subframe_rfh, subframe_rfh->GetLastCommittedURL(), "error_page_contents"); |
| navigation_observer.Wait(); |
| EXPECT_FALSE(navigation_observer.last_navigation_succeeded()); |
| |
| // Verify that the origin of the srcdoc frame's parent wasn't inherited and |
| // also wasn't used for the precursor. The error page's origin should be |
| // opaque without a valid precursor. |
| url::Origin origin = |
| root->child_at(0)->current_frame_host()->GetLastCommittedOrigin(); |
| EXPECT_TRUE(origin.opaque()); |
| EXPECT_FALSE(origin.GetTupleOrPrecursorTupleIfOpaque().IsValid()); |
| } |
| |
| // Verify that when navigation 1, which starts in an initial siteless |
| // SiteInstance and results in an error page, races with navigation 2, which |
| // requires a dedicated process and wants to reuse an existing process, |
| // navigation 2 does not incorrectly reuse navigation 1's process. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| ErrorPageMarksProcessAsUsed) { |
| // The scenario in this test originally led to a site isolation bypass only |
| // when error page isolation for main frames is turned off. Do this via a |
| // custom ContentBrowserClient. |
| class NoErrorPageIsolationContentBrowserClient |
| : public ContentBrowserTestContentBrowserClient { |
| public: |
| bool ShouldIsolateErrorPage(bool in_main_frame) override { return false; } |
| } no_error_isolation_client; |
| |
| // Set the process limit to 1. This will force main frame navigations to |
| // attempt to reuse existing processes. |
| RenderProcessHost::SetMaxRendererProcessCount(1); |
| |
| // The test should start in a blank shell with a siteless SiteInstance. |
| EXPECT_FALSE( |
| static_cast<SiteInstanceImpl*>(shell()->web_contents()->GetSiteInstance()) |
| ->HasSite()); |
| |
| // Set up a foo.com URL which will fail to load. |
| GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); |
| std::unique_ptr<URLLoaderInterceptor> interceptor = |
| URLLoaderInterceptor::SetupRequestFailForURL(foo_url, |
| net::ERR_CONNECTION_REFUSED); |
| |
| // Set up a throttle that will be used to wait for WillFailRequest() and then |
| // defer the navigation. |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), |
| NavigationThrottle::PROCEED /* will_start_result */, |
| NavigationThrottle::PROCEED /* will_redirect_result */, |
| NavigationThrottle::DEFER /* will_fail_result */, |
| NavigationThrottle::PROCEED /* will_process_result */, |
| NavigationThrottle::PROCEED /* will_commit_without_url_loader_result */); |
| |
| // Start a navigation to foo.com that will result in an error. |
| shell()->web_contents()->GetController().LoadURL( |
| foo_url, Referrer(), ui::PAGE_TRANSITION_LINK, std::string()); |
| |
| // Wait for WillFailRequest(). After this point, we will have picked the |
| // final RenderFrameHost for the error page. |
| installer.WaitForThrottleWillFail(); |
| |
| // Create a new tab and navigate it to a different site. Ensure this site |
| // requires a dedicated process, even on Android. |
| GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title1.html")); |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| policy->AddFutureIsolatedOrigins( |
| {url::Origin::Create(bar_url)}, |
| ChildProcessSecurityPolicy::IsolatedOriginSource::TEST); |
| Shell* new_shell = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(new_shell, bar_url)); |
| |
| // Resume the error page navigation. It should be able to finish without |
| // crashing. |
| installer.navigation_throttle()->ResumeNavigation(); |
| EXPECT_FALSE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_TRUE( |
| shell()->web_contents()->GetPrimaryMainFrame()->IsErrorDocument()); |
| |
| // Ensure that bar.com didn't reuse the foo.com error page process. |
| EXPECT_NE(shell()->web_contents()->GetPrimaryMainFrame()->GetProcess(), |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| } |
| |
| // Check that a renderer-initiated navigation from an error page to about:blank |
| // honors the initiator origin when selecting the SiteInstance and process for |
| // about:blank. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| NavigateToAboutBlankFromErrorPage) { |
| GURL url(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| std::unique_ptr<URLLoaderInterceptor> url_interceptor = |
| URLLoaderInterceptor::SetupRequestFailForURL(url, net::ERR_DNS_TIMED_OUT); |
| |
| // Start off with navigation to a.com, which results in an error page. |
| WebContents* web_contents = shell()->web_contents(); |
| { |
| TestNavigationObserver observer(web_contents); |
| ASSERT_FALSE(NavigateToURL(shell(), url)); |
| EXPECT_FALSE(observer.last_navigation_succeeded()); |
| if (SiteIsolationPolicy::IsErrorPageIsolationEnabled(true)) { |
| EXPECT_EQ( |
| GURL(kUnreachableWebDataURL), |
| web_contents->GetPrimaryMainFrame()->GetSiteInstance()->GetSiteURL()); |
| } |
| } |
| |
| // Now, do a renderer-initiated navigation to about:blank out of the error |
| // page. We don't expect error pages to normally do this, but this might |
| // still be possible via DevTools or automation. |
| GURL about_blank(url::kAboutBlankURL); |
| { |
| TestNavigationObserver observer(web_contents); |
| EXPECT_TRUE(ExecJs(web_contents, "location = 'about:blank';")); |
| observer.Wait(); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| EXPECT_EQ(about_blank, observer.last_navigation_url()); |
| } |
| RenderFrameHostImpl* rfh = |
| static_cast<RenderFrameHostImpl*>(web_contents->GetPrimaryMainFrame()); |
| EXPECT_NE(GURL(kUnreachableWebDataURL), rfh->GetSiteInstance()->GetSiteURL()); |
| |
| // Note that the error page's origin was opaque with a.com as the precursor. |
| // This becomes the initiator origin for the about:blank navigation, and it |
| // should end up as the final origin for the blank document. See |
| // https://crbug.com/585649. |
| EXPECT_TRUE(rfh->GetLastCommittedOrigin().opaque()); |
| EXPECT_EQ( |
| "a.com", |
| rfh->GetLastCommittedOrigin().GetTupleOrPrecursorTupleIfOpaque().host()); |
| |
| // Because about:blank's origin is opaque with a.com as the precursor, its |
| // SiteInstance and process should also correspond to a.com, rather than be |
| // left unassigned/unused. |
| // |
| // This covers an interesting and rare corner case, where an about:blank |
| // navigation can't use the source SiteInstance, which would normally keep it |
| // in the initiator's process and SiteInstance. This is because the |
| // navigation originates from an error page process, which is incompatible |
| // with a non-error navigation to about:blank. In this case, a new |
| // SiteInstance and process will be created, and they should still reflect |
| // about:blank's committed origin, rather than end up in an unlocked process |
| // and an unassigned SiteInstance. See https://crbug.com/1426928. |
| EXPECT_FALSE(rfh->GetProcess()->IsUnused()); |
| if (AreAllSitesIsolatedForTesting()) { |
| EXPECT_EQ("http://a.com/", rfh->GetSiteInstance()->GetSiteURL()); |
| EXPECT_TRUE(rfh->GetProcess()->GetProcessLock().is_locked_to_site()); |
| EXPECT_EQ("http://a.com/", rfh->GetProcess()->GetProcessLock().site_url()); |
| } else { |
| EXPECT_TRUE(rfh->GetProcess()->GetProcessLock().allows_any_site()); |
| } |
| } |
| |
| using CSPEmbeddedEnforcementBrowserTest = NavigationRequestBrowserTest; |
| |
| IN_PROC_BROWSER_TEST_F(CSPEmbeddedEnforcementBrowserTest, |
| CheckCSPEmbeddedEnforcement) { |
| // We need one initial navigation to set up everything. |
| EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL( |
| "www.example.org", "/empty.html"))); |
| |
| struct TestCase { |
| const char* name; |
| const char* required_csp; |
| const char* frame_url; |
| const char* allow_csp_from; |
| const char* returned_csp; |
| bool expect_allow; |
| } cases[] = { |
| { |
| "No required csp", |
| "", |
| "www.not-example.org", |
| nullptr, |
| nullptr, |
| true, |
| }, |
| { |
| "Required csp - Same origin", |
| "script-src 'none'", |
| "www.example.org", |
| nullptr, |
| nullptr, |
| false, |
| }, |
| { |
| "Required csp - Cross origin", |
| "script-src 'none'", |
| "www.not-example.org", |
| nullptr, |
| nullptr, |
| false, |
| }, |
| { |
| "Required csp - Cross origin with Allow-CSP-From", |
| "script-src 'none'", |
| "www.not-example.org", |
| "*", |
| nullptr, |
| true, |
| }, |
| { |
| "Required csp - Cross origin with wrong Allow-CSP-From", |
| "script-src 'none'", |
| "www.not-example.org", |
| "www.another-example.org", |
| nullptr, |
| false, |
| }, |
| { |
| "Required csp - Cross origin with non-subsuming CSPs", |
| "script-src 'none'", |
| "www.not-example.org", |
| nullptr, |
| "style-src 'none'", |
| false, |
| }, |
| { |
| "Required csp - Cross origin with subsuming CSPs", |
| "script-src 'none'", |
| "www.not-example.org", |
| nullptr, |
| "script-src 'none'", |
| true, |
| }, |
| { |
| "Required csp - Cross origin with wrong Allow-CSP-From but subsuming " |
| "CSPs", |
| "script-src 'none'", |
| "www.not-example.org", |
| "www.another-example.org", |
| "script-src 'none'", |
| true, |
| }, |
| }; |
| |
| for (auto test : cases) { |
| SCOPED_TRACE(test.name); |
| |
| std::string headers; |
| if (test.returned_csp) { |
| headers += |
| base::StringPrintf("Content-Security-Policy: %s&", test.returned_csp); |
| } |
| if (test.allow_csp_from) { |
| headers += base::StringPrintf("Allow-CSP-From: %s&", test.allow_csp_from); |
| } |
| |
| GURL frame_url = embedded_test_server()->GetURL(test.frame_url, |
| "/set-header?" + headers); |
| content::TestNavigationManager observer(shell()->web_contents(), frame_url); |
| |
| EXPECT_TRUE(ExecJs(shell()->web_contents(), |
| JsReplace(R"( |
| const iframe = document.createElement("iframe"); |
| iframe.src = $2; |
| if ($1) |
| iframe.csp = $1; |
| document.body.appendChild(iframe); |
| )", |
| test.required_csp, frame_url))); |
| |
| ASSERT_TRUE(observer.WaitForNavigationFinished()); |
| EXPECT_EQ(test.expect_allow, observer.was_successful()); |
| } |
| } |
| |
| class NavigationRequestFencedFrameBrowserTest |
| : public NavigationRequestBrowserTest { |
| public: |
| NavigationRequestFencedFrameBrowserTest() = default; |
| ~NavigationRequestFencedFrameBrowserTest() override = default; |
| NavigationRequestFencedFrameBrowserTest( |
| const NavigationRequestFencedFrameBrowserTest&) = delete; |
| |
| NavigationRequestFencedFrameBrowserTest& operator=( |
| const NavigationRequestFencedFrameBrowserTest&) = delete; |
| |
| void SetUpOnMainThread() override { |
| https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES); |
| https_server()->ServeFilesFromSourceDirectory(GetTestDataFilePath()); |
| net::test_server::RegisterDefaultHandlers(https_server()); |
| ASSERT_TRUE(https_server()->Start()); |
| NavigationRequestBrowserTest::SetUpOnMainThread(); |
| } |
| |
| content::test::FencedFrameTestHelper& fenced_frame_test_helper() { |
| return fenced_frame_helper_; |
| } |
| |
| net::EmbeddedTestServer* https_server() { return &https_server_; } |
| |
| private: |
| content::test::FencedFrameTestHelper fenced_frame_helper_; |
| net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS}; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F( |
| NavigationRequestFencedFrameBrowserTest, |
| ShouldRespectOutermostFrameCOEPParentAndChildOnInsecureContent) { |
| // Navigate |untrustworthy_url| to test if a fenced frame sets the outermost |
| // main frame's COEP. |
| GURL untrustworthy_url = |
| embedded_test_server()->GetURL("a.test", "/title1.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), untrustworthy_url)); |
| |
| // Create a fenced frame on an insecure content and its document should have |
| // the COEP of the outermost main frame. |
| GURL fenced_frame_url = embedded_test_server()->GetURL( |
| "a.test", |
| "/set-header?" |
| "Supports-Loading-Mode: fenced-frame&" |
| "Cross-Origin-Embedder-Policy: require-corp"); |
| content::RenderFrameHostImpl* fenced_frame_host = |
| static_cast<content::RenderFrameHostImpl*>( |
| fenced_frame_test_helper().CreateFencedFrame( |
| shell()->web_contents()->GetPrimaryMainFrame(), |
| fenced_frame_url)); |
| ASSERT_TRUE(fenced_frame_host); |
| EXPECT_EQ(network::mojom::CrossOriginEmbedderPolicyValue::kNone, |
| fenced_frame_host->cross_origin_embedder_policy().value); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| NavigationRequestFencedFrameBrowserTest, |
| RespectOutermostFrameCOEPParentOnInsecureContentAndChildOnSecureContent) { |
| // Navigate |untrustworthy_url| to test if a fenced frame sets the outermost |
| // main frame's COEP. |
| GURL untrustworthy_url = |
| embedded_test_server()->GetURL("a.test", "/title1.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), untrustworthy_url)); |
| |
| // Create a fenced frame on a secure content and its document should have the |
| // COEP of the outermost main frame. |
| GURL fenced_frame_url = |
| https_server()->GetURL("a.test", |
| "/set-header?" |
| "Supports-Loading-Mode: fenced-frame&" |
| "Cross-Origin-Embedder-Policy: require-corp"); |
| content::RenderFrameHostImpl* fenced_frame_host = |
| static_cast<content::RenderFrameHostImpl*>( |
| fenced_frame_test_helper().CreateFencedFrame( |
| shell()->web_contents()->GetPrimaryMainFrame(), |
| fenced_frame_url)); |
| ASSERT_TRUE(fenced_frame_host); |
| EXPECT_EQ(network::mojom::CrossOriginEmbedderPolicyValue::kNone, |
| fenced_frame_host->cross_origin_embedder_policy().value); |
| } |
| |
| // Ensure that fenced frames don't enable the view source mode since navigations |
| // in fenced frames to view-sources URLs are blocked. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestFencedFrameBrowserTest, |
| ViewSourceNavigation_FencedFrame) { |
| EXPECT_TRUE( |
| NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"))); |
| |
| GURL fenced_frame_url = |
| embedded_test_server()->GetURL("/fenced_frames/title1.html"); |
| RenderFrameHost* fenced_frame_host = |
| fenced_frame_test_helper().CreateFencedFrame( |
| shell()->web_contents()->GetPrimaryMainFrame(), fenced_frame_url); |
| EXPECT_NE(nullptr, fenced_frame_host); |
| |
| GURL view_source_url(kViewSourceScheme + std::string(":") + |
| fenced_frame_url.spec()); |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| console_observer.SetPattern("Not allowed to load local resource: " + |
| view_source_url.spec()); |
| |
| // Attempt to navigate to a view source url in the fenced frame. |
| EXPECT_EQ(view_source_url.spec(), |
| EvalJs(fenced_frame_host, |
| JsReplace(R"({location.href = $1;})", view_source_url))); |
| ASSERT_TRUE(console_observer.Wait()); |
| |
| // Original page shouldn't navigate away. |
| EXPECT_EQ(fenced_frame_url, fenced_frame_host->GetLastCommittedURL()); |
| EXPECT_FALSE(shell() |
| ->web_contents() |
| ->GetController() |
| .GetLastCommittedEntry() |
| ->IsViewSourceMode()); |
| } |
| |
| class NavigationRequestPrerenderBrowserTest |
| : public NavigationRequestBrowserTest { |
| public: |
| NavigationRequestPrerenderBrowserTest() { |
| prerender_helper_ = |
| std::make_unique<test::PrerenderTestHelper>(base::BindRepeating( |
| &NavigationRequestPrerenderBrowserTest::web_contents, |
| base::Unretained(this))); |
| } |
| ~NavigationRequestPrerenderBrowserTest() override = default; |
| |
| NavigationRequestPrerenderBrowserTest( |
| const NavigationRequestPrerenderBrowserTest&) = delete; |
| NavigationRequestPrerenderBrowserTest& operator=( |
| const NavigationRequestPrerenderBrowserTest&) = delete; |
| |
| void SetUpOnMainThread() override { |
| NavigationRequestBrowserTest::SetUpOnMainThread(); |
| |
| https_server_ = std::make_unique<net::EmbeddedTestServer>( |
| net::EmbeddedTestServer::TYPE_HTTPS); |
| https_server_->AddDefaultHandlers(GetTestDataFilePath()); |
| https_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES); |
| |
| ASSERT_TRUE(https_server()->Start()); |
| } |
| |
| protected: |
| net::EmbeddedTestServer* https_server() { return https_server_.get(); } |
| |
| test::PrerenderTestHelper& prerender_helper() { return *prerender_helper_; } |
| |
| WebContents* web_contents() { return shell()->web_contents(); } |
| |
| private: |
| std::unique_ptr<net::EmbeddedTestServer> https_server_; |
| std::unique_ptr<test::PrerenderTestHelper> prerender_helper_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestPrerenderBrowserTest, |
| CoopCoepCheckWithPrerender) { |
| GURL url( |
| https_server()->GetURL("a.test", |
| "/set-header" |
| "?cross-origin-opener-policy: same-origin" |
| "&cross-origin-embedder-policy: require-corp")); |
| |
| // Navigate to a document that sets COOP and COEP. |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| content::RenderFrameHostImpl* primary_main_frame = |
| static_cast<content::RenderFrameHostImpl*>( |
| shell()->web_contents()->GetPrimaryMainFrame()); |
| |
| EXPECT_EQ(network::mojom::CrossOriginOpenerPolicyValue::kSameOriginPlusCoep, |
| primary_main_frame->cross_origin_opener_policy().value); |
| EXPECT_EQ(network::mojom::CrossOriginEmbedderPolicyValue::kRequireCorp, |
| primary_main_frame->cross_origin_embedder_policy().value); |
| |
| // Add a prerender. |
| int host_id = prerender_helper().AddPrerender( |
| https_server()->GetURL("a.test", "/title1.html?prerendering")); |
| content::RenderFrameHostImpl* prerender_main_frame = |
| static_cast<content::RenderFrameHostImpl*>( |
| prerender_helper().GetPrerenderedMainFrameHost(host_id)); |
| |
| // The prerender rfh's polices are none. |
| EXPECT_EQ(network::mojom::CrossOriginEmbedderPolicyValue::kNone, |
| prerender_main_frame->cross_origin_embedder_policy().value); |
| EXPECT_EQ(network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone, |
| prerender_main_frame->cross_origin_opener_policy().value); |
| |
| // Prerendering should not affect the primary rfh's polices. |
| EXPECT_EQ(network::mojom::CrossOriginOpenerPolicyValue::kSameOriginPlusCoep, |
| primary_main_frame->cross_origin_opener_policy().value); |
| EXPECT_EQ(network::mojom::CrossOriginEmbedderPolicyValue::kRequireCorp, |
| primary_main_frame->cross_origin_embedder_policy().value); |
| } |
| |
| enum class TestMPArchType { |
| kPrerender, |
| kFencedFrame, |
| kPortal, |
| }; |
| |
| class NavigationRequestMPArchBrowserTest |
| : public NavigationRequestBrowserTest, |
| public testing::WithParamInterface<TestMPArchType> { |
| public: |
| NavigationRequestMPArchBrowserTest() { |
| switch (GetParam()) { |
| case TestMPArchType::kPrerender: |
| prerender_helper_ = |
| std::make_unique<test::PrerenderTestHelper>(base::BindRepeating( |
| &NavigationRequestMPArchBrowserTest::web_contents, |
| base::Unretained(this))); |
| break; |
| |
| case TestMPArchType::kFencedFrame: |
| fenced_frame_helper_ = |
| std::make_unique<content::test::FencedFrameTestHelper>(); |
| break; |
| |
| case TestMPArchType::kPortal: |
| scoped_feature_list_.InitWithFeatures( |
| /*enabled_features=*/{blink::features::kPortals, |
| blink::features::kPortalsCrossOrigin}, |
| /*disabled_features=*/{}); |
| break; |
| } |
| } |
| ~NavigationRequestMPArchBrowserTest() override = default; |
| NavigationRequestMPArchBrowserTest( |
| const NavigationRequestMPArchBrowserTest&) = delete; |
| |
| NavigationRequestMPArchBrowserTest& operator=( |
| const NavigationRequestMPArchBrowserTest&) = delete; |
| |
| protected: |
| test::PrerenderTestHelper& prerender_helper() { return *prerender_helper_; } |
| |
| test::FencedFrameTestHelper& fenced_frame_test_helper() { |
| return *fenced_frame_helper_; |
| } |
| |
| WebContents* web_contents() { return shell()->web_contents(); } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| std::unique_ptr<test::PrerenderTestHelper> prerender_helper_; |
| std::unique_ptr<test::FencedFrameTestHelper> fenced_frame_helper_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| NavigationRequestMPArchBrowserTest, |
| ::testing::Values(TestMPArchType::kPrerender, |
| TestMPArchType::kFencedFrame, |
| TestMPArchType::kPortal)); |
| |
| IN_PROC_BROWSER_TEST_P(NavigationRequestMPArchBrowserTest, |
| ShouldNotUpdateHistory) { |
| const auto get_observer = [&](WebContents* web_contents) { |
| return DidFinishNavigationObserver( |
| web_contents, |
| base::BindLambdaForTesting([](NavigationHandle* navigation_handle) { |
| DCHECK_EQ(navigation_handle->GetNavigatingFrameType(), |
| GetParam() == TestMPArchType::kPrerender |
| ? FrameType::kPrerenderMainFrame |
| : GetParam() == TestMPArchType::kFencedFrame |
| ? FrameType::kFencedFrameRoot |
| : FrameType::kPrimaryMainFrame); |
| EXPECT_FALSE(navigation_handle->ShouldUpdateHistory()); |
| })); |
| }; |
| |
| { |
| DidFinishNavigationObserver observer( |
| web_contents(), |
| base::BindLambdaForTesting([](NavigationHandle* navigation_handle) { |
| DCHECK_EQ(navigation_handle->GetNavigatingFrameType(), |
| FrameType::kPrimaryMainFrame); |
| EXPECT_TRUE(navigation_handle->ShouldUpdateHistory()); |
| })); |
| |
| // Navigate the primary page. |
| EXPECT_TRUE( |
| NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"))); |
| } |
| { |
| switch (GetParam()) { |
| case TestMPArchType::kPrerender: { |
| const auto prerender_observer = get_observer(web_contents()); |
| // Load a page in the prerender. |
| prerender_helper().AddPrerender( |
| embedded_test_server()->GetURL("/title1.html?prendering")); |
| break; |
| } |
| |
| case TestMPArchType::kFencedFrame: { |
| const auto fenced_frame_observer = get_observer(web_contents()); |
| // Create a fenced frame. |
| ASSERT_TRUE(fenced_frame_test_helper().CreateFencedFrame( |
| web_contents()->GetPrimaryMainFrame(), |
| embedded_test_server()->GetURL("/fenced_frames/title1.html"))); |
| break; |
| } |
| |
| case TestMPArchType::kPortal: { |
| GURL portal_url(embedded_test_server()->GetURL("/title1.html")); |
| |
| // Create a portal. |
| const char script[] = R"( |
| new Promise(async resolve => { |
| let portal = document.createElement('portal'); |
| portal.src = $1; |
| portal.onload = resolve; |
| document.body.appendChild(portal); |
| }); |
| )"; |
| ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(), |
| content::JsReplace(script, portal_url))); |
| break; |
| } |
| } |
| } |
| } |
| |
| // Tests that when trying to commit an error page for a failed navigation, but |
| // the renderer process of the error page crashed, the navigation won't commit |
| // and the browser won't crash. |
| // Regression test for https://crbug.com/1444360. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| RendererCrashedBeforeCommitErrorPage) { |
| // Navigate to `url_a` first. |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| ASSERT_TRUE(NavigateToURL(shell(), url_a)); |
| |
| // Set up an URLLoaderInterceptor which will cause future navigations to fail. |
| auto url_loader_interceptor = std::make_unique<URLLoaderInterceptor>( |
| base::BindRepeating([](URLLoaderInterceptor::RequestParams* params) { |
| network::URLLoaderCompletionStatus status; |
| status.error_code = net::ERR_NOT_IMPLEMENTED; |
| params->client->OnComplete(status); |
| return true; |
| })); |
| |
| // Do a navigation to `url_b1` that will fail and commit an error page. This |
| // is important so that the next error page navigation won't need to create a |
| // speculative RenderFrameHost (unless RenderDocument is enabled) and won't |
| // get cancelled earlier than commit time due to speculative RFH deletion. |
| GURL url_b1(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| EXPECT_FALSE(NavigateToURL(shell(), url_b1)); |
| EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_b1); |
| EXPECT_TRUE( |
| shell()->web_contents()->GetPrimaryMainFrame()->IsErrorDocument()); |
| |
| // For the next navigation, set up a throttle that will be used to wait for |
| // WillFailRequest() and then defer the navigation, so that we can crash the |
| // error page process first. |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), |
| NavigationThrottle::PROCEED /* will_start_result */, |
| NavigationThrottle::PROCEED /* will_redirect_result */, |
| NavigationThrottle::DEFER /* will_fail_result */, |
| NavigationThrottle::PROCEED /* will_process_result */, |
| NavigationThrottle::PROCEED /* will_commit_without_url_loader_result */); |
| |
| // Start a navigation to `url_b2` that will also fail, but before it commits |
| // an error page, cause the error page process to crash. |
| GURL url_b2(embedded_test_server()->GetURL("b.com", "/title2.html")); |
| TestNavigationManager manager(shell()->web_contents(), url_b2); |
| shell()->LoadURL(url_b2); |
| EXPECT_TRUE(manager.WaitForRequestStart()); |
| |
| // Resume the navigation and wait for WillFailRequest(). After this point, we |
| // will have picked the final RenderFrameHost & RenderProcessHost for the |
| // failed navigation. |
| manager.ResumeNavigation(); |
| installer.WaitForThrottleWillFail(); |
| |
| // Kill the error page process. This will cause the navigation to `url_b2` to |
| // return early in `NavigationRequest::ReadyToCommitNavigation()` and not |
| // commit a new error page. |
| RenderProcessHost* process_to_crash = |
| manager.GetNavigationHandle()->GetRenderFrameHost()->GetProcess(); |
| ASSERT_TRUE(process_to_crash->IsInitializedAndNotDead()); |
| RenderProcessHostWatcher crash_observer( |
| process_to_crash, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| process_to_crash->Shutdown(0); |
| crash_observer.Wait(); |
| ASSERT_FALSE(process_to_crash->IsInitializedAndNotDead()); |
| |
| // Resume the navigation, which won't commit. |
| if (!ShouldCreateNewHostForAllFrames()) { |
| installer.navigation_throttle()->ResumeNavigation(); |
| } |
| EXPECT_TRUE(manager.WaitForNavigationFinished()); |
| EXPECT_FALSE(WaitForLoadStop(shell()->web_contents())); |
| |
| // The tab stayed at `url_b1` as the `url_b2` navigation didn't commit. |
| EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_b1); |
| } |
| |
| namespace { |
| |
| constexpr char kResponseBody[] = "response-body-contents"; |
| |
| // HTTP response template with adjustable header and body contents. |
| const char kResponseTemplate[] = |
| "HTTP/1.1 200 OK\r\n" |
| "%s" |
| "\r\n" |
| "%s"; |
| |
| // Test version of a NavigationThrottle that requests the response body. |
| class ResponseBodyNavigationThrottle : public NavigationThrottle { |
| public: |
| explicit ResponseBodyNavigationThrottle(NavigationHandle* handle) |
| : NavigationThrottle(handle) {} |
| ResponseBodyNavigationThrottle(const ResponseBodyNavigationThrottle&) = |
| delete; |
| ResponseBodyNavigationThrottle& operator=( |
| const ResponseBodyNavigationThrottle&) = delete; |
| ~ResponseBodyNavigationThrottle() override = default; |
| |
| NavigationThrottle::ThrottleCheckResult WillProcessResponse() override { |
| // It is safe to use base::Unretained as the NavigationThrottle will not be |
| // destroyed before the callback is called. |
| navigation_handle()->GetResponseBody( |
| base::BindOnce(&ResponseBodyNavigationThrottle::OnResponseBodyReady, |
| base::Unretained(this))); |
| return NavigationThrottle::DEFER; |
| } |
| |
| const char* GetNameForLogging() override { |
| return "ResponseBodyNavigationThrottle"; |
| } |
| |
| bool was_callback_called() const { return was_callback_called_; } |
| |
| const std::string& response_body() const { return response_body_; } |
| |
| private: |
| void OnResponseBodyReady(const std::string& response_body) { |
| was_callback_called_ = true; |
| response_body_ = response_body; |
| Resume(); |
| } |
| |
| bool was_callback_called_ = false; |
| std::string response_body_; |
| }; |
| |
| } // namespace |
| |
| class NavigationRequestResponseBodyBrowserTest |
| : public NavigationRequestBrowserTest { |
| protected: |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestResponseBodyBrowserTest, Received) { |
| net::test_server::ControllableHttpResponse response(embedded_test_server(), |
| "/target.html"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| ResponseBodyNavigationThrottle* client_throttle = nullptr; |
| |
| // Set the client to register a ResponseBodyNavigationThrottle. Save a pointer |
| // to this throttle in `client_throttle` on registration. |
| content::ShellContentBrowserClient::Get() |
| ->set_create_throttles_for_navigation_callback(base::BindLambdaForTesting( |
| [&client_throttle](content::NavigationHandle* handle) |
| -> std::vector<std::unique_ptr<content::NavigationThrottle>> { |
| std::vector<std::unique_ptr<content::NavigationThrottle>> throttles; |
| auto throttle = |
| std::make_unique<ResponseBodyNavigationThrottle>(handle); |
| client_throttle = throttle.get(); |
| throttles.push_back(std::move(throttle)); |
| return throttles; |
| })); |
| |
| // Start navigating. |
| GURL simple_url(embedded_test_server()->GetURL("/target.html")); |
| TestNavigationManager manager(shell()->web_contents(), simple_url); |
| shell()->LoadURL(simple_url); |
| |
| EXPECT_TRUE(manager.WaitForRequestStart()); |
| manager.ResumeNavigation(); |
| |
| // Build the response with no headers and some body text. |
| response.WaitForRequest(); |
| response.Send(base::StringPrintf(kResponseTemplate, "", kResponseBody)); |
| response.Done(); |
| ASSERT_TRUE(manager.WaitForResponse()); |
| ASSERT_NE(nullptr, client_throttle); |
| EXPECT_TRUE(client_throttle->was_callback_called()); |
| EXPECT_EQ(kResponseBody, client_throttle->response_body()); |
| |
| // Finish the navigation. |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestResponseBodyBrowserTest, |
| ContentLengthZero) { |
| net::test_server::ControllableHttpResponse response(embedded_test_server(), |
| "/target.html"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| ResponseBodyNavigationThrottle* client_throttle = nullptr; |
| |
| // Set the client to register a ResponseBodyNavigationThrottle. Save a pointer |
| // to this throttle in `client_throttle` on registration. |
| content::ShellContentBrowserClient::Get() |
| ->set_create_throttles_for_navigation_callback(base::BindLambdaForTesting( |
| [&client_throttle](content::NavigationHandle* handle) |
| -> std::vector<std::unique_ptr<content::NavigationThrottle>> { |
| std::vector<std::unique_ptr<content::NavigationThrottle>> throttles; |
| auto throttle = |
| std::make_unique<ResponseBodyNavigationThrottle>(handle); |
| client_throttle = throttle.get(); |
| throttles.push_back(std::move(throttle)); |
| return throttles; |
| })); |
| |
| // Start navigating. |
| GURL simple_url(embedded_test_server()->GetURL("/target.html")); |
| TestNavigationManager manager(shell()->web_contents(), simple_url); |
| shell()->LoadURL(simple_url); |
| |
| EXPECT_TRUE(manager.WaitForRequestStart()); |
| manager.ResumeNavigation(); |
| |
| // Build the response with Content-Length: 0 and some body text. |
| response.WaitForRequest(); |
| response.Send(base::StringPrintf(kResponseTemplate, "Content-Length: 0", |
| kResponseBody)); |
| response.Done(); |
| ASSERT_TRUE(manager.WaitForResponse()); |
| ASSERT_NE(nullptr, client_throttle); |
| EXPECT_TRUE(client_throttle->was_callback_called()); |
| // The received response body is empty due to the Content-Length value. |
| EXPECT_EQ(std::string(), client_throttle->response_body()); |
| |
| // Finish the navigation. |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestResponseBodyBrowserTest, |
| BodyLargerThanDataPipeSize) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| ResponseBodyNavigationThrottle* client_throttle = nullptr; |
| |
| // Set the client to register a ResponseBodyNavigationThrottle. Save a pointer |
| // to this throttle in `client_throttle` on registration. |
| content::ShellContentBrowserClient::Get() |
| ->set_create_throttles_for_navigation_callback(base::BindLambdaForTesting( |
| [&client_throttle](content::NavigationHandle* handle) |
| -> std::vector<std::unique_ptr<content::NavigationThrottle>> { |
| std::vector<std::unique_ptr<content::NavigationThrottle>> throttles; |
| auto throttle = |
| std::make_unique<ResponseBodyNavigationThrottle>(handle); |
| client_throttle = throttle.get(); |
| throttles.push_back(std::move(throttle)); |
| return throttles; |
| })); |
| |
| // Start navigating to a page with a large body (>5 million characters). |
| GURL simple_url(embedded_test_server()->GetURL("/long_response_body.html")); |
| TestNavigationManager manager(shell()->web_contents(), simple_url); |
| shell()->LoadURL(simple_url); |
| |
| ASSERT_TRUE(manager.WaitForResponse()); |
| ASSERT_NE(nullptr, client_throttle); |
| EXPECT_TRUE(client_throttle->was_callback_called()); |
| // Ensure that the received response body contains text from the target page. |
| EXPECT_NE(client_throttle->response_body().npos, |
| client_throttle->response_body().find( |
| "Test page with a long response body")); |
| // The initial response body chunk may be smaller than the max data pipe size. |
| EXPECT_LE( |
| client_throttle->response_body().length(), |
| network::features::GetDataPipeDefaultAllocationSize( |
| network::features::DataPipeAllocationSize::kLargerSizeIfPossible)); |
| |
| // Finish the navigation. |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| } |
| |
| // Helper class to turn off strict site isolation, to allow testing dynamic |
| // isolated origins added for future BrowsingInstances. |
| class NavigationRequestNoSiteIsolationBrowserTest |
| : public NavigationRequestBrowserTest { |
| public: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch(switches::kDisableSiteIsolation); |
| command_line->RemoveSwitch(switches::kSitePerProcess); |
| NavigationRequestBrowserTest::SetUpCommandLine(command_line); |
| } |
| }; |
| |
| // Test the early swap metrics logged when performing the early swap out of the |
| // initial RenderFrameHost. Currently, the vast majority of navigations are |
| // allowed to reuse the initial RFH. One exception is a navigation to a |
| // future-isolated origin, which forces a BrowsingInstance swap so that the |
| // isolation can take effect right away, so this is the case this test |
| // exercises. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestNoSiteIsolationBrowserTest, |
| EarlySwapMetrics_InitialFrame) { |
| base::HistogramTester histograms; |
| |
| EXPECT_FALSE(SiteIsolationPolicy::UseDedicatedProcessesForAllSites()); |
| |
| // Isolate bar.com in future BrowsingInstances. |
| GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title1.html")); |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| policy->AddFutureIsolatedOrigins( |
| {url::Origin::Create(bar_url)}, |
| ChildProcessSecurityPolicy::IsolatedOriginSource::TEST); |
| |
| // Navigate the initial frame to bar.com which requires isolation. Currently, |
| // the isolation heuristics force a BrowsingInstance and a RenderFrameHost |
| // swap, so the initial RFH cannot be reused for such a URL, and hence we |
| // should create a speculative RenderFrameHost and swap it in early because |
| // the initial frame is not live. |
| ASSERT_FALSE( |
| shell()->web_contents()->GetPrimaryMainFrame()->IsRenderFrameLive()); |
| ASSERT_TRUE(NavigateToURL(shell(), bar_url)); |
| histograms.ExpectUniqueSample( |
| "Navigation.EarlyRenderFrameHostSwapType", |
| NavigationRequest::EarlyRenderFrameHostSwapType::kInitialFrame, 1); |
| histograms.ExpectUniqueSample( |
| "Navigation.EarlyRenderFrameHostSwap.HasCommitted", 1, 1); |
| histograms.ExpectUniqueSample( |
| "Navigation.EarlyRenderFrameHostSwap.IsInOutermostMainFrame", 1, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| EarlySwapMetrics_CrashNoCommit) { |
| GURL url_a(embedded_test_server()->GetURL("a.test", "/title1.html")); |
| { |
| base::HistogramTester histograms; |
| // Ensure that a normal navigation doesn't log early swap metrics. |
| ASSERT_TRUE(NavigateToURL(shell(), url_a)); |
| histograms.ExpectUniqueSample( |
| "Navigation.EarlyRenderFrameHostSwapType", |
| NavigationRequest::EarlyRenderFrameHostSwapType::kNone, 1); |
| histograms.ExpectTotalCount( |
| "Navigation.EarlyRenderFrameHostSwap.HasCommitted", 0); |
| histograms.ExpectTotalCount( |
| "Navigation.EarlyRenderFrameHostSwap.IsInOutermostMainFrame", 0); |
| } |
| |
| // Crash the main frame. |
| RenderProcessHost* process = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetProcess(); |
| RenderProcessHostWatcher crash_observer( |
| process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| process->Shutdown(0); |
| crash_observer.Wait(); |
| ASSERT_FALSE( |
| shell()->web_contents()->GetPrimaryMainFrame()->IsRenderFrameLive()); |
| |
| { |
| base::HistogramTester histograms; |
| |
| // Load a page that results in a 204 error. This should result in an early |
| // RFH swap that leaves the new RFH in a blank state. The HasCommitted |
| // early swap metric should be logged as false. |
| GURL url_204(embedded_test_server()->GetURL("a.test", "/nocontent")); |
| EXPECT_TRUE(NavigateToURLAndExpectNoCommit(shell(), url_204)); |
| |
| histograms.ExpectUniqueSample( |
| "Navigation.EarlyRenderFrameHostSwapType", |
| NavigationRequest::EarlyRenderFrameHostSwapType::kCrashedFrame, 1); |
| histograms.ExpectUniqueSample( |
| "Navigation.EarlyRenderFrameHostSwap.HasCommitted", 0, 1); |
| histograms.ExpectUniqueSample( |
| "Navigation.EarlyRenderFrameHostSwap.IsInOutermostMainFrame", 1, 1); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| EarlySwapMetrics_CrashedSubframe) { |
| // Needed to guarantee that the subframe will be an OOPIF on Android. |
| IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess()); |
| |
| GURL url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| ASSERT_TRUE(NavigateToURL(shell(), url)); |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| RenderFrameHostImpl* subframe_rfh = root->child_at(0)->current_frame_host(); |
| |
| // Crash the subframe. |
| RenderProcessHost* child_process = subframe_rfh->GetProcess(); |
| RenderProcessHostWatcher crash_observer( |
| child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| child_process->Shutdown(0); |
| crash_observer.Wait(); |
| ASSERT_FALSE(subframe_rfh->IsRenderFrameLive()); |
| |
| { |
| base::HistogramTester histograms; |
| |
| // Navigate the subframe, which should result in an early RFH swap. |
| GURL subframe_url(embedded_test_server()->GetURL("c.com", "/title1.html")); |
| TestNavigationObserver load_observer(shell()->web_contents()); |
| EXPECT_TRUE( |
| ExecJs(shell(), JsReplace("frames[0].location = $1", subframe_url))); |
| load_observer.Wait(); |
| |
| histograms.ExpectUniqueSample( |
| "Navigation.EarlyRenderFrameHostSwapType", |
| NavigationRequest::EarlyRenderFrameHostSwapType::kCrashedFrame, 1); |
| histograms.ExpectUniqueSample( |
| "Navigation.EarlyRenderFrameHostSwap.HasCommitted", 1, 1); |
| histograms.ExpectUniqueSample( |
| "Navigation.EarlyRenderFrameHostSwap.IsInOutermostMainFrame", 0, 1); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| EarlySwapMetrics_NoSwapForWebUI) { |
| base::HistogramTester histograms; |
| |
| // Navigate the initial frame to a WebUI URL. The initial RFH should be reused |
| // for such a URL, and hence there should be no early swap recorded in the |
| // metrics. |
| ASSERT_FALSE( |
| shell()->web_contents()->GetPrimaryMainFrame()->IsRenderFrameLive()); |
| GURL web_ui_url(GetWebUIURL("gpu")); |
| ASSERT_TRUE(NavigateToURL(shell(), web_ui_url)); |
| histograms.ExpectUniqueSample( |
| "Navigation.EarlyRenderFrameHostSwapType", |
| NavigationRequest::EarlyRenderFrameHostSwapType::kNone, 1); |
| histograms.ExpectTotalCount( |
| "Navigation.EarlyRenderFrameHostSwap.HasCommitted", 0); |
| histograms.ExpectTotalCount( |
| "Navigation.EarlyRenderFrameHostSwap.IsInOutermostMainFrame", 0); |
| } |
| |
| // Check the output of NavigationHandle::SandboxFlagsInitiator() when the |
| // navigation is initiated from the omnibox. It must be `kNone`. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| InitiatorSandboxFlags_BrowserInitiated) { |
| GURL url_initiator = embedded_test_server()->GetURL( |
| "a.com", "/set-header?Content-Security-Policy: sandbox allow-scripts"); |
| ASSERT_TRUE(NavigateToURL(shell(), url_initiator)); |
| |
| GURL url_target = embedded_test_server()->GetURL("a.com", "/"); |
| TestNavigationManager manager(shell()->web_contents(), url_target); |
| shell()->LoadURL(url_target); |
| EXPECT_TRUE(manager.WaitForRequestStart()); |
| ASSERT_TRUE(manager.GetNavigationHandle()); |
| EXPECT_EQ(manager.GetNavigationHandle()->SandboxFlagsInitiator(), |
| network::mojom::WebSandboxFlags::kNone); |
| EXPECT_EQ(manager.GetNavigationHandle()->SandboxFlagsInherited(), |
| network::mojom::WebSandboxFlags::kNone); |
| } |
| |
| // Check the output of NavigationHandle::SandboxFlagsInitiator() when the |
| // navigation is initiated by the top-level document. It must match. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| InitiatorSandboxFlags_DocumentInitiated) { |
| GURL url_initiator = embedded_test_server()->GetURL( |
| "a.com", "/set-header?Content-Security-Policy: sandbox allow-scripts"); |
| ASSERT_TRUE(NavigateToURL(shell(), url_initiator)); |
| |
| GURL url_target = embedded_test_server()->GetURL("a.com", "/"); |
| TestNavigationManager manager(shell()->web_contents(), url_target); |
| EXPECT_TRUE(ExecJs(shell()->web_contents(), |
| JsReplace("location.href = $1", url_target))); |
| EXPECT_TRUE(manager.WaitForRequestStart()); |
| ASSERT_TRUE(manager.GetNavigationHandle()); |
| EXPECT_EQ(manager.GetNavigationHandle()->SandboxFlagsInitiator(), |
| network::mojom::WebSandboxFlags::kAll & |
| ~network::mojom::WebSandboxFlags::kScripts & |
| ~network::mojom::WebSandboxFlags::kAutomaticFeatures); |
| EXPECT_EQ(manager.GetNavigationHandle()->SandboxFlagsInherited(), |
| network::mojom::WebSandboxFlags::kNone); |
| } |
| |
| // Check the output of NavigationHandle::SandboxFlagsInitiator() when the |
| // navigation is initiated by a sandboxed iframe. |
| IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, |
| InitiatorSandboxFlags_SandboxedIframe) { |
| // Create the parent document: |
| GURL parent_url = embedded_test_server()->GetURL("a.com", "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), parent_url)); |
| |
| // Create the child frame. It has sandbox flags from the |
| // Content-Security-Policy and from its iframe.sandbox flags: |
| GURL url_initiator = embedded_test_server()->GetURL( |
| "a.com", |
| "/set-header?Content-Security-Policy: sandbox allow-scripts allow-forms"); |
| EXPECT_TRUE(ExecJs(shell()->web_contents(), JsReplace(R"( |
| const iframe = document.createElement("iframe"); |
| iframe.src = $1; |
| iframe.sandbox = "allow-scripts allow-popups"; |
| document.body.appendChild(iframe); |
| )", |
| url_initiator))); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>( |
| shell()->web_contents()->GetPrimaryMainFrame()); |
| ASSERT_EQ(1u, main_frame->child_count()); |
| |
| // Navigate the child frame. |
| GURL url_target = embedded_test_server()->GetURL("a.com", "/"); |
| TestNavigationManager manager(shell()->web_contents(), url_target); |
| EXPECT_TRUE(ExecJs(main_frame->child_at(0), |
| JsReplace("location.href = $1", url_target))); |
| EXPECT_TRUE(manager.WaitForRequestStart()); |
| ASSERT_TRUE(manager.GetNavigationHandle()); |
| EXPECT_EQ(manager.GetNavigationHandle()->SandboxFlagsInitiator(), |
| // `allow-script`: |
| network::mojom::WebSandboxFlags::kAll & |
| ~network::mojom::WebSandboxFlags::kScripts & |
| ~network::mojom::WebSandboxFlags::kAutomaticFeatures); |
| EXPECT_EQ( |
| manager.GetNavigationHandle()->SandboxFlagsInherited(), |
| //`allow-scripts allow-popups`: |
| network::mojom::WebSandboxFlags::kAll & |
| ~network::mojom::WebSandboxFlags::kScripts & |
| ~network::mojom::WebSandboxFlags::kPopups & |
| ~network::mojom::WebSandboxFlags::kTopNavigationToCustomProtocols & |
| ~network::mojom::WebSandboxFlags::kAutomaticFeatures); |
| } |
| |
| // Tests the scenario when a navigation without URLLoader is cancelled and an |
| // error page is committed using the same NavigationRequest. |
| // See https://crbug.com/1487944. |
| IN_PROC_BROWSER_TEST_F( |
| NavigationRequestBrowserTest, |
| ThrottleDeferAndCancelCommitWithoutUrlLoaderWithErrorPage) { |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| GURL about_blank_url(url::kAboutBlankURL); |
| |
| // Perform a new-document navigation (setup). |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| // Navigate to about:blank so the NavigationRequest is expected to commit |
| // without URL loader. |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| NavigationHandleObserver observer(shell()->web_contents(), about_blank_url); |
| TestNavigationThrottleInstaller installer( |
| shell()->web_contents(), NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, NavigationThrottle::DEFER, |
| NavigationThrottle::DEFER, NavigationThrottle::DEFER); |
| |
| shell()->LoadURL(about_blank_url); |
| |
| // Wait for WillCommitWithoutUrlLoader. |
| installer.WaitForThrottleWillCommitWithoutUrlLoader(); |
| EXPECT_EQ(0, installer.will_start_called()); |
| EXPECT_EQ(0, installer.will_redirect_called()); |
| EXPECT_EQ(0, installer.will_fail_called()); |
| EXPECT_EQ(0, installer.will_process_called()); |
| EXPECT_EQ(1, installer.will_commit_without_url_loader_called()); |
| |
| // Cancel the deferred navigation with `net::ERR_BLOCKED_BY_RESPONSE`, so |
| // the NavigationRequest will be used for an error page commit. |
| installer.navigation_throttle()->CancelNavigation( |
| {NavigationThrottle::CANCEL_AND_IGNORE, net::ERR_BLOCKED_BY_RESPONSE}); |
| |
| // Wait for the end of the navigation. |
| navigation_observer.Wait(); |
| |
| EXPECT_FALSE(observer.is_same_document()); |
| EXPECT_TRUE(observer.has_committed()); |
| EXPECT_FALSE(observer.was_redirected()); |
| EXPECT_TRUE(observer.is_error()); |
| |
| EXPECT_TRUE( |
| shell()->web_contents()->GetPrimaryMainFrame()->IsErrorDocument()); |
| } |
| } |
| |
| } // namespace content |