blob: 43df28fa4c611d3deee86dfbe2eabe1cc27284fe [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/loader/navigation_url_loader_impl.h"
#include "base/memory/raw_ptr.h"
#include "build/build_config.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/unguessable_token.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/browser/loader/navigation_loader_interceptor.h"
#include "content/browser/loader/navigation_url_loader.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_request_info.h"
#include "content/browser/web_package/prefetched_signed_exchange_cache.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_ui_data.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_renderer_host.h"
#include "content/test/test_navigation_url_loader_delegate.h"
#include "content/test/test_web_contents.h"
#include "ipc/ipc_message.h"
#include "net/base/load_flags.h"
#include "net/base/mock_network_change_notifier.h"
#include "net/proxy_resolution/configured_proxy_resolution_service.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_builder.h"
#include "ppapi/buildflags/buildflags.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/network/public/cpp/cors/origin_access_list.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/single_request_url_loader_factory.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "services/network/resource_scheduler/resource_scheduler_client.h"
#include "services/network/test/url_loader_context_for_tests.h"
#include "services/network/url_loader.h"
#include "services/network/url_request_context_owner.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/navigation/navigation_params.h"
#include "third_party/blink/public/mojom/loader/mixed_content.mojom.h"
#include "third_party/blink/public/mojom/navigation/navigation_params.mojom.h"
#if BUILDFLAG(ENABLE_PLUGINS)
#include "content/public/browser/plugin_service.h"
#endif
namespace content {
namespace {
class TestNavigationLoaderInterceptor : public NavigationLoaderInterceptor {
public:
explicit TestNavigationLoaderInterceptor(
absl::optional<network::ResourceRequest>* most_recent_resource_request)
: most_recent_resource_request_(most_recent_resource_request) {
net::URLRequestContextBuilder url_request_context_builder;
url_request_context_builder.set_proxy_resolution_service(
net::ConfiguredProxyResolutionService::CreateDirect());
url_request_context_ = url_request_context_builder.Build();
url_loader_context_.set_url_request_context(url_request_context_.get());
constexpr network::ResourceScheduler::ClientId kClientId(3);
resource_scheduler_client_ =
base::MakeRefCounted<network::ResourceSchedulerClient>(
kClientId, network::IsBrowserInitiated(false), &resource_scheduler_,
url_request_context_->network_quality_estimator());
url_loader_context_.set_resource_scheduler_client(
resource_scheduler_client_);
url_loader_context_.mutable_factory_params().process_id =
network::mojom::kBrowserProcessId;
url_loader_context_.mutable_factory_params().is_corb_enabled = false;
}
~TestNavigationLoaderInterceptor() override {
url_loader_ = nullptr;
resource_scheduler_client_ = nullptr;
}
void MaybeCreateLoader(const network::ResourceRequest& resource_request,
BrowserContext* browser_context,
LoaderCallback callback,
FallbackCallback fallback_callback) override {
std::move(callback).Run(
base::MakeRefCounted<network::SingleRequestURLLoaderFactory>(
base::BindOnce(&TestNavigationLoaderInterceptor::StartLoader,
base::Unretained(this))));
}
void StartLoader(
const network::ResourceRequest& resource_request,
mojo::PendingReceiver<network::mojom::URLLoader> receiver,
mojo::PendingRemote<network::mojom::URLLoaderClient> client) {
*most_recent_resource_request_ = resource_request;
url_loader_ = std::make_unique<network::URLLoader>(
url_loader_context_,
base::BindOnce(&TestNavigationLoaderInterceptor::DeleteURLLoader,
base::Unretained(this)),
std::move(receiver), 0 /* options */, resource_request,
std::move(client), nullptr /* sync_url_loader_client */,
TRAFFIC_ANNOTATION_FOR_TESTS, 0, /* request_id */
0 /* keepalive_request_size */,
nullptr /* keepalive_statistics_recorder */,
nullptr /* trust_token_helper */,
mojo::NullRemote() /* cookie_observer */,
mojo::NullRemote() /* trust_token_observer */,
mojo::NullRemote() /* url_loader_network_observer */,
/*devtools_observer=*/mojo::NullRemote(),
/*accept_ch_frame_observer=*/mojo::NullRemote(),
/*third_party_cookies_enabled=*/true, net::CookieSettingOverrides(),
/*cache_transparency_settings=*/nullptr,
/*attribution_request_helper=*/nullptr);
}
bool MaybeCreateLoaderForResponse(
const network::URLLoaderCompletionStatus& status,
const network::ResourceRequest& request,
network::mojom::URLResponseHeadPtr* response,
mojo::ScopedDataPipeConsumerHandle* response_body,
mojo::PendingRemote<network::mojom::URLLoader>* loader,
mojo::PendingReceiver<network::mojom::URLLoaderClient>* client_receiver,
blink::ThrottlingURLLoader* url_loader,
bool* skip_other_interceptors,
bool* will_return_unsafe_redirect) override {
return false;
}
private:
void DeleteURLLoader(network::URLLoader* url_loader) {
DCHECK_EQ(url_loader_.get(), url_loader);
url_loader_.reset();
}
raw_ptr<absl::optional<network::ResourceRequest>>
most_recent_resource_request_; // NOT OWNED.
network::ResourceScheduler resource_scheduler_;
network::URLLoaderContextForTests url_loader_context_;
std::unique_ptr<net::URLRequestContext> url_request_context_;
scoped_refptr<network::ResourceSchedulerClient> resource_scheduler_client_;
std::unique_ptr<network::URLLoader> url_loader_;
const network::cors::OriginAccessList kEmptyOriginAccessList;
};
} // namespace
class NavigationURLLoaderImplTest : public testing::Test {
public:
NavigationURLLoaderImplTest()
: task_environment_(std::make_unique<BrowserTaskEnvironment>(
base::test::TaskEnvironment::MainThreadType::IO,
content::BrowserTaskEnvironment::TimeSource::MOCK_TIME)),
network_change_notifier_(
net::test::MockNetworkChangeNotifier::Create()) {
browser_context_ = std::make_unique<TestBrowserContext>();
http_test_server_.AddDefaultHandlers(
base::FilePath(FILE_PATH_LITERAL("content/test/data")));
#if BUILDFLAG(ENABLE_PLUGINS)
PluginService::GetInstance()->Init();
#endif
}
~NavigationURLLoaderImplTest() override {
browser_context_.reset();
// Reset the BrowserTaskEnvironment to force destruction of the local
// NetworkService, which is held in SequenceLocalStorage. This must happen
// before destruction of |network_change_notifier_|, to allow observers to
// be unregistered.
task_environment_.reset();
}
void SetUp() override {
// Do not create TestNavigationURLLoaderFactory as this tests creates
// NavigationURLLoaders explicitly and TestNavigationURLLoaderFactory
// interferes with that.
rvh_test_enabler_ = std::make_unique<RenderViewHostTestEnabler>(
RenderViewHostTestEnabler::NavigationURLLoaderFactoryType::kNone);
web_contents_ = TestWebContents::Create(
browser_context_.get(),
SiteInstanceImpl::Create(browser_context_.get()));
}
void TearDown() override {
pending_navigation_.reset();
web_contents_.reset();
rvh_test_enabler_.reset();
}
std::unique_ptr<NavigationURLLoader> CreateTestLoader(
const GURL& url,
const std::string& headers,
const std::string& method,
NavigationURLLoaderDelegate* delegate,
blink::NavigationDownloadPolicy download_policy =
blink::NavigationDownloadPolicy(),
bool is_main_frame = true,
bool upgrade_if_insecure = false) {
// NavigationURLLoader assumes that the corresponding FrameTreeNode has an
// associated NavigationRequest.
pending_navigation_ = NavigationSimulator::CreateBrowserInitiated(
GURL("https://example.com"), web_contents_.get());
pending_navigation_->Start();
blink::mojom::BeginNavigationParamsPtr begin_params =
blink::mojom::BeginNavigationParams::New(
absl::nullopt /* initiator_frame_token */, headers,
net::LOAD_NORMAL, false /* skip_service_worker */,
blink::mojom::RequestContextType::LOCATION,
blink::mojom::MixedContentContextType::kBlockable,
false /* is_form_submission */,
false /* was_initiated_by_link_click */,
blink::mojom::ForceHistoryPush::kNo,
GURL() /* searchable_form_url */,
std::string() /* searchable_form_encoding */,
GURL() /* client_side_redirect_url */,
absl::nullopt /* devtools_initiator_info */,
nullptr /* trust_token_params */, absl::nullopt /* impression */,
base::TimeTicks() /* renderer_before_unload_start */,
base::TimeTicks() /* renderer_before_unload_end */,
absl::nullopt /* web_bundle_token */,
blink::mojom::NavigationInitiatorActivationAndAdStatus::
kDidNotStartWithTransientActivation);
auto common_params = blink::CreateCommonNavigationParams();
common_params->url = url;
common_params->initiator_origin = url::Origin::Create(url);
common_params->method = method;
common_params->download_policy = download_policy;
common_params->request_destination =
network::mojom::RequestDestination::kDocument;
url::Origin origin = url::Origin::Create(url);
uint32_t frame_tree_node_id =
web_contents_->GetPrimaryMainFrame()->GetFrameTreeNodeId();
bool is_primary_main_frame = is_main_frame;
bool is_outermost_main_frame = is_main_frame;
std::unique_ptr<NavigationRequestInfo> request_info(
std::make_unique<NavigationRequestInfo>(
std::move(common_params), std::move(begin_params),
network::mojom::WebSandboxFlags::kNone,
net::IsolationInfo::Create(
net::IsolationInfo::RequestType::kMainFrame, origin, origin,
net::SiteForCookies::FromUrl(url)),
is_primary_main_frame, is_outermost_main_frame, is_main_frame,
false /* are_ancestors_secure */, frame_tree_node_id,
false /* report_raw_headers */,
upgrade_if_insecure /* upgrade_if_insecure */,
nullptr /* blob_url_loader_factory */,
base::UnguessableToken::Create() /* devtools_navigation_token */,
base::UnguessableToken::Create() /* devtools_frame_token */,
net::HttpRequestHeaders() /* cors_exempt_headers */,
nullptr /* client_security_state */,
absl::nullopt /* devtools_accepted_stream_types */,
false /* is_pdf */,
content::WeakDocumentPtr() /* initiator_document */,
false /* allow_cookies_from_browser */));
std::vector<std::unique_ptr<NavigationLoaderInterceptor>> interceptors;
most_recent_resource_request_ = absl::nullopt;
interceptors.push_back(std::make_unique<TestNavigationLoaderInterceptor>(
&most_recent_resource_request_));
return std::make_unique<NavigationURLLoaderImpl>(
browser_context_.get(), browser_context_->GetDefaultStoragePartition(),
std::move(request_info), nullptr /* navigation_ui_data */,
nullptr /* service_worker_handle */,
nullptr /* prefetched_signed_exchange_cache */, delegate,
mojo::NullRemote() /* cookie_access_obsever */,
mojo::NullRemote() /* url_loader_network_observer */,
/*devtools_observer=*/mojo::NullRemote(), std::move(interceptors));
}
// Requests |redirect_url|, which must return a HTTP 3xx redirect. It's also
// used as the initial origin.
// |request_method| is the method to use for the initial request.
// |expected_redirect_method| is the method that is expected to be used for
// the second request, after redirection.
// |expected_origin_value| is the expected value for the Origin header after
// redirection. If empty, expects that there will be no Origin header.
void HTTPRedirectOriginHeaderTest(const GURL& redirect_url,
const std::string& request_method,
const std::string& expected_redirect_method,
const std::string& expected_origin_value,
bool expect_request_fail = false) {
TestNavigationURLLoaderDelegate delegate;
std::unique_ptr<NavigationURLLoader> loader = CreateTestLoader(
redirect_url,
base::StringPrintf(
"%s: %s", net::HttpRequestHeaders::kOrigin,
redirect_url.DeprecatedGetOriginAsURL().spec().c_str()),
request_method, &delegate);
loader->Start();
delegate.WaitForRequestRedirected();
loader->FollowRedirect({}, {}, {});
EXPECT_EQ(expected_redirect_method, delegate.redirect_info().new_method);
if (expect_request_fail) {
delegate.WaitForRequestFailed();
} else {
delegate.WaitForResponseStarted();
}
ASSERT_TRUE(most_recent_resource_request_);
// Note that there is no check for request success here because, for
// purposes of testing, the request very well may fail. For example, if the
// test redirects to an HTTPS server from an HTTP origin, thus it is cross
// origin, there is not an HTTPS server in this unit test framework, so the
// request would fail. However, that's fine, as long as the request headers
// are in order and pass the checks below.
if (expected_origin_value.empty()) {
EXPECT_FALSE(most_recent_resource_request_->headers.HasHeader(
net::HttpRequestHeaders::kOrigin));
} else {
std::string origin_header;
EXPECT_TRUE(most_recent_resource_request_->headers.GetHeader(
net::HttpRequestHeaders::kOrigin, &origin_header));
EXPECT_EQ(expected_origin_value, origin_header);
}
}
net::RedirectInfo NavigateAndReturnRedirectInfo(const GURL& url,
bool upgrade_if_insecure,
bool expect_request_fail) {
TestNavigationURLLoaderDelegate delegate;
std::unique_ptr<NavigationURLLoader> loader = CreateTestLoader(
url,
base::StringPrintf("%s: %s", net::HttpRequestHeaders::kOrigin,
url.DeprecatedGetOriginAsURL().spec().c_str()),
"GET", &delegate, blink::NavigationDownloadPolicy(),
true /*is_main_frame*/, upgrade_if_insecure);
loader->Start();
delegate.WaitForRequestRedirected();
loader->FollowRedirect({}, {}, {});
if (expect_request_fail) {
delegate.WaitForRequestFailed();
} else {
delegate.WaitForResponseStarted();
}
return delegate.redirect_info();
}
protected:
std::unique_ptr<BrowserTaskEnvironment> task_environment_;
std::unique_ptr<net::test::MockNetworkChangeNotifier>
network_change_notifier_;
std::unique_ptr<TestBrowserContext> browser_context_;
net::EmbeddedTestServer http_test_server_;
absl::optional<network::ResourceRequest> most_recent_resource_request_;
std::unique_ptr<RenderViewHostTestEnabler> rvh_test_enabler_;
std::unique_ptr<TestWebContents> web_contents_;
// NavigationURLLoaderImpl relies on the existence of the
// |frame_tree_node->navigation_request()|.
std::unique_ptr<NavigationSimulator> pending_navigation_;
};
TEST_F(NavigationURLLoaderImplTest, IsolationInfoOfMainFrameNavigation) {
ASSERT_TRUE(http_test_server_.Start());
const GURL url = http_test_server_.GetURL("/foo");
const url::Origin origin = url::Origin::Create(url);
TestNavigationURLLoaderDelegate delegate;
std::unique_ptr<NavigationURLLoader> loader = CreateTestLoader(
url,
base::StringPrintf("%s: %s", net::HttpRequestHeaders::kOrigin,
url.DeprecatedGetOriginAsURL().spec().c_str()),
"GET", &delegate, blink::NavigationDownloadPolicy(),
true /*is_main_frame*/, false /*upgrade_if_insecure*/);
loader->Start();
delegate.WaitForResponseStarted();
ASSERT_TRUE(most_recent_resource_request_);
ASSERT_TRUE(most_recent_resource_request_->trusted_params);
EXPECT_TRUE(
net::IsolationInfo::Create(net::IsolationInfo::RequestType::kMainFrame,
origin, origin,
net::SiteForCookies::FromOrigin(origin))
.IsEqualForTesting(
most_recent_resource_request_->trusted_params->isolation_info));
}
TEST_F(NavigationURLLoaderImplTest,
IsolationInfoOfRedirectedMainFrameNavigation) {
ASSERT_TRUE(http_test_server_.Start());
const GURL url = http_test_server_.GetURL("/redirect301-to-echo");
const GURL final_url = http_test_server_.GetURL("/echo");
const url::Origin origin = url::Origin::Create(url);
HTTPRedirectOriginHeaderTest(url, "GET", "GET",
url.DeprecatedGetOriginAsURL().spec());
ASSERT_TRUE(most_recent_resource_request_->trusted_params);
EXPECT_TRUE(
net::IsolationInfo::Create(net::IsolationInfo::RequestType::kMainFrame,
origin, origin,
net::SiteForCookies::FromOrigin(origin))
.IsEqualForTesting(
most_recent_resource_request_->trusted_params->isolation_info));
}
TEST_F(NavigationURLLoaderImplTest, Redirect301Tests) {
ASSERT_TRUE(http_test_server_.Start());
const GURL url = http_test_server_.GetURL("/redirect301-to-echo");
const GURL https_redirect_url =
http_test_server_.GetURL("/redirect301-to-https");
HTTPRedirectOriginHeaderTest(url, "GET", "GET",
url.DeprecatedGetOriginAsURL().spec());
HTTPRedirectOriginHeaderTest(https_redirect_url, "GET", "GET", "null", true);
HTTPRedirectOriginHeaderTest(url, "POST", "GET", std::string());
HTTPRedirectOriginHeaderTest(https_redirect_url, "POST", "GET", std::string(),
true);
}
TEST_F(NavigationURLLoaderImplTest, Redirect302Tests) {
ASSERT_TRUE(http_test_server_.Start());
const GURL url = http_test_server_.GetURL("/redirect302-to-echo");
const GURL https_redirect_url =
http_test_server_.GetURL("/redirect302-to-https");
HTTPRedirectOriginHeaderTest(url, "GET", "GET",
url.DeprecatedGetOriginAsURL().spec());
HTTPRedirectOriginHeaderTest(https_redirect_url, "GET", "GET", "null", true);
HTTPRedirectOriginHeaderTest(url, "POST", "GET", std::string());
HTTPRedirectOriginHeaderTest(https_redirect_url, "POST", "GET", std::string(),
true);
}
TEST_F(NavigationURLLoaderImplTest, Redirect303Tests) {
ASSERT_TRUE(http_test_server_.Start());
const GURL url = http_test_server_.GetURL("/redirect303-to-echo");
const GURL https_redirect_url =
http_test_server_.GetURL("/redirect303-to-https");
HTTPRedirectOriginHeaderTest(url, "GET", "GET",
url.DeprecatedGetOriginAsURL().spec());
HTTPRedirectOriginHeaderTest(https_redirect_url, "GET", "GET", "null", true);
HTTPRedirectOriginHeaderTest(url, "POST", "GET", std::string());
HTTPRedirectOriginHeaderTest(https_redirect_url, "POST", "GET", std::string(),
true);
}
TEST_F(NavigationURLLoaderImplTest, Redirect307Tests) {
ASSERT_TRUE(http_test_server_.Start());
const GURL url = http_test_server_.GetURL("/redirect307-to-echo");
const GURL https_redirect_url =
http_test_server_.GetURL("/redirect307-to-https");
HTTPRedirectOriginHeaderTest(url, "GET", "GET",
url.DeprecatedGetOriginAsURL().spec());
HTTPRedirectOriginHeaderTest(https_redirect_url, "GET", "GET", "null", true);
HTTPRedirectOriginHeaderTest(url, "POST", "POST",
url.DeprecatedGetOriginAsURL().spec());
HTTPRedirectOriginHeaderTest(https_redirect_url, "POST", "POST", "null",
true);
}
TEST_F(NavigationURLLoaderImplTest, Redirect308Tests) {
ASSERT_TRUE(http_test_server_.Start());
const GURL url = http_test_server_.GetURL("/redirect308-to-echo");
const GURL https_redirect_url =
http_test_server_.GetURL("/redirect308-to-https");
HTTPRedirectOriginHeaderTest(url, "GET", "GET",
url.DeprecatedGetOriginAsURL().spec());
HTTPRedirectOriginHeaderTest(https_redirect_url, "GET", "GET", "null", true);
HTTPRedirectOriginHeaderTest(url, "POST", "POST",
url.DeprecatedGetOriginAsURL().spec());
HTTPRedirectOriginHeaderTest(https_redirect_url, "POST", "POST", "null",
true);
}
TEST_F(NavigationURLLoaderImplTest, RedirectModifiedHeaders) {
ASSERT_TRUE(http_test_server_.Start());
const GURL redirect_url = http_test_server_.GetURL("/redirect301-to-echo");
TestNavigationURLLoaderDelegate delegate;
std::unique_ptr<NavigationURLLoader> loader = CreateTestLoader(
redirect_url, "Header1: Value1\r\nHeader2: Value2", "GET", &delegate);
loader->Start();
delegate.WaitForRequestRedirected();
ASSERT_TRUE(most_recent_resource_request_);
// Initial request should only have initial headers.
std::string header1, header2;
EXPECT_TRUE(
most_recent_resource_request_->headers.GetHeader("Header1", &header1));
EXPECT_EQ("Value1", header1);
EXPECT_TRUE(
most_recent_resource_request_->headers.GetHeader("Header2", &header2));
EXPECT_EQ("Value2", header2);
EXPECT_FALSE(most_recent_resource_request_->headers.HasHeader("Header3"));
// Overwrite Header2 and add Header3.
net::HttpRequestHeaders redirect_headers;
redirect_headers.SetHeader("Header2", "");
redirect_headers.SetHeader("Header3", "Value3");
loader->FollowRedirect({}, redirect_headers, {});
delegate.WaitForResponseStarted();
// Redirected request should also have modified headers.
EXPECT_TRUE(
most_recent_resource_request_->headers.GetHeader("Header1", &header1));
EXPECT_EQ("Value1", header1);
EXPECT_TRUE(
most_recent_resource_request_->headers.GetHeader("Header2", &header2));
EXPECT_EQ("", header2);
std::string header3;
EXPECT_TRUE(
most_recent_resource_request_->headers.GetHeader("Header3", &header3));
EXPECT_EQ("Value3", header3);
}
// Tests that the Upgrade If Insecure flag is obeyed.
TEST_F(NavigationURLLoaderImplTest, UpgradeIfInsecureTest) {
ASSERT_TRUE(http_test_server_.Start());
const GURL url = http_test_server_.GetURL("/redirect301-to-http");
GURL expected_url = GURL("http://test.test/test");
// We expect the request to fail since there is no server listening at
// test.test, but for the purpose of this test we only need to validate the
// redirect URL was not changed.
net::RedirectInfo redirect_info = NavigateAndReturnRedirectInfo(
url, false /* upgrade_if_insecure */, true /* expect_request_fail */);
EXPECT_FALSE(redirect_info.insecure_scheme_was_upgraded);
EXPECT_EQ(expected_url, redirect_info.new_url);
GURL::Replacements replacements;
replacements.SetSchemeStr("https");
expected_url = expected_url.ReplaceComponents(replacements);
redirect_info = NavigateAndReturnRedirectInfo(
url, true /* upgrade_if_insecure */, true /* expect_request_fail */);
// Same as above, but validating the URL is upgraded to https.
EXPECT_TRUE(redirect_info.insecure_scheme_was_upgraded);
EXPECT_EQ(expected_url, redirect_info.new_url);
}
// Tests that when a navigation timeout is set and the navigation takes longer
// than that timeout, then the navigation load fails with ERR_TIMED_OUT.
TEST_F(NavigationURLLoaderImplTest, NavigationTimeoutTest) {
ASSERT_TRUE(http_test_server_.Start());
const GURL url = http_test_server_.GetURL("/hung");
TestNavigationURLLoaderDelegate delegate;
std::unique_ptr<NavigationURLLoader> loader =
CreateTestLoader(url, std::string(), "GET", &delegate);
loader->Start();
loader->SetNavigationTimeout(base::Seconds(3));
delegate.WaitForRequestFailed();
EXPECT_EQ(net::ERR_TIMED_OUT, delegate.net_error());
}
// Like NavigationTimeoutTest but the navigation initially results in a redirect
// before hanging, to test a slightly more complicated navigation.
// TODO(crbug.com/1271228): Flaky on Linux.
#if BUILDFLAG(IS_LINUX)
#define MAYBE_NavigationTimeoutRedirectTest \
DISABLED_NavigationTimeoutRedirectTest
#else
#define MAYBE_NavigationTimeoutRedirectTest NavigationTimeoutRedirectTest
#endif
TEST_F(NavigationURLLoaderImplTest, MAYBE_NavigationTimeoutRedirectTest) {
ASSERT_TRUE(http_test_server_.Start());
const GURL hang_url = http_test_server_.GetURL("/hung");
const GURL redirect_url =
http_test_server_.GetURL("/server-redirect?" + hang_url.spec());
TestNavigationURLLoaderDelegate delegate;
std::unique_ptr<NavigationURLLoader> loader =
CreateTestLoader(redirect_url, std::string(), "GET", &delegate);
loader->Start();
loader->SetNavigationTimeout(base::Seconds(3));
delegate.WaitForRequestRedirected();
delegate.WaitForRequestFailed();
EXPECT_EQ(net::ERR_TIMED_OUT, delegate.net_error());
}
// Verify that UKMs are recorded when OnAcceptCHFrameReceived is called.
TEST_F(NavigationURLLoaderImplTest, OnAcceptCHFrameReceivedUKM) {
ASSERT_TRUE(http_test_server_.Start());
const GURL url = http_test_server_.GetURL("/foo");
const url::Origin origin = url::Origin::Create(url);
TestNavigationURLLoaderDelegate delegate;
std::unique_ptr<NavigationURLLoader> loader = CreateTestLoader(
url,
base::StringPrintf("%s: %s", net::HttpRequestHeaders::kOrigin,
url.DeprecatedGetOriginAsURL().spec().c_str()),
"GET", &delegate, blink::NavigationDownloadPolicy(),
true /*is_main_frame*/, false /*upgrade_if_insecure*/);
loader->Start();
// Try recording no hints.
{
ukm::TestAutoSetUkmRecorder ukm_recorder;
static_cast<NavigationURLLoaderImpl*>(loader.get())
->OnAcceptCHFrameReceived(origin, {},
base::BindOnce([](int32_t) { return; }));
auto ukm_entries = ukm_recorder.GetEntriesByName(
ukm::builders::ClientHints_AcceptCHFrameUsage::kEntryName);
ASSERT_EQ(ukm_entries.size(), 0u);
}
// Try recording one hint.
{
ukm::TestAutoSetUkmRecorder ukm_recorder;
static_cast<NavigationURLLoaderImpl*>(loader.get())
->OnAcceptCHFrameReceived(origin,
{network::mojom::WebClientHintsType::kDpr},
base::BindOnce([](int32_t) { return; }));
auto ukm_entries = ukm_recorder.GetEntriesByName(
ukm::builders::ClientHints_AcceptCHFrameUsage::kEntryName);
ASSERT_EQ(ukm_entries.size(), 1u);
EXPECT_EQ(*ukm_recorder.GetEntryMetric(
ukm_entries[0],
ukm::builders::ClientHints_AcceptCHFrameUsage::kTypeName),
static_cast<int64_t>(network::mojom::WebClientHintsType::kDpr));
}
// Try recording all hints.
{
ukm::TestAutoSetUkmRecorder ukm_recorder;
std::vector<network::mojom::WebClientHintsType> accept_ch_frame;
for (int64_t i = 0; i <= static_cast<int64_t>(
network::mojom::WebClientHintsType::kMaxValue);
++i) {
accept_ch_frame.push_back(
static_cast<network::mojom::WebClientHintsType>(i));
}
static_cast<NavigationURLLoaderImpl*>(loader.get())
->OnAcceptCHFrameReceived(origin, accept_ch_frame,
base::BindOnce([](int32_t) { return; }));
auto ukm_entries = ukm_recorder.GetEntriesByName(
ukm::builders::ClientHints_AcceptCHFrameUsage::kEntryName);
// If you're here because the test is failing when you added a new client
// hint be sure to increment the number below and add your new hint to the
// enum WebClientHintsType in tools/metrics/histograms/enums.xml.
ASSERT_EQ(ukm_entries.size(), 29u);
for (int64_t i = 0; i <= static_cast<int64_t>(
network::mojom::WebClientHintsType::kMaxValue);
++i) {
EXPECT_EQ(*ukm_recorder.GetEntryMetric(
ukm_entries[i],
ukm::builders::ClientHints_AcceptCHFrameUsage::kTypeName),
i);
}
}
}
} // namespace content