blob: b9e5fd78cb019edd79660ea14203f49fd8381085 [file] [log] [blame]
// Copyright 2020 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 "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/renderer/subresource_redirect/login_robots_decider_agent.h"
#include "chrome/renderer/subresource_redirect/login_robots_decider_test_util.h"
#include "chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.h"
#include "chrome/renderer/subresource_redirect/subresource_redirect_util.h"
#include "chrome/test/base/chrome_render_view_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_view.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/fetch_api.mojom-shared.h"
#include "services/network/test/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/loader/previews_state.h"
#include "third_party/blink/public/platform/web_network_state_notifier.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/platform/web_url_request.h"
namespace subresource_redirect {
// Possible deferral states for a subresource after sending the
// WillStartRequest.
enum WillStartRequestDeferralState {
kRedirected,
kNotRedirected,
kDeferred,
};
// Holds the url loader throttle and the delegate together.
class LoginRobotsDeciderInfo : public blink::URLLoaderThrottle::Delegate {
public:
LoginRobotsDeciderInfo(
const std::string url,
std::unique_ptr<SubresourceRedirectURLLoaderThrottle> throttle)
: url_(url), throttle_(std::move(throttle)) {
throttle_->set_delegate(this);
}
// Sends WillStartRequest and verifies the deferral state.
void SendStartRequestAndVerifyDeferral(
WillStartRequestDeferralState expected_deferral) {
network::ResourceRequest request;
request.url = url_;
request.destination = network::mojom::RequestDestination::kImage;
request.previews_state = blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON;
bool defer = true;
throttle_->WillStartRequest(&request, &defer);
switch (expected_deferral) {
case WillStartRequestDeferralState::kDeferred:
EXPECT_TRUE(defer);
EXPECT_EQ(GetSubresourceURLForURL(url_), request.url);
break;
case WillStartRequestDeferralState::kRedirected:
EXPECT_FALSE(defer);
EXPECT_EQ(GetSubresourceURLForURL(url_), request.url);
break;
case WillStartRequestDeferralState::kNotRedirected:
EXPECT_FALSE(defer);
EXPECT_EQ(url_, request.url);
break;
default:
NOTREACHED();
}
}
// blink::URLLoaderThrottle::Delegate
void Resume() override {
EXPECT_FALSE(did_resume_);
did_resume_ = true;
}
void RestartWithURLResetAndFlags(int load_flags) override {
EXPECT_FALSE(did_restart_with_url_reset_and_flags_);
did_restart_with_url_reset_and_flags_ = true;
}
void CancelWithError(int error_code,
base::StringPiece custom_reason = "") override {
NOTIMPLEMENTED();
}
void VerifyWillProcessResponse() {
network::mojom::URLResponseHeadPtr head =
network::CreateURLResponseHead(net::HTTP_OK);
head->headers->SetHeader("Content-Length", "1024");
bool defer = false;
throttle_->WillProcessResponse(GURL("https://foo.com/img.jpg"), head.get(),
&defer);
EXPECT_FALSE(defer);
}
bool did_resume() const { return did_resume_; }
bool did_restart_with_url_reset_and_flags() const {
return did_restart_with_url_reset_and_flags_;
}
private:
GURL url_;
std::unique_ptr<SubresourceRedirectURLLoaderThrottle> throttle_;
// The state of delegate callbacks
bool did_resume_ = false;
bool did_restart_with_url_reset_and_flags_ = false;
};
class SubresourceRedirectLoginRobotsURLLoaderThrottleTest
: public ChromeRenderViewTest {
public:
void DisableSubresourceRedirectFeature() {
scoped_feature_list_.Reset();
scoped_feature_list_.InitAndDisableFeature(
blink::features::kSubresourceRedirect);
}
void SetUpRobotsRules(const std::string& origin,
const std::vector<Rule>& patterns) {
login_robots_decider_agent_->UpdateRobotsRulesForTesting(
url::Origin::Create(GURL(origin)), GetRobotsRulesProtoString(patterns));
}
std::unique_ptr<SubresourceRedirectURLLoaderThrottle>
CreateLoginRobotsDecider(
const std::string& url,
network::mojom::RequestDestination request_destination,
int previews_state) {
blink::WebURLRequest request;
request.SetUrl(GURL(url));
request.SetPreviewsState(previews_state);
request.SetRequestDestination(request_destination);
auto throttle = SubresourceRedirectURLLoaderThrottle::MaybeCreateThrottle(
request, view_->GetMainRenderFrame()->GetRoutingID());
EXPECT_TRUE(throttle.get());
return throttle;
}
std::unique_ptr<LoginRobotsDeciderInfo> CreateURLLoaderThrottleInfo(
const std::string& url) {
return std::make_unique<LoginRobotsDeciderInfo>(
url, CreateLoginRobotsDecider(
url, network::mojom::RequestDestination::kImage,
blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON));
}
void SetLoggedInState(bool is_logged_in) {
login_robots_decider_agent_->SetLoggedInState(is_logged_in);
}
protected:
void SetUp() override {
ChromeRenderViewTest::SetUp();
scoped_feature_list_.InitWithFeaturesAndParameters(
{{blink::features::kSubresourceRedirect,
{{"enable_subresource_server_redirect", "true"},
{"enable_login_robots_based_compression", "true"},
{"enable_public_image_hints_based_compression", "false"}}}},
{});
login_robots_decider_agent_ = new LoginRobotsDeciderAgent(
&associated_interfaces_, view_->GetMainRenderFrame());
}
protected:
LoginRobotsDeciderAgent* login_robots_decider_agent_;
base::test::ScopedFeatureList scoped_feature_list_;
base::HistogramTester histogram_tester_;
};
TEST_F(SubresourceRedirectLoginRobotsURLLoaderThrottleTest,
TestMaybeCreateThrottle) {
struct TestCase {
bool data_saver_enabled;
bool is_subresource_redirect_feature_enabled;
network::mojom::RequestDestination destination;
int previews_state;
const std::string url;
bool expected_is_throttle_created;
};
const TestCase kTestCases[]{
{true, true, network::mojom::RequestDestination::kImage,
blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON,
"https://www.test.com/test.jpg", true},
// Failure cases
{false, true, network::mojom::RequestDestination::kImage,
blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON,
"https://www.test.com/test.jpg", false},
{true, false, network::mojom::RequestDestination::kImage,
blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON,
"https://www.test.com/test.jpg", false},
{true, true, network::mojom::RequestDestination::kScript,
blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON,
"https://www.test.com/test.jpg", false},
{true, true, network::mojom::RequestDestination::kImage,
blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON,
"http://www.test.com/test.jpg", false},
};
for (const TestCase& test_case : kTestCases) {
blink::WebNetworkStateNotifier::SetSaveDataEnabled(
test_case.data_saver_enabled);
if (!test_case.is_subresource_redirect_feature_enabled) {
DisableSubresourceRedirectFeature();
}
blink::WebURLRequest request;
request.SetPreviewsState(test_case.previews_state);
request.SetUrl(GURL(test_case.url));
request.SetRequestDestination(test_case.destination);
EXPECT_EQ(
test_case.expected_is_throttle_created,
SubresourceRedirectURLLoaderThrottle::MaybeCreateThrottle(
request, view_->GetMainRenderFrame()->GetRoutingID()) != nullptr);
}
}
TEST_F(SubresourceRedirectLoginRobotsURLLoaderThrottleTest,
TestGetSubresourceURL) {
struct TestCase {
int previews_state;
bool is_logged_in;
std::string original_url;
GURL redirected_subresource_url; // Empty URL means there will be no
// redirect.
};
const TestCase kTestCases[]{
{
blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON,
false,
"https://www.test.com/public_img.jpg",
GetSubresourceURLForURL(GURL("https://www.test.com/public_img.jpg")),
},
{
blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON,
false,
"https://www.test.com/public_img.jpg#anchor",
GetSubresourceURLForURL(
GURL("https://www.test.com/public_img.jpg#anchor")),
},
{
blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON,
false,
"https://www.test.com/public_img.jpg?public_arg1=bar&public_arg2",
GetSubresourceURLForURL(
GURL("https://www.test.com/"
"public_img.jpg?public_arg1=bar&public_arg2")),
},
// Private images will not be redirected.
{
blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON,
false,
"https://www.test.com/private_img.jpg",
GURL(),
},
{
blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON,
false,
"https://www.test.com/public_img.jpg&private_arg1=foo",
GURL(),
},
// No redirection when logged-in
{
blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON,
true,
"https://www.test.com/public_img.jpg",
GURL(),
},
};
blink::WebNetworkStateNotifier::SetSaveDataEnabled(true);
SetUpRobotsRules("https://www.test.com", {{kRuleTypeDisallow, "*private"},
{kRuleTypeAllow, "/public"},
{kRuleTypeDisallow, ""}});
for (const TestCase& test_case : kTestCases) {
SetLoggedInState(test_case.is_logged_in);
auto throttle = CreateLoginRobotsDecider(
test_case.original_url, network::mojom::RequestDestination::kImage,
test_case.previews_state);
network::ResourceRequest request;
request.url = GURL(test_case.original_url);
request.destination = network::mojom::RequestDestination::kImage;
request.previews_state = test_case.previews_state;
bool defer = true;
throttle->WillStartRequest(&request, &defer);
EXPECT_FALSE(defer);
if (!test_case.redirected_subresource_url.is_empty()) {
EXPECT_EQ(request.url, test_case.redirected_subresource_url);
} else {
EXPECT_EQ(request.url, test_case.original_url);
}
}
}
// Tests the cases when robots rules are already sent, before throttles are
// created.
TEST_F(SubresourceRedirectLoginRobotsURLLoaderThrottleTest,
TestRobotsRulesSentBeforeThrottle) {
blink::WebNetworkStateNotifier::SetSaveDataEnabled(true);
SetLoggedInState(false);
SetUpRobotsRules("https://www.test.com",
{{kRuleTypeAllow, "/public"}, {kRuleTypeDisallow, ""}});
auto throttle_info1 =
CreateURLLoaderThrottleInfo("https://www.test.com/public.jpg");
auto throttle_info2 =
CreateURLLoaderThrottleInfo("https://www.test.com/private.jpg");
throttle_info1->SendStartRequestAndVerifyDeferral(
WillStartRequestDeferralState::kRedirected);
throttle_info2->SendStartRequestAndVerifyDeferral(
WillStartRequestDeferralState::kNotRedirected);
EXPECT_FALSE(throttle_info1->did_resume());
EXPECT_FALSE(throttle_info2->did_resume());
EXPECT_FALSE(throttle_info1->did_restart_with_url_reset_and_flags());
EXPECT_FALSE(throttle_info2->did_restart_with_url_reset_and_flags());
histogram_tester_.ExpectTotalCount(
"SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult", 2);
histogram_tester_.ExpectBucketCount(
"SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult",
RedirectResult::kRedirectable, 1);
histogram_tester_.ExpectBucketCount(
"SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult",
RedirectResult::kIneligibleRobotsDisallowed, 1);
}
// Tests the cases when robots rules are sent, after throttles are
// created.
TEST_F(SubresourceRedirectLoginRobotsURLLoaderThrottleTest,
TestRobotsRulesSentAfterThrottle) {
blink::WebNetworkStateNotifier::SetSaveDataEnabled(true);
SetLoggedInState(false);
auto throttle_info1 =
CreateURLLoaderThrottleInfo("https://www.test.com/public.jpg");
auto throttle_info2 =
CreateURLLoaderThrottleInfo("https://www.test.com/private.jpg");
// Both requests will be deferred until rules are received.
throttle_info1->SendStartRequestAndVerifyDeferral(
WillStartRequestDeferralState::kDeferred);
throttle_info2->SendStartRequestAndVerifyDeferral(
WillStartRequestDeferralState::kDeferred);
EXPECT_FALSE(throttle_info1->did_resume());
EXPECT_FALSE(throttle_info2->did_resume());
EXPECT_FALSE(throttle_info1->did_restart_with_url_reset_and_flags());
EXPECT_FALSE(throttle_info2->did_restart_with_url_reset_and_flags());
SetUpRobotsRules("https://www.test.com",
{{kRuleTypeAllow, "/public"}, {kRuleTypeDisallow, ""}});
// The public resource should resume loading, with the already modified URL.
EXPECT_TRUE(throttle_info1->did_resume());
EXPECT_FALSE(throttle_info1->did_restart_with_url_reset_and_flags());
// The private resource should restart and resume loading with the original
// URL.
EXPECT_TRUE(throttle_info2->did_restart_with_url_reset_and_flags());
EXPECT_TRUE(throttle_info2->did_resume());
throttle_info1->VerifyWillProcessResponse();
throttle_info2->VerifyWillProcessResponse();
histogram_tester_.ExpectTotalCount(
"SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult", 2);
histogram_tester_.ExpectBucketCount(
"SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult",
RedirectResult::kRedirectable, 1);
histogram_tester_.ExpectBucketCount(
"SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult",
RedirectResult::kIneligibleRobotsDisallowed, 1);
}
// Tests the cases when robots rules retrieval timesout.
TEST_F(SubresourceRedirectLoginRobotsURLLoaderThrottleTest,
TestRobotsRulesTimeout) {
blink::WebNetworkStateNotifier::SetSaveDataEnabled(true);
SetLoggedInState(false);
auto throttle_info1 =
CreateURLLoaderThrottleInfo("https://www.test.com/public.jpg");
auto throttle_info2 =
CreateURLLoaderThrottleInfo("https://www.test.com/private.jpg");
// Both requests will be deferred until rule retrieval times out.
throttle_info1->SendStartRequestAndVerifyDeferral(
WillStartRequestDeferralState::kDeferred);
throttle_info2->SendStartRequestAndVerifyDeferral(
WillStartRequestDeferralState::kDeferred);
EXPECT_FALSE(throttle_info1->did_resume());
EXPECT_FALSE(throttle_info2->did_resume());
EXPECT_FALSE(throttle_info1->did_restart_with_url_reset_and_flags());
EXPECT_FALSE(throttle_info2->did_restart_with_url_reset_and_flags());
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(10));
// Both resources should restart and resume loading with the original URL.
EXPECT_TRUE(throttle_info1->did_restart_with_url_reset_and_flags());
EXPECT_TRUE(throttle_info1->did_resume());
EXPECT_TRUE(throttle_info2->did_restart_with_url_reset_and_flags());
EXPECT_TRUE(throttle_info2->did_resume());
throttle_info1->VerifyWillProcessResponse();
throttle_info2->VerifyWillProcessResponse();
histogram_tester_.ExpectTotalCount(
"SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult", 2);
histogram_tester_.ExpectBucketCount(
"SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult",
RedirectResult::kIneligibleRobotsTimeout, 2);
}
} // namespace subresource_redirect