blob: a076ad2f007adf031437ac23fc54565c9e813b95 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/renderer_host/navigation_request.h"
#include "base/bind.h"
#include "base/macros.h"
#include "base/optional.h"
#include "build/build_config.h"
#include "content/public/browser/navigation_throttle.h"
#include "content/public/browser/ssl_status.h"
#include "content/public/common/content_client.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/test_navigation_throttle.h"
#include "content/test/navigation_simulator_impl.h"
#include "content/test/test_content_browser_client.h"
#include "content/test/test_render_frame_host.h"
#include "content/test/test_web_contents.h"
#include "net/ssl/ssl_connection_status_flags.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
namespace content {
// Test version of a NavigationThrottle that will execute a callback when
// called.
class DeletingNavigationThrottle : public NavigationThrottle {
public:
DeletingNavigationThrottle(NavigationHandle* handle,
const base::RepeatingClosure& deletion_callback)
: NavigationThrottle(handle), deletion_callback_(deletion_callback) {}
~DeletingNavigationThrottle() override {}
NavigationThrottle::ThrottleCheckResult WillStartRequest() override {
deletion_callback_.Run();
return NavigationThrottle::PROCEED;
}
NavigationThrottle::ThrottleCheckResult WillRedirectRequest() override {
deletion_callback_.Run();
return NavigationThrottle::PROCEED;
}
NavigationThrottle::ThrottleCheckResult WillFailRequest() override {
deletion_callback_.Run();
return NavigationThrottle::PROCEED;
}
NavigationThrottle::ThrottleCheckResult WillProcessResponse() override {
deletion_callback_.Run();
return NavigationThrottle::PROCEED;
}
const char* GetNameForLogging() override {
return "DeletingNavigationThrottle";
}
private:
base::RepeatingClosure deletion_callback_;
};
class NavigationRequestTest : public RenderViewHostImplTestHarness {
public:
NavigationRequestTest()
: was_callback_called_(false),
callback_result_(NavigationThrottle::DEFER) {}
void SetUp() override {
RenderViewHostImplTestHarness::SetUp();
CreateNavigationHandle();
contents()->GetMainFrame()->InitializeRenderFrameIfNeeded();
}
void TearDown() override {
// Release the |request_| before destroying the WebContents, to match
// the WebContentsObserverConsistencyChecker expectations.
request_.reset();
RenderViewHostImplTestHarness::TearDown();
}
void CancelDeferredNavigation(
NavigationThrottle::ThrottleCheckResult result) {
request_->CancelDeferredNavigationInternal(result);
}
// Helper function to call WillStartRequest on |handle|. If this function
// returns DEFER, |callback_result_| will be set to the actual result of
// the throttle checks when they are finished.
void SimulateWillStartRequest() {
was_callback_called_ = false;
callback_result_ = NavigationThrottle::DEFER;
// It's safe to use base::Unretained since the NavigationRequest is owned by
// the NavigationRequestTest.
request_->set_complete_callback_for_testing(
base::BindOnce(&NavigationRequestTest::UpdateThrottleCheckResult,
base::Unretained(this)));
request_->WillStartRequest();
}
// Helper function to call WillRedirectRequest on |handle|. If this function
// returns DEFER, |callback_result_| will be set to the actual result of the
// throttle checks when they are finished.
// TODO(clamy): this should also simulate that WillStartRequest was called if
// it has not been called before.
void SimulateWillRedirectRequest() {
was_callback_called_ = false;
callback_result_ = NavigationThrottle::DEFER;
// It's safe to use base::Unretained since the NavigationRequest is owned by
// the NavigationRequestTest.
request_->set_complete_callback_for_testing(
base::BindOnce(&NavigationRequestTest::UpdateThrottleCheckResult,
base::Unretained(this)));
request_->WillRedirectRequest(
GURL(), CoopCoepCrossOriginIsolatedInfo::CreateNonIsolated(),
nullptr /* post_redirect_process */);
}
// Helper function to call WillFailRequest on |handle|. If this function
// returns DEFER, |callback_result_| will be set to the actual result of the
// throttle checks when they are finished.
void SimulateWillFailRequest(
net::Error net_error_code,
const base::Optional<net::SSLInfo> ssl_info = base::nullopt) {
was_callback_called_ = false;
callback_result_ = NavigationThrottle::DEFER;
request_->set_net_error(net_error_code);
// It's safe to use base::Unretained since the NavigationRequest is owned by
// the NavigationRequestTest.
request_->set_complete_callback_for_testing(
base::BindOnce(&NavigationRequestTest::UpdateThrottleCheckResult,
base::Unretained(this)));
request_->WillFailRequest();
}
// Whether the callback was called.
bool was_callback_called() const { return was_callback_called_; }
// Returns the callback_result.
NavigationThrottle::ThrottleCheckResult callback_result() const {
return callback_result_;
}
NavigationRequest::NavigationState state() { return request_->state(); }
bool call_counts_match(TestNavigationThrottle* throttle,
int start,
int redirect,
int failure,
int process) {
return start == throttle->GetCallCount(
TestNavigationThrottle::WILL_START_REQUEST) &&
redirect == throttle->GetCallCount(
TestNavigationThrottle::WILL_REDIRECT_REQUEST) &&
failure == throttle->GetCallCount(
TestNavigationThrottle::WILL_FAIL_REQUEST) &&
process == throttle->GetCallCount(
TestNavigationThrottle::WILL_PROCESS_RESPONSE);
}
// Creates, register and returns a TestNavigationThrottle that will
// synchronously return |result| on checks by default.
TestNavigationThrottle* CreateTestNavigationThrottle(
NavigationThrottle::ThrottleCheckResult result) {
TestNavigationThrottle* test_throttle =
new TestNavigationThrottle(request_.get());
test_throttle->SetResponseForAllMethods(TestNavigationThrottle::SYNCHRONOUS,
result);
request_->RegisterThrottleForTesting(
std::unique_ptr<TestNavigationThrottle>(test_throttle));
return test_throttle;
}
// Creates, register and returns a TestNavigationThrottle that will
// synchronously return |result| on check for the given |method|, and
// NavigationThrottle::PROCEED otherwise.
TestNavigationThrottle* CreateTestNavigationThrottle(
TestNavigationThrottle::ThrottleMethod method,
NavigationThrottle::ThrottleCheckResult result) {
TestNavigationThrottle* test_throttle =
CreateTestNavigationThrottle(NavigationThrottle::PROCEED);
test_throttle->SetResponse(method, TestNavigationThrottle::SYNCHRONOUS,
result);
return test_throttle;
}
// TODO(zetamoo): Use NavigationSimulator instead of creating
// NavigationRequest and NavigationHandleImpl.
void CreateNavigationHandle() {
auto common_params = CreateCommonNavigationParams();
common_params->initiator_origin =
url::Origin::Create(GURL("https://initiator.example.com"));
auto commit_params = CreateCommitNavigationParams();
commit_params->frame_policy =
main_test_rfh()->frame_tree_node()->pending_frame_policy();
request_ = NavigationRequest::CreateBrowserInitiated(
main_test_rfh()->frame_tree_node(), std::move(common_params),
std::move(commit_params), false /* browser-initiated */,
false /* is_prerendering */, false /* was_opener_suppressed */,
nullptr /* initiator_frame_token */,
ChildProcessHost::kInvalidUniqueID /* initiator_process_id */,
std::string() /* extra_headers */, nullptr /* frame_entry */,
nullptr /* entry */, nullptr /* post_body */,
nullptr /* navigation_ui_data */, base::nullopt /* impression */);
request_->StartNavigation(true);
}
private:
// The callback provided to NavigationRequest::WillStartRequest,
// NavigationRequest::WillRedirectRequest, and
// NavigationRequest::WillFailRequest during the tests.
bool UpdateThrottleCheckResult(
NavigationThrottle::ThrottleCheckResult result) {
callback_result_ = result;
was_callback_called_ = true;
return true;
}
std::unique_ptr<NavigationRequest> request_;
bool was_callback_called_;
NavigationThrottle::ThrottleCheckResult callback_result_;
};
// Checks that the request_context_type is properly set.
// Note: can be extended to cover more internal members.
TEST_F(NavigationRequestTest, SimpleDataChecksRedirectAndProcess) {
const GURL kUrl1 = GURL("http://chromium.org");
const GURL kUrl2 = GURL("http://google.com");
auto navigation =
NavigationSimulatorImpl::CreateRendererInitiated(kUrl1, main_rfh());
navigation->Start();
EXPECT_EQ(blink::mojom::RequestContextType::HYPERLINK,
NavigationRequest::From(navigation->GetNavigationHandle())
->request_context_type());
EXPECT_EQ(net::HttpResponseInfo::CONNECTION_INFO_UNKNOWN,
navigation->GetNavigationHandle()->GetConnectionInfo());
navigation->set_http_connection_info(
net::HttpResponseInfo::CONNECTION_INFO_HTTP1_1);
navigation->Redirect(kUrl2);
EXPECT_EQ(blink::mojom::RequestContextType::HYPERLINK,
NavigationRequest::From(navigation->GetNavigationHandle())
->request_context_type());
EXPECT_EQ(net::HttpResponseInfo::CONNECTION_INFO_HTTP1_1,
navigation->GetNavigationHandle()->GetConnectionInfo());
navigation->set_http_connection_info(
net::HttpResponseInfo::CONNECTION_INFO_QUIC_35);
navigation->ReadyToCommit();
EXPECT_EQ(blink::mojom::RequestContextType::HYPERLINK,
NavigationRequest::From(navigation->GetNavigationHandle())
->request_context_type());
EXPECT_EQ(net::HttpResponseInfo::CONNECTION_INFO_QUIC_35,
navigation->GetNavigationHandle()->GetConnectionInfo());
}
TEST_F(NavigationRequestTest, SimpleDataCheckNoRedirect) {
const GURL kUrl = GURL("http://chromium.org");
auto navigation =
NavigationSimulatorImpl::CreateRendererInitiated(kUrl, main_rfh());
navigation->Start();
EXPECT_EQ(net::HttpResponseInfo::CONNECTION_INFO_UNKNOWN,
navigation->GetNavigationHandle()->GetConnectionInfo());
navigation->set_http_connection_info(
net::HttpResponseInfo::CONNECTION_INFO_QUIC_35);
navigation->ReadyToCommit();
EXPECT_EQ(net::HttpResponseInfo::CONNECTION_INFO_QUIC_35,
navigation->GetNavigationHandle()->GetConnectionInfo());
}
TEST_F(NavigationRequestTest, SimpleDataChecksFailure) {
const GURL kUrl = GURL("http://chromium.org");
auto navigation =
NavigationSimulatorImpl::CreateRendererInitiated(kUrl, main_rfh());
navigation->Start();
EXPECT_EQ(blink::mojom::RequestContextType::HYPERLINK,
NavigationRequest::From(navigation->GetNavigationHandle())
->request_context_type());
EXPECT_EQ(net::HttpResponseInfo::CONNECTION_INFO_UNKNOWN,
navigation->GetNavigationHandle()->GetConnectionInfo());
navigation->Fail(net::ERR_CERT_DATE_INVALID);
EXPECT_EQ(blink::mojom::RequestContextType::HYPERLINK,
NavigationRequest::From(navigation->GetNavigationHandle())
->request_context_type());
EXPECT_EQ(net::ERR_CERT_DATE_INVALID,
navigation->GetNavigationHandle()->GetNetErrorCode());
}
// Checks that a navigation deferred during WillStartRequest can be properly
// cancelled.
TEST_F(NavigationRequestTest, CancelDeferredWillStart) {
TestNavigationThrottle* test_throttle =
CreateTestNavigationThrottle(NavigationThrottle::DEFER);
EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, state());
EXPECT_TRUE(call_counts_match(test_throttle, 0, 0, 0, 0));
// Simulate WillStartRequest. The request should be deferred. The callback
// should not have been called.
SimulateWillStartRequest();
EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, state());
EXPECT_FALSE(was_callback_called());
EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 0, 0));
// Cancel the request. The callback should have been called.
CancelDeferredNavigation(NavigationThrottle::CANCEL_AND_IGNORE);
EXPECT_EQ(NavigationRequest::CANCELING, state());
EXPECT_TRUE(was_callback_called());
EXPECT_EQ(NavigationThrottle::CANCEL_AND_IGNORE, callback_result());
EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 0, 0));
}
// Checks that a navigation deferred during WillRedirectRequest can be properly
// cancelled.
TEST_F(NavigationRequestTest, CancelDeferredWillRedirect) {
TestNavigationThrottle* test_throttle =
CreateTestNavigationThrottle(NavigationThrottle::DEFER);
EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, state());
EXPECT_TRUE(call_counts_match(test_throttle, 0, 0, 0, 0));
// Simulate WillRedirectRequest. The request should be deferred. The callback
// should not have been called.
SimulateWillRedirectRequest();
EXPECT_EQ(NavigationRequest::WILL_REDIRECT_REQUEST, state());
EXPECT_FALSE(was_callback_called());
EXPECT_TRUE(call_counts_match(test_throttle, 0, 1, 0, 0));
// Cancel the request. The callback should have been called.
CancelDeferredNavigation(NavigationThrottle::CANCEL_AND_IGNORE);
EXPECT_EQ(NavigationRequest::CANCELING, state());
EXPECT_TRUE(was_callback_called());
EXPECT_EQ(NavigationThrottle::CANCEL_AND_IGNORE, callback_result());
EXPECT_TRUE(call_counts_match(test_throttle, 0, 1, 0, 0));
}
// Checks that a navigation deferred during WillFailRequest can be properly
// cancelled.
TEST_F(NavigationRequestTest, CancelDeferredWillFail) {
TestNavigationThrottle* test_throttle = CreateTestNavigationThrottle(
TestNavigationThrottle::WILL_FAIL_REQUEST, NavigationThrottle::DEFER);
EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, state());
EXPECT_TRUE(call_counts_match(test_throttle, 0, 0, 0, 0));
// Simulate WillStartRequest.
SimulateWillStartRequest();
EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 0, 0));
// Simulate WillFailRequest. The request should be deferred. The callback
// should not have been called.
SimulateWillFailRequest(net::ERR_CERT_DATE_INVALID);
EXPECT_EQ(NavigationRequest::WILL_FAIL_REQUEST, state());
EXPECT_FALSE(was_callback_called());
EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 1, 0));
// Cancel the request. The callback should have been called.
CancelDeferredNavigation(NavigationThrottle::CANCEL_AND_IGNORE);
EXPECT_EQ(NavigationRequest::CANCELING, state());
EXPECT_TRUE(was_callback_called());
EXPECT_EQ(NavigationThrottle::CANCEL_AND_IGNORE, callback_result());
EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 1, 0));
}
// Checks that a navigation deferred can be canceled and not ignored.
TEST_F(NavigationRequestTest, CancelDeferredWillRedirectNoIgnore) {
TestNavigationThrottle* test_throttle =
CreateTestNavigationThrottle(NavigationThrottle::DEFER);
EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, state());
EXPECT_TRUE(call_counts_match(test_throttle, 0, 0, 0, 0));
// Simulate WillStartRequest. The request should be deferred. The callback
// should not have been called.
SimulateWillStartRequest();
EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, state());
EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 0, 0));
// Cancel the request. The callback should have been called with CANCEL, and
// not CANCEL_AND_IGNORE.
CancelDeferredNavigation(NavigationThrottle::CANCEL);
EXPECT_EQ(NavigationRequest::CANCELING, state());
EXPECT_TRUE(was_callback_called());
EXPECT_EQ(NavigationThrottle::CANCEL, callback_result());
EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 0, 0));
}
// Checks that a navigation deferred by WillFailRequest can be canceled and not
// ignored.
TEST_F(NavigationRequestTest, CancelDeferredWillFailNoIgnore) {
TestNavigationThrottle* test_throttle = CreateTestNavigationThrottle(
TestNavigationThrottle::WILL_FAIL_REQUEST, NavigationThrottle::DEFER);
EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, state());
EXPECT_TRUE(call_counts_match(test_throttle, 0, 0, 0, 0));
// Simulate WillStartRequest.
SimulateWillStartRequest();
EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 0, 0));
// Simulate WillFailRequest. The request should be deferred. The callback
// should not have been called.
SimulateWillFailRequest(net::ERR_CERT_DATE_INVALID);
EXPECT_EQ(NavigationRequest::WILL_FAIL_REQUEST, state());
EXPECT_FALSE(was_callback_called());
EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 1, 0));
// Cancel the request. The callback should have been called with CANCEL, and
// not CANCEL_AND_IGNORE.
CancelDeferredNavigation(NavigationThrottle::CANCEL);
EXPECT_EQ(NavigationRequest::CANCELING, state());
EXPECT_TRUE(was_callback_called());
EXPECT_EQ(NavigationThrottle::CANCEL, callback_result());
EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 1, 0));
}
// Checks that data from the SSLInfo passed into SimulateWillStartRequest() is
// stored on the handle.
TEST_F(NavigationRequestTest, WillFailRequestSetsSSLInfo) {
uint16_t cipher_suite = 0xc02f; // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
int connection_status = 0;
net::SSLConnectionStatusSetCipherSuite(cipher_suite, &connection_status);
// Set some test values.
net::SSLInfo ssl_info;
ssl_info.cert_status = net::CERT_STATUS_AUTHORITY_INVALID;
ssl_info.connection_status = connection_status;
const GURL kUrl = GURL("https://chromium.org");
auto navigation =
NavigationSimulatorImpl::CreateRendererInitiated(kUrl, main_rfh());
navigation->SetSSLInfo(ssl_info);
navigation->Fail(net::ERR_CERT_DATE_INVALID);
EXPECT_EQ(net::CERT_STATUS_AUTHORITY_INVALID,
navigation->GetNavigationHandle()->GetSSLInfo()->cert_status);
EXPECT_EQ(connection_status,
navigation->GetNavigationHandle()->GetSSLInfo()->connection_status);
}
namespace {
// Helper throttle which checks that it can access NavigationHandle's
// RenderFrameHost in WillFailRequest() and then defers the failure.
class GetRenderFrameHostOnFailureNavigationThrottle
: public NavigationThrottle {
public:
GetRenderFrameHostOnFailureNavigationThrottle(NavigationHandle* handle)
: NavigationThrottle(handle) {}
~GetRenderFrameHostOnFailureNavigationThrottle() override {}
NavigationThrottle::ThrottleCheckResult WillFailRequest() override {
EXPECT_TRUE(navigation_handle()->GetRenderFrameHost());
return NavigationThrottle::DEFER;
}
const char* GetNameForLogging() override {
return "GetRenderFrameHostOnFailureNavigationThrottle";
}
private:
DISALLOW_COPY_AND_ASSIGN(GetRenderFrameHostOnFailureNavigationThrottle);
};
class ThrottleTestContentBrowserClient : public ContentBrowserClient {
std::vector<std::unique_ptr<NavigationThrottle>> CreateThrottlesForNavigation(
NavigationHandle* navigation_handle) override {
std::vector<std::unique_ptr<NavigationThrottle>> throttle;
throttle.push_back(
std::make_unique<GetRenderFrameHostOnFailureNavigationThrottle>(
navigation_handle));
return throttle;
}
};
} // namespace
// Verify that the NavigationHandle::GetRenderFrameHost() can be retrieved by a
// throttle in WillFailRequest(), as well as after deferring the failure. This
// is allowed, since at that point the final RenderFrameHost will have already
// been chosen. See https://crbug.com/817881.
TEST_F(NavigationRequestTest, WillFailRequestCanAccessRenderFrameHost) {
std::unique_ptr<ContentBrowserClient> client(
new ThrottleTestContentBrowserClient);
ContentBrowserClient* old_browser_client =
SetBrowserClientForTesting(client.get());
const GURL kUrl = GURL("http://chromium.org");
auto navigation =
NavigationSimulatorImpl::CreateRendererInitiated(kUrl, main_rfh());
navigation->SetAutoAdvance(false);
navigation->Start();
navigation->Fail(net::ERR_CERT_DATE_INVALID);
EXPECT_EQ(
NavigationRequest::WILL_FAIL_REQUEST,
NavigationRequest::From(navigation->GetNavigationHandle())->state());
EXPECT_TRUE(navigation->GetNavigationHandle()->GetRenderFrameHost());
NavigationRequest::From(navigation->GetNavigationHandle())
->GetNavigationThrottleRunnerForTesting()
->CallResumeForTesting();
EXPECT_TRUE(navigation->GetNavigationHandle()->GetRenderFrameHost());
SetBrowserClientForTesting(old_browser_client);
}
TEST_F(NavigationRequestTest, PolicyContainerInheritance) {
struct TestCase {
const char* url;
bool expect_inherit;
} cases[]{{"about:blank", true},
{"data:text/plain,hello", true},
{"file://local", false},
{"http://chromium.org", false}};
const GURL kUrl1 = GURL("http://chromium.org");
auto navigation =
NavigationSimulatorImpl::CreateRendererInitiated(kUrl1, main_rfh());
navigation->Commit();
for (auto test : cases) {
// We navigate child frames because the BlockedSchemeNavigationThrottle
// restricts navigations in the main frame.
auto* child_frame = static_cast<TestRenderFrameHost*>(
content::RenderFrameHostTester::For(main_rfh())->AppendChild("child"));
// We set the referrer policy of the frame to "always". We then create a new
// navigation, set as initiator the frame itself, start the navigation, and
// change the referrer policy of the frame to "never". After we commit the
// navigation:
// - If navigating to a local scheme, the target frame should have inherited
// the referrer policy of the initiator ("always").
// - If navigating to a non-local scheme, the target frame should have a new
// policy container (hence referrer policy set to "default").
const GURL kUrl = GURL(test.url);
auto navigation =
NavigationSimulatorImpl::CreateRendererInitiated(kUrl, child_frame);
static_cast<blink::mojom::PolicyContainerHost*>(
child_frame->policy_container_host())
->SetReferrerPolicy(network::mojom::ReferrerPolicy::kAlways);
navigation->SetInitiatorFrame(child_frame);
navigation->Start();
static_cast<blink::mojom::PolicyContainerHost*>(
child_frame->policy_container_host())
->SetReferrerPolicy(network::mojom::ReferrerPolicy::kNever);
navigation->Commit();
EXPECT_EQ(
test.expect_inherit ? network::mojom::ReferrerPolicy::kAlways
: network::mojom::ReferrerPolicy::kDefault,
static_cast<RenderFrameHostImpl*>(navigation->GetFinalRenderFrameHost())
->policy_container_host()
->referrer_policy());
}
}
} // namespace content