blob: 37980e239ed45271c19758efab9ca438cc60e668 [file] [log] [blame]
// Copyright 2019 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/path_service.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/test/metrics/histogram_tester.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h"
#include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_factory.h"
#include "chrome/browser/metrics/subprocess_metrics_provider.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/optimization_guide/hints_component_info.h"
#include "components/optimization_guide/hints_component_util.h"
#include "components/optimization_guide/optimization_guide_constants.h"
#include "components/optimization_guide/optimization_guide_features.h"
#include "components/optimization_guide/optimization_guide_service.h"
#include "components/optimization_guide/optimization_guide_switches.h"
#include "components/optimization_guide/proto/hints.pb.h"
#include "components/optimization_guide/test_hints_component_creator.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/network_connection_change_simulator.h"
#include "net/base/escape.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/network/public/cpp/network_quality_tracker.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
namespace {
// Retries fetching |histogram_name| until it contains at least |count| samples.
// TODO(rajendrant): Convert the tests to wait for image load to complete or the
// page load complete, instead of waiting on the histograms.
void RetryForHistogramUntilCountReached(base::HistogramTester* histogram_tester,
const std::string& histogram_name,
size_t count) {
while (true) {
base::ThreadPoolInstance::Get()->FlushForTesting();
base::RunLoop().RunUntilIdle();
content::FetchHistogramsFromChildProcesses();
SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
const std::vector<base::Bucket> buckets =
histogram_tester->GetAllSamples(histogram_name);
size_t total_count = 0;
for (const auto& bucket : buckets) {
total_count += bucket.count;
}
if (total_count >= count) {
break;
}
}
}
class SubresourceRedirectBrowserTest : public InProcessBrowserTest {
public:
explicit SubresourceRedirectBrowserTest(bool enable_lite_page_redirect = true)
: enable_lite_page_redirect_(enable_lite_page_redirect),
https_server_(net::EmbeddedTestServer::TYPE_HTTPS),
compression_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
void SetUp() override {
// |http_server| setup.
http_server_.ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(http_server_.Start());
http_url_ = http_server_.GetURL("insecure.com", "/");
ASSERT_TRUE(http_url_.SchemeIs(url::kHttpScheme));
// |https_server| setup.
https_server_.ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(https_server_.Start());
https_url_ = https_server_.GetURL("secure.com", "/");
ASSERT_TRUE(https_url_.SchemeIs(url::kHttpsScheme));
// |compression_server| setup.
compression_server_.RegisterRequestHandler(base::BindRepeating(
&SubresourceRedirectBrowserTest::HandleCompressionServerRequest,
base::Unretained(this)));
ASSERT_TRUE(compression_server_.Start());
compression_url_ = compression_server_.GetURL("compression.com", "/");
ASSERT_TRUE(compression_url_.SchemeIs(url::kHttpsScheme));
scoped_feature_list_.InitWithFeaturesAndParameters(
{{blink::features::kSubresourceRedirect,
{{"enable_lite_page_redirect",
enable_lite_page_redirect_ ? "true" : "false"},
{"lite_page_subresource_origin", compression_url_.spec()}}},
{optimization_guide::features::kOptimizationHints, {}},
{optimization_guide::features::kRemoteOptimizationGuideFetching, {}}},
{});
InProcessBrowserTest::SetUp();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
// Need to resolve all 3 of the above servers to 127.0.0.1:port, and
// the servers themselves can't serve using 127.0.0.1:port as the
// compressed resource URLs rely on subdomains, and subdomains
// do not function properly when using 127.0.0.1:port
command_line->AppendSwitchASCII("host-rules", "MAP * 127.0.0.1");
command_line->AppendSwitch("enable-spdy-proxy-auth");
command_line->AppendSwitch("optimization-guide-disable-installer");
command_line->AppendSwitch("purge_hint_cache_store");
}
void EnableDataSaver(bool enabled) {
data_reduction_proxy::DataReductionProxySettings::
SetDataSaverEnabledForTesting(browser()->profile()->GetPrefs(),
enabled);
base::RunLoop().RunUntilIdle();
}
bool RunScriptExtractBool(const std::string& script,
content::WebContents* web_contents = nullptr) {
if (!web_contents)
web_contents = browser()->tab_strip_model()->GetActiveWebContents();
return EvalJs(web_contents, script).ExtractBool();
}
std::string RunScriptExtractString(
const std::string& script,
content::WebContents* web_contents = nullptr) {
if (!web_contents)
web_contents = browser()->tab_strip_model()->GetActiveWebContents();
std::string result;
EXPECT_TRUE(ExecuteScriptAndExtractString(web_contents, script, &result));
return result;
}
// Sets up public image URL hint data.
void SetUpPublicImageURLPaths(
std::string url,
const std::vector<std::string>& public_image_paths) {
std::vector<std::string> public_image_urls;
for (const auto& image_path : public_image_paths) {
public_image_urls.push_back(
https_server_.GetURL("secure.com", image_path).spec());
}
const auto hint_setup_url = https_server_.GetURL("/hint_setup.html");
const optimization_guide::HintsComponentInfo& component_info =
test_hints_component_creator_
.CreateHintsComponentInfoWithPublicImageHints(
{https_server_.GetURL("secure.com", "/").host()}, url,
public_image_urls);
g_browser_process->optimization_guide_service()->MaybeUpdateHintsComponent(
component_info);
RetryForHistogramUntilCountReached(
&histogram_tester_,
optimization_guide::kComponentHintsUpdatedResultHistogramString, 1);
}
void CreateUkmRecorder() {
ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
}
std::map<uint64_t, size_t> GetImageCompressionUkmMetrics() {
using ImageCompressionUkm = ukm::builders::PublicImageCompressionDataUse;
std::map<uint64_t, size_t> metric_bytes;
// Flatten the metrics from multiple ukm sources.
for (const auto* metrics :
ukm_recorder_->GetEntriesByName(ImageCompressionUkm::kEntryName)) {
for (const auto& metric : metrics->metrics) {
if (metric_bytes.find(metric.first) == metric_bytes.end())
metric_bytes[metric.first] = 0;
metric_bytes[metric.first] += metric.second;
}
}
return metric_bytes;
}
void WaitForImageCompressionUkmMetrics(size_t count) {
while (ukm_recorder_
->GetEntriesByName(
ukm::builders::PublicImageCompressionDataUse::kEntryName)
.size() < count) {
base::RunLoop().RunUntilIdle();
}
}
void VerifyPublicImageCompressionUkm(uint64_t hash, size_t num_images) {
const auto metrics = GetImageCompressionUkmMetrics();
if (num_images) {
EXPECT_THAT(metrics, testing::Contains(
testing::Pair(hash,
testing::Gt(num_images * 500))));
} else {
EXPECT_EQ(metrics.find(hash), metrics.end());
}
}
void VerifyCompressibleImageUkm(size_t num_images) {
VerifyPublicImageCompressionUkm(
ukm::builders::PublicImageCompressionDataUse::
kCompressibleImageBytesNameHash,
num_images);
}
void VerifyIneligibleImageHintsUnavailableUkm(size_t num_images) {
VerifyPublicImageCompressionUkm(
ukm::builders::PublicImageCompressionDataUse::
kIneligibleImageHintsUnavailableBytesNameHash,
num_images);
}
void VerifyIneligibleImageHintsUnavailableUkmButCompressible(
size_t num_images) {
VerifyPublicImageCompressionUkm(
ukm::builders::PublicImageCompressionDataUse::
kIneligibleImageHintsUnavailableButCompressibleBytesNameHash,
num_images);
}
void VerifyIneligibleImageHintsUnavailableAndMissingInHintsUkm(
size_t num_images) {
VerifyPublicImageCompressionUkm(
ukm::builders::PublicImageCompressionDataUse::
kIneligibleImageHintsUnavailableAndMissingInHintsBytesNameHash,
num_images);
}
void VerifyIneligibleMissingInImageHintsUkm(size_t num_images) {
VerifyPublicImageCompressionUkm(
ukm::builders::PublicImageCompressionDataUse::
kIneligibleMissingInImageHintsBytesNameHash,
num_images);
}
void VerifyIneligibleOtherImageUkm(size_t num_images) {
VerifyPublicImageCompressionUkm(
ukm::builders::PublicImageCompressionDataUse::
kIneligibleOtherImageBytesNameHash,
num_images);
}
GURL http_url() const { return http_url_; }
GURL https_url() const { return https_url_; }
GURL compression_url() const { return compression_url_; }
GURL request_url() const { return request_url_; }
GURL HttpURLWithPath(const std::string& path) {
return http_server_.GetURL("insecure.com", path);
}
GURL HttpsURLWithPath(const std::string& path) {
return https_server_.GetURL("secure.com", path);
}
void SetCompressionServerToFail() { compression_server_fail_ = true; }
base::HistogramTester* histogram_tester() { return &histogram_tester_; }
private:
void TearDownOnMainThread() override {
EXPECT_TRUE(https_server_.ShutdownAndWaitUntilComplete());
InProcessBrowserTest::TearDownOnMainThread();
}
// Called by |compression_server_|.
std::unique_ptr<net::test_server::HttpResponse>
HandleCompressionServerRequest(const net::test_server::HttpRequest& request) {
std::unique_ptr<net::test_server::BasicHttpResponse> response =
std::make_unique<net::test_server::BasicHttpResponse>();
request_url_ = request.GetURL();
// If |compression_server_fail_| is set to true, return a hung response.
if (compression_server_fail_ == true) {
return std::make_unique<net::test_server::RawHttpResponse>("", "");
}
// For the purpose of this browsertest, a redirect to the compression server
// that is looking to access image.png will be treated as though it is
// compressed. All other redirects will be assumed failures to retrieve the
// requested resource and return a redirect to private_url_image.png.
if (request.GetURL().query().find(
net::EscapeQueryParamValue("/image.png", true /* use_plus */), 0) !=
std::string::npos) {
// Serve the correct image file.
std::string file_contents;
base::FilePath test_data_directory;
base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory);
if (base::ReadFileToString(
test_data_directory.AppendASCII("load_image/image.png"),
&file_contents)) {
response->set_content(file_contents);
response->set_code(net::HTTP_OK);
}
} else if (request.GetURL().query().find(
net::EscapeQueryParamValue("/fail_image.png",
true /* use_plus */),
0) != std::string::npos) {
response->set_code(net::HTTP_NOT_FOUND);
} else {
response->set_code(net::HTTP_TEMPORARY_REDIRECT);
response->AddCustomHeader(
"Location",
HttpsURLWithPath("/load_image/private_url_image.png").spec());
}
return std::move(response);
}
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
bool enable_lite_page_redirect_ = false;
GURL compression_url_;
GURL http_url_;
GURL https_url_;
GURL request_url_;
net::EmbeddedTestServer http_server_;
net::EmbeddedTestServer https_server_;
net::EmbeddedTestServer compression_server_;
base::HistogramTester histogram_tester_;
bool compression_server_fail_ = false;
optimization_guide::testing::TestHintsComponentCreator
test_hints_component_creator_;
DISALLOW_COPY_AND_ASSIGN(SubresourceRedirectBrowserTest);
};
class RedirectDisabledSubresourceRedirectBrowserTest
: public SubresourceRedirectBrowserTest {
public:
RedirectDisabledSubresourceRedirectBrowserTest()
: SubresourceRedirectBrowserTest(false) {}
};
#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
#define DISABLE_ON_WIN_MAC_CHROMEOS(x) DISABLED_##x
#else
#define DISABLE_ON_WIN_MAC_CHROMEOS(x) x
#endif
// NOTE: It is indirectly verified that correct requests are being sent to
// the mock compression server by the counts in the histogram bucket for
// HTTP_TEMPORARY_REDIRECTs.
// This test loads image.html, which triggers a subresource request
// for image.png. This triggers an internal redirect to the mocked
// compression server, which responds with HTTP_OK.
IN_PROC_BROWSER_TEST_F(
SubresourceRedirectBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(TestHTMLLoadRedirectSuccess)) {
EnableDataSaver(true);
CreateUkmRecorder();
SetUpPublicImageURLPaths("/load_image/image_delayed_load.html",
{"/load_image/image.png"});
ui_test_utils::NavigateToURL(
browser(), HttpsURLWithPath("/load_image/image_delayed_load.html"));
RetryForHistogramUntilCountReached(
histogram_tester(), "SubresourceRedirect.CompressionAttempt.ResponseCode",
2);
histogram_tester()->ExpectBucketCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", net::HTTP_OK, 1);
histogram_tester()->ExpectBucketCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode",
net::HTTP_TEMPORARY_REDIRECT, 1);
EXPECT_TRUE(RunScriptExtractBool("checkImage()"));
EXPECT_EQ(request_url().port(), compression_url().port());
VerifyCompressibleImageUkm(1);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(0);
}
// This test loads private_url_image.html, which triggers a subresource
// request for private_url_image.png. This triggers an internal redirect
// to the mock compression server, which bypasses the request. The
// mock compression server creates a redirect to the original resource.
IN_PROC_BROWSER_TEST_F(
SubresourceRedirectBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(TestHTMLLoadRedirectBypass)) {
EnableDataSaver(true);
CreateUkmRecorder();
SetUpPublicImageURLPaths("/load_image/private_url_image.html",
{"/load_image/private_url_image.png"});
ui_test_utils::NavigateToURL(
browser(), HttpsURLWithPath("/load_image/private_url_image.html"));
RetryForHistogramUntilCountReached(
histogram_tester(), "SubresourceRedirect.CompressionAttempt.ResponseCode",
2);
histogram_tester()->ExpectBucketCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode",
net::HTTP_TEMPORARY_REDIRECT, 2);
EXPECT_TRUE(RunScriptExtractBool("checkImage()"));
EXPECT_EQ(GURL(RunScriptExtractString("imageSrc()")).port(),
https_url().port());
// The image will be marked as compressible even though the private image
// redirect was bypassed.
VerifyCompressibleImageUkm(1);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(0);
}
IN_PROC_BROWSER_TEST_F(SubresourceRedirectBrowserTest,
NoTriggerWhenDataSaverOff) {
EnableDataSaver(false);
CreateUkmRecorder();
ui_test_utils::NavigateToURL(
browser(), HttpsURLWithPath("/load_image/image_delayed_load.html"));
content::FetchHistogramsFromChildProcesses();
SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", 0);
EXPECT_TRUE(RunScriptExtractBool("checkImage()"));
EXPECT_EQ(GURL(RunScriptExtractString("imageSrc()")).port(),
https_url().port());
// No coverage metrics recorded.
VerifyCompressibleImageUkm(0);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(0);
}
IN_PROC_BROWSER_TEST_F(SubresourceRedirectBrowserTest, NoTriggerInIncognito) {
EnableDataSaver(true);
CreateUkmRecorder();
auto* incognito_browser = CreateIncognitoBrowser();
ui_test_utils::NavigateToURL(
incognito_browser,
HttpsURLWithPath("/load_image/image_delayed_load.html"));
content::FetchHistogramsFromChildProcesses();
SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", 0);
EXPECT_TRUE(RunScriptExtractBool(
"checkImage()",
incognito_browser->tab_strip_model()->GetActiveWebContents()));
EXPECT_EQ(
GURL(RunScriptExtractString(
"imageSrc()",
incognito_browser->tab_strip_model()->GetActiveWebContents()))
.port(),
https_url().port());
// No coverage metrics recorded.
VerifyCompressibleImageUkm(0);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(0);
}
// This test loads image.html, from a non secure site. This triggers a
// subresource request, but no internal redirect should be created for
// non-secure sites.
IN_PROC_BROWSER_TEST_F(SubresourceRedirectBrowserTest,
NoTriggerOnNonSecureSite) {
EnableDataSaver(true);
CreateUkmRecorder();
SetUpPublicImageURLPaths("/load_image/image_delayed_load.html",
{"/load_image/image.png"});
ui_test_utils::NavigateToURL(
browser(), HttpURLWithPath("/load_image/image_delayed_load.html"));
content::FetchHistogramsFromChildProcesses();
SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", 0);
EXPECT_TRUE(RunScriptExtractBool("checkImage()"));
EXPECT_EQ(GURL(RunScriptExtractString("imageSrc()")).port(),
http_url().port());
// No coverage metrics recorded.
VerifyCompressibleImageUkm(0);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(0);
}
// This test loads page_with_favicon.html, which creates a subresource
// request for icon.png. There should be no internal redirect as favicons
// are not considered images by chrome.
IN_PROC_BROWSER_TEST_F(SubresourceRedirectBrowserTest, NoTriggerOnNonImage) {
EnableDataSaver(true);
CreateUkmRecorder();
SetUpPublicImageURLPaths("/favicon/page_with_favicon.html",
{"/load_image/image.png"});
ui_test_utils::NavigateToURL(
browser(), HttpsURLWithPath("/favicon/page_with_favicon.html"));
content::FetchHistogramsFromChildProcesses();
SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", 0);
// No coverage metrics recorded.
VerifyCompressibleImageUkm(0);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(0);
}
} // namespace
// This test loads a resource that will return a 404 from the server, this
// should trigger the fallback logic back to the original resource. In total
// This results in 2 redirects (to the compression server, and back to the
// original resource), 1 404 not-found from the compression server, and 1
// 200 ok from the original resource.
IN_PROC_BROWSER_TEST_F(SubresourceRedirectBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(FallbackOnServerNotFound)) {
EnableDataSaver(true);
CreateUkmRecorder();
SetUpPublicImageURLPaths("/load_image/fail_image.html",
{"/load_image/fail_image.png"});
ui_test_utils::NavigateToURL(browser(),
HttpsURLWithPath("/load_image/fail_image.html"));
EXPECT_TRUE(RunScriptExtractBool("checkImage()"));
content::FetchHistogramsFromChildProcesses();
SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", 3);
histogram_tester()->ExpectBucketCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode",
net::HTTP_TEMPORARY_REDIRECT, 2);
histogram_tester()->ExpectBucketCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode",
net::HTTP_NOT_FOUND, 1);
EXPECT_EQ(GURL(RunScriptExtractString("imageSrc()")).port(),
https_url().port());
// Ineligible Other bucket ukm recorded.
VerifyCompressibleImageUkm(0);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(1);
}
// This test verifies that the client will utilize the fallback logic if the
// server/network fails and returns nothing.
IN_PROC_BROWSER_TEST_F(SubresourceRedirectBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(FallbackOnServerFailure)) {
EnableDataSaver(true);
CreateUkmRecorder();
SetUpPublicImageURLPaths("/load_image/image_delayed_load.html",
{"/load_image/image.png"});
SetCompressionServerToFail();
base::RunLoop().RunUntilIdle();
ui_test_utils::NavigateToURL(
browser(), HttpsURLWithPath("/load_image/image_delayed_load.html"));
EXPECT_TRUE(RunScriptExtractBool("checkImage()"));
RetryForHistogramUntilCountReached(
histogram_tester(),
"SubresourceRedirect.CompressionAttempt.ServerResponded", 1);
histogram_tester()->ExpectBucketCount(
"SubresourceRedirect.CompressionAttempt.ServerResponded", false, 1);
EXPECT_EQ(GURL(RunScriptExtractString("imageSrc()")).port(),
https_url().port());
// Ineligible Other bucket ukm recorded.
VerifyCompressibleImageUkm(0);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(1);
}
IN_PROC_BROWSER_TEST_F(
SubresourceRedirectBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(TestTwoPublicImagesAreRedirected)) {
EnableDataSaver(true);
CreateUkmRecorder();
SetUpPublicImageURLPaths(
"/load_image/two_images.html",
{"/load_image/image.png", "/load_image/image.png?foo"});
ui_test_utils::NavigateToURL(browser(),
HttpsURLWithPath("/load_image/two_images.html"));
RetryForHistogramUntilCountReached(
histogram_tester(), "SubresourceRedirect.CompressionAttempt.ResponseCode",
4);
histogram_tester()->ExpectBucketCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", net::HTTP_OK, 2);
histogram_tester()->ExpectBucketCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode",
net::HTTP_TEMPORARY_REDIRECT, 2);
EXPECT_TRUE(RunScriptExtractBool("checkBothImagesLoaded()"));
EXPECT_EQ(GURL(RunScriptExtractString("imageSrc()")).port(),
https_url().port());
VerifyCompressibleImageUkm(2);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(0);
}
// This test verifies that only the images in the public image URL list are
// is_subresource_redirect_feature_enabled. In this test both images should load
// but only one image should be redirected.
IN_PROC_BROWSER_TEST_F(
SubresourceRedirectBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(TestOnlyPublicImageIsRedirected)) {
EnableDataSaver(true);
CreateUkmRecorder();
SetUpPublicImageURLPaths("/load_image/two_images.html",
{"/load_image/image.png"});
ui_test_utils::NavigateToURL(browser(),
HttpsURLWithPath("/load_image/two_images.html"));
RetryForHistogramUntilCountReached(
histogram_tester(), "SubresourceRedirect.CompressionAttempt.ResponseCode",
2);
histogram_tester()->ExpectBucketCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", net::HTTP_OK, 1);
histogram_tester()->ExpectBucketCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode",
net::HTTP_TEMPORARY_REDIRECT, 1);
EXPECT_TRUE(RunScriptExtractBool("checkBothImagesLoaded()"));
EXPECT_EQ(GURL(RunScriptExtractString("imageSrc()")).port(),
https_url().port());
VerifyCompressibleImageUkm(1);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleMissingInImageHintsUkm(1);
VerifyIneligibleOtherImageUkm(0);
}
// This test verifies that the fragments in the image URL are removed before
// checking against the public image URL list.
IN_PROC_BROWSER_TEST_F(
SubresourceRedirectBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(TestImageURLFragmentAreRemoved)) {
EnableDataSaver(true);
CreateUkmRecorder();
SetUpPublicImageURLPaths("/load_image/image_with_fragment.html",
{"/load_image/image.png"});
ui_test_utils::NavigateToURL(
browser(), HttpsURLWithPath("/load_image/image_with_fragment.html"));
RetryForHistogramUntilCountReached(
histogram_tester(), "SubresourceRedirect.CompressionAttempt.ResponseCode",
2);
histogram_tester()->ExpectBucketCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", net::HTTP_OK, 1);
histogram_tester()->ExpectBucketCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode",
net::HTTP_TEMPORARY_REDIRECT, 1);
EXPECT_TRUE(RunScriptExtractBool("checkImage()"));
EXPECT_EQ(GURL(RunScriptExtractString("imageSrc()")).port(),
https_url().port());
VerifyCompressibleImageUkm(1);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(0);
}
// This test loads image_js.html, which triggers a javascript request
// for image.png for which subresource redirect will not be attempted.
IN_PROC_BROWSER_TEST_F(SubresourceRedirectBrowserTest,
NoTriggerOnJavaScriptImageRequest) {
EnableDataSaver(true);
CreateUkmRecorder();
SetUpPublicImageURLPaths("/load_image/image_js.html",
{"/load_image/image.png"});
ui_test_utils::NavigateToURL(browser(),
HttpsURLWithPath("/load_image/image_js.html"));
content::FetchHistogramsFromChildProcesses();
SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", 0);
EXPECT_TRUE(RunScriptExtractBool("checkImage()"));
EXPECT_EQ(GURL(RunScriptExtractString("imageSrc()")).port(),
https_url().port());
VerifyIneligibleOtherImageUkm(1);
VerifyCompressibleImageUkm(0);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleMissingInImageHintsUkm(0);
}
// This test verifies that no image redirect happens when empty hints is sent.
IN_PROC_BROWSER_TEST_F(
SubresourceRedirectBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(TestNoRedirectWithEmptyHints)) {
EnableDataSaver(true);
CreateUkmRecorder();
SetUpPublicImageURLPaths("/load_image/image_delayed_load.html", {});
ui_test_utils::NavigateToURL(
browser(), HttpsURLWithPath("/load_image/image_delayed_load.html"));
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", 0);
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ServerResponded", 0);
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.DidCompress.CompressionPercent", 0);
EXPECT_TRUE(RunScriptExtractBool("checkImage()"));
EXPECT_EQ(GURL(RunScriptExtractString("imageSrc()")).port(),
https_url().port());
VerifyIneligibleMissingInImageHintsUkm(1);
VerifyCompressibleImageUkm(0);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleOtherImageUkm(0);
}
// This test verifies that no image redirect happens when hints are not yet
// received.
IN_PROC_BROWSER_TEST_F(SubresourceRedirectBrowserTest,
TestNoRedirectWithoutHints) {
EnableDataSaver(true);
CreateUkmRecorder();
ui_test_utils::NavigateToURL(
browser(), HttpsURLWithPath("/load_image/image_delayed_load.html"));
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", 0);
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ServerResponded", 0);
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.DidCompress.CompressionPercent", 0);
EXPECT_TRUE(RunScriptExtractBool("checkImage()"));
EXPECT_EQ(GURL(RunScriptExtractString("imageSrc()")).port(),
https_url().port());
WaitForImageCompressionUkmMetrics(1);
VerifyIneligibleImageHintsUnavailableUkm(1);
VerifyCompressibleImageUkm(0);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(0);
}
// This test verifies that two images in a page are not redirected, when hints
// are missing.
IN_PROC_BROWSER_TEST_F(SubresourceRedirectBrowserTest,
TestNoRedirectWithoutHintsTwoImages) {
EnableDataSaver(true);
CreateUkmRecorder();
ui_test_utils::NavigateToURL(browser(),
HttpsURLWithPath("/load_image/two_images.html"));
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", 0);
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ServerResponded", 0);
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.DidCompress.CompressionPercent", 0);
EXPECT_TRUE(RunScriptExtractBool("checkBothImagesLoaded()"));
EXPECT_EQ(GURL(RunScriptExtractString("imageSrc()")).port(),
https_url().port());
WaitForImageCompressionUkmMetrics(2);
VerifyCompressibleImageUkm(0);
VerifyIneligibleImageHintsUnavailableUkm(2);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(0);
}
// This test initiates same-origin navigation and verifies the hints from the
// previous navigation are not used.
IN_PROC_BROWSER_TEST_F(SubresourceRedirectBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(TestSameOriginNavigation)) {
g_browser_process->network_quality_tracker()
->ReportEffectiveConnectionTypeForTesting(
net::EFFECTIVE_CONNECTION_TYPE_2G);
EnableDataSaver(true);
CreateUkmRecorder();
SetUpPublicImageURLPaths("/load_image/image_delayed_load.html",
{"/load_image/image.png"});
ui_test_utils::NavigateToURL(
browser(), HttpsURLWithPath("/load_image/image_delayed_load.html"));
RetryForHistogramUntilCountReached(
histogram_tester(), "SubresourceRedirect.CompressionAttempt.ResponseCode",
2);
histogram_tester()->ExpectBucketCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", net::HTTP_OK, 1);
histogram_tester()->ExpectBucketCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode",
net::HTTP_TEMPORARY_REDIRECT, 1);
EXPECT_TRUE(RunScriptExtractBool("checkImage()"));
EXPECT_EQ(request_url().port(), compression_url().port());
VerifyCompressibleImageUkm(1);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(0);
// Initiate a same-origin navigation without hints, and let the timeout ukm be
// recorded.
CreateUkmRecorder();
ui_test_utils::NavigateToURL(browser(),
HttpsURLWithPath("/load_image/two_images.html"));
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", 2);
EXPECT_TRUE(RunScriptExtractBool("checkBothImagesLoaded()"));
EXPECT_EQ(GURL(RunScriptExtractString("imageSrc()")).port(),
https_url().port());
WaitForImageCompressionUkmMetrics(2);
VerifyCompressibleImageUkm(0);
VerifyIneligibleImageHintsUnavailableUkm(2);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(0);
}
// This test verifies that the image redirect to lite page is disabled via
// finch, and only the coverage metrics are recorded.
IN_PROC_BROWSER_TEST_F(RedirectDisabledSubresourceRedirectBrowserTest,
ImagesNotRedirected) {
EnableDataSaver(true);
CreateUkmRecorder();
SetUpPublicImageURLPaths("/load_image/image_delayed_load.html",
{"/load_image/image.png"});
ui_test_utils::NavigateToURL(
browser(), HttpsURLWithPath("/load_image/image_delayed_load.html"));
content::FetchHistogramsFromChildProcesses();
SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", 0);
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ServerResponded", 0);
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.DidCompress.CompressionPercent", 0);
EXPECT_TRUE(RunScriptExtractBool("checkImage()"));
EXPECT_EQ(GURL(RunScriptExtractString("imageSrc()")).port(),
https_url().port());
VerifyCompressibleImageUkm(1);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(0);
}
// This class sets up a hints server where image hints are fetched from to test
// the page level hint fetches.
class SubresourceRedirectWithHintsServerBrowserTest
: public SubresourceRedirectBrowserTest {
public:
// How the hints server should respond to the get hints request.
enum HintFetchMode {
// Delay the hints fetch until images are loaded. Delay 3 seconds before
// sending response. This delay is chosen such that the server sends the
// hints after the image has completed loading but before the hints receive
// timeout(5 seconds).
HINT_FETCH_AFTER_IMAGES_LOADED,
// Do not send response.
HINT_FETCH_HUNG,
};
SubresourceRedirectWithHintsServerBrowserTest()
: SubresourceRedirectBrowserTest(true),
hints_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
void SetUp() override {
hints_server_.ServeFilesFromSourceDirectory("chrome/test/data/previews");
hints_server_.RegisterRequestHandler(base::BindRepeating(
&SubresourceRedirectWithHintsServerBrowserTest::HandleGetHintsRequest,
base::Unretained(this)));
ASSERT_TRUE(hints_server_.Start());
SubresourceRedirectBrowserTest::SetUp();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch("ignore-certificate-errors");
command_line->AppendSwitch("purge_hint_cache_store");
command_line->AppendSwitch(optimization_guide::switches::
kDisableCheckingUserPermissionsForTesting);
command_line->AppendSwitchASCII(
optimization_guide::switches::kOptimizationGuideServiceGetHintsURL,
hints_server_.base_url().spec());
command_line->AppendSwitchASCII(
optimization_guide::switches::kFetchHintsOverride, "secure.com");
command_line->AppendSwitch(
optimization_guide::switches::kFetchHintsOverrideTimer);
SubresourceRedirectBrowserTest::SetUpCommandLine(command_line);
}
void SetUpOnMainThread() override {
content::NetworkConnectionChangeSimulator().SetConnectionType(
network::mojom::ConnectionType::CONNECTION_2G);
SubresourceRedirectBrowserTest::SetUpOnMainThread();
}
// Sets up public image URL hint data that is returned by the hints server.
void SetUpPublicImageURLPaths(
const std::string& url,
const std::vector<std::string>& public_image_paths,
HintFetchMode hint_fetch_mode) {
hint_fetch_mode_ = hint_fetch_mode;
optimization_guide::proto::GetHintsResponse get_hints_response;
optimization_guide::proto::Hint* hint = get_hints_response.add_hints();
hint->set_key_representation(optimization_guide::proto::FULL_URL);
hint->set_key(HttpsURLWithPath(url).spec());
optimization_guide::proto::PageHint* page_hint = hint->add_page_hints();
page_hint->set_page_pattern(HttpsURLWithPath(url).spec());
auto* optimization = page_hint->add_whitelisted_optimizations();
optimization->set_optimization_type(
optimization_guide::proto::OptimizationType::COMPRESS_PUBLIC_IMAGES);
auto* public_image_metadata = optimization->mutable_public_image_metadata();
for (const auto& url : public_image_paths)
public_image_metadata->add_url(HttpsURLWithPath(url).spec());
base::AutoLock lock(lock_);
get_hints_response.SerializeToString(&get_hints_response_);
}
private:
std::unique_ptr<net::test_server::HttpResponse> HandleGetHintsRequest(
const net::test_server::HttpRequest& request) {
switch (hint_fetch_mode_) {
case HintFetchMode::HINT_FETCH_AFTER_IMAGES_LOADED: {
auto response = std::make_unique<net::test_server::DelayedHttpResponse>(
base::TimeDelta::FromSeconds(3));
response->set_content(get_hints_response_);
response->set_code(net::HTTP_OK);
return std::move(response);
}
case HintFetchMode::HINT_FETCH_HUNG:
return std::make_unique<net::test_server::HungResponse>();
}
return nullptr;
}
net::EmbeddedTestServer hints_server_;
std::string get_hints_response_;
base::Lock lock_;
HintFetchMode hint_fetch_mode_ =
HintFetchMode::HINT_FETCH_AFTER_IMAGES_LOADED;
};
// This test verifies that two images in a page are not redirected, when hints
// are received delayed.
IN_PROC_BROWSER_TEST_F(
SubresourceRedirectWithHintsServerBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(TestNoRedirectWithDelayedHintsTwoImages)) {
g_browser_process->network_quality_tracker()
->ReportEffectiveConnectionTypeForTesting(
net::EFFECTIVE_CONNECTION_TYPE_2G);
EnableDataSaver(true);
CreateUkmRecorder();
SetUpPublicImageURLPaths("/load_image/two_images.html",
{"/load_image/image.png"},
HintFetchMode::HINT_FETCH_AFTER_IMAGES_LOADED);
ui_test_utils::NavigateToURL(browser(),
HttpsURLWithPath("/load_image/two_images.html"));
// Let the images load.
EXPECT_TRUE(RunScriptExtractBool("checkBothImagesLoaded()"));
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", 0);
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ServerResponded", 0);
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.DidCompress.CompressionPercent", 0);
EXPECT_EQ(GURL(RunScriptExtractString("imageSrc()")).port(),
https_url().port());
// One image will be recorded as compressible, but image hints not received in
// time. Another image is recorded as not compressible, and image hints not
// received in time.
WaitForImageCompressionUkmMetrics(2);
VerifyIneligibleImageHintsUnavailableUkmButCompressible(1);
VerifyIneligibleImageHintsUnavailableAndMissingInHintsUkm(1);
VerifyCompressibleImageUkm(0);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(0);
}
// Tests CSS background images are redirected.
// Disabled due to flakes. See https://crbug.com/1063736.
IN_PROC_BROWSER_TEST_F(
SubresourceRedirectBrowserTest,
DISABLED_TestCSSBackgroundImageRedirect) {
EnableDataSaver(true);
CreateUkmRecorder();
SetUpPublicImageURLPaths("/load_image/css_background_image.html",
{"/load_image/image.png"});
ui_test_utils::NavigateToURLWithDisposition(
browser(), HttpsURLWithPath("/load_image/css_background_image.html"),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
RetryForHistogramUntilCountReached(
histogram_tester(), "SubresourceRedirect.CompressionAttempt.ResponseCode",
2);
base::RunLoop().RunUntilIdle();
histogram_tester()->ExpectBucketCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", net::HTTP_OK, 1);
histogram_tester()->ExpectBucketCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode",
net::HTTP_TEMPORARY_REDIRECT, 1);
EXPECT_EQ(request_url().port(), compression_url().port());
VerifyCompressibleImageUkm(1);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(0);
}
// Tests CSS background image coverage metrics is recorded but not redirected,
// when redirect is disabled.
IN_PROC_BROWSER_TEST_F(
RedirectDisabledSubresourceRedirectBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(TestCSSBackgroundImageRedirect)) {
EnableDataSaver(true);
CreateUkmRecorder();
SetUpPublicImageURLPaths("/load_image/css_background_image.html",
{"/load_image/image.png"});
ui_test_utils::NavigateToURLWithDisposition(
browser(), HttpsURLWithPath("/load_image/css_background_image.html"),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
content::FetchHistogramsFromChildProcesses();
SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
base::RunLoop().RunUntilIdle();
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ResponseCode", 0);
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.CompressionAttempt.ServerResponded", 0);
histogram_tester()->ExpectTotalCount(
"SubresourceRedirect.DidCompress.CompressionPercent", 0);
VerifyCompressibleImageUkm(1);
VerifyIneligibleImageHintsUnavailableUkm(0);
VerifyIneligibleMissingInImageHintsUkm(0);
VerifyIneligibleOtherImageUkm(0);
}