blob: 1d8d20364fdcaed26812057c7d083884250e5ed1 [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 <map>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/task/post_task.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/metrics/subprocess_metrics_provider.h"
#include "chrome/browser/previews/previews_service.h"
#include "chrome/browser/previews/previews_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_features.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/proto/hints.pb.h"
#include "components/optimization_guide/test_hints_component_creator.h"
#include "components/previews/core/previews_black_list.h"
#include "components/previews/core/previews_features.h"
#include "components/previews/core/previews_switches.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/test/browser_test_utils.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"
namespace {
// Expected console output when defer preview is not applied to the test
// webpage.
static const char kNonDeferredPageExpectedOutput[] =
"ScriptLog:_InlineScript_SyncScript_BodyEnd_DeveloperDeferScript_OnLoad";
// Expected console output when defer preview is applied to the test webpage.
static const char kDeferredPageExpectedOutput[] =
"ScriptLog:_BodyEnd_InlineScript_SyncScript_DeveloperDeferScript_OnLoad";
// Retries fetching |histogram_name| until it contains at least |count| samples.
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;
}
}
}
} // namespace
class DeferAllScriptBrowserTest : public InProcessBrowserTest,
public testing::WithParamInterface<bool> {
public:
DeferAllScriptBrowserTest() {
scoped_feature_list_.InitWithFeatures(
{previews::features::kPreviews,
previews::features::kDeferAllScriptPreviews,
optimization_guide::features::kOptimizationHints,
data_reduction_proxy::features::
kDataReductionProxyEnabledWithNetworkService},
{});
if (GetParam()) {
param_feature_list_.InitWithFeatures(
{optimization_guide::features::kOptimizationGuideKeyedService}, {});
} else {
param_feature_list_.InitWithFeatures(
{}, {optimization_guide::features::kOptimizationGuideKeyedService});
}
}
~DeferAllScriptBrowserTest() override = default;
void SetUpOnMainThread() override {
g_browser_process->network_quality_tracker()
->ReportEffectiveConnectionTypeForTesting(
net::EFFECTIVE_CONNECTION_TYPE_2G);
https_server_.reset(
new net::EmbeddedTestServer(net::EmbeddedTestServer::TYPE_HTTPS));
https_server_->ServeFilesFromSourceDirectory("chrome/test/data/previews");
ASSERT_TRUE(https_server_->Start());
https_url_ = https_server_->GetURL("/defer_all_script_test.html");
ASSERT_TRUE(https_url_.SchemeIs(url::kHttpsScheme));
client_redirect_url_ = https_server_->GetURL("/client_redirect_base.html");
client_redirect_url_target_url_ = https_server_->GetURL(
"/client_redirect_loop_with_defer_all_script.html");
server_redirect_url_ = https_server_->GetURL("/server_redirect_base.html");
server_redirect_base_redirect_to_final_server_redirect_url_ =
https_server_->GetURL(
"/server_redirect_base_redirect_to_final_server_redirect.html");
server_denylist_url_ = https_server_->GetURL("/login.html");
InProcessBrowserTest::SetUpOnMainThread();
}
void SetUpCommandLine(base::CommandLine* cmd) override {
cmd->AppendSwitch("enable-spdy-proxy-auth");
cmd->AppendSwitch("optimization-guide-disable-installer");
cmd->AppendSwitch("purge_hint_cache_store");
// Due to race conditions, it's possible that blacklist data is not loaded
// at the time of first navigation. That may prevent Preview from
// triggering, and causing the test to flake.
cmd->AppendSwitch(previews::switches::kIgnorePreviewsBlacklist);
}
// Creates hint data from the |component_info| and waits for it to be fully
// processed before returning.
void ProcessHintsComponent(
const optimization_guide::HintsComponentInfo& component_info) {
base::HistogramTester histogram_tester;
g_browser_process->optimization_guide_service()->MaybeUpdateHintsComponent(
component_info);
RetryForHistogramUntilCountReached(
&histogram_tester,
optimization_guide::kComponentHintsUpdatedResultHistogramString, 1);
}
// Performs a navigation to |url| and waits for the the url's host's hints to
// load before returning. This ensures that the hints will be available in the
// hint cache for a subsequent navigation to a test url with the same host.
void LoadHintsForUrl(const GURL& url) {
base::HistogramTester histogram_tester;
// Navigate to the url to prime the OptimizationGuide hints for the
// url's host and ensure that they have been loaded from the store (via
// histogram) prior to the navigation that tests functionality.
ui_test_utils::NavigateToURL(browser(), url);
RetryForHistogramUntilCountReached(
&histogram_tester, optimization_guide::kLoadedHintLocalHistogramString,
1);
}
void SetDeferAllScriptHintWithPageWithPattern(
const GURL& hint_setup_url,
const std::string& page_pattern) {
ProcessHintsComponent(
test_hints_component_creator_.CreateHintsComponentInfoWithPageHints(
optimization_guide::proto::DEFER_ALL_SCRIPT,
{hint_setup_url.host()}, page_pattern, {}));
LoadHintsForUrl(hint_setup_url);
}
virtual const GURL& https_url() const { return https_url_; }
const GURL& client_redirect_url() const { return client_redirect_url_; }
const GURL& client_redirect_url_target_url() const {
return client_redirect_url_target_url_;
}
const GURL& server_redirect_url() const { return server_redirect_url_; }
const GURL& server_redirect_base_redirect_to_final_server_redirect() const {
return server_redirect_base_redirect_to_final_server_redirect_url_;
}
const GURL& server_denylist_url() const { return server_denylist_url_; }
std::string GetScriptLog() {
std::string script_log;
EXPECT_TRUE(ExecuteScriptAndExtractString(
browser()->tab_strip_model()->GetActiveWebContents(), "sendLogToTest()",
&script_log));
return script_log;
}
protected:
base::test::ScopedFeatureList scoped_feature_list_;
base::test::ScopedFeatureList param_feature_list_;
private:
void TearDownOnMainThread() override {
EXPECT_TRUE(https_server_->ShutdownAndWaitUntilComplete());
InProcessBrowserTest::TearDownOnMainThread();
}
optimization_guide::testing::TestHintsComponentCreator
test_hints_component_creator_;
std::unique_ptr<net::EmbeddedTestServer> https_server_;
GURL https_url_;
GURL client_redirect_url_;
GURL client_redirect_url_target_url_;
GURL server_redirect_url_;
GURL server_denylist_url_;
GURL server_redirect_base_redirect_to_final_server_redirect_url_;
DISALLOW_COPY_AND_ASSIGN(DeferAllScriptBrowserTest);
};
// True if testing using the OptimizationGuideKeyedService implementation.
INSTANTIATE_TEST_SUITE_P(OptimizationGuideKeyedServiceImplementation,
DeferAllScriptBrowserTest,
testing::Bool());
// Avoid flakes and issues on non-applicable platforms.
#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
#define DISABLE_ON_WIN_MAC_CHROMESOS(x) DISABLED_##x
#else
#define DISABLE_ON_WIN_MAC_CHROMESOS(x) x
#endif
IN_PROC_BROWSER_TEST_P(
DeferAllScriptBrowserTest,
DISABLE_ON_WIN_MAC_CHROMESOS(DeferAllScriptHttpsWhitelisted)) {
GURL url = https_url();
// Whitelist DeferAllScript for any path for the url's host.
SetDeferAllScriptHintWithPageWithPattern(url, "*");
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder test_ukm_recorder;
ui_test_utils::NavigateToURL(browser(), url);
RetryForHistogramUntilCountReached(
&histogram_tester, "PageLoad.DocumentTiming.NavigationToLoadEventFired",
1);
EXPECT_EQ(kDeferredPageExpectedOutput, GetScriptLog());
histogram_tester.ExpectBucketCount(
"Previews.EligibilityReason.DeferAllScript",
static_cast<int>(previews::PreviewsEligibilityReason::COMMITTED), 1);
histogram_tester.ExpectBucketCount("Previews.PreviewShown.DeferAllScript",
true, 1);
histogram_tester.ExpectTotalCount("Previews.PageEndReason.DeferAllScript", 1);
histogram_tester.ExpectUniqueSample(
"Blink.Script.ForceDeferredScripts.Mainframe", 2, 1);
histogram_tester.ExpectUniqueSample(
"Blink.Script.ForceDeferredScripts.Mainframe.External", 1, 1);
// Verify UKM force deferred count entries.
using UkmDeferEntry = ukm::builders::PreviewsDeferAllScript;
auto entries = test_ukm_recorder.GetEntriesByName(UkmDeferEntry::kEntryName);
ASSERT_EQ(1u, entries.size());
auto* entry = entries.at(0);
test_ukm_recorder.ExpectEntryMetric(
entry, UkmDeferEntry::kforce_deferred_scripts_mainframeName, 2);
test_ukm_recorder.ExpectEntryMetric(
entry, UkmDeferEntry::kforce_deferred_scripts_mainframe_externalName, 1);
}
// Defer should not be used on a webpage whose URL matches the denylist regex.
IN_PROC_BROWSER_TEST_P(
DeferAllScriptBrowserTest,
DISABLE_ON_WIN_MAC_CHROMESOS(DeferAllScriptHttpsWhitelistedDenylistURL)) {
// Whitelist DeferAllScript for any path for the url's host.
SetDeferAllScriptHintWithPageWithPattern(server_denylist_url(), "*");
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder test_ukm_recorder;
ui_test_utils::NavigateToURL(browser(), server_denylist_url());
RetryForHistogramUntilCountReached(
&histogram_tester, "PageLoad.DocumentTiming.NavigationToLoadEventFired",
1);
RetryForHistogramUntilCountReached(
&histogram_tester, "Previews.DeferAllScript.DenyListMatch", 1);
histogram_tester.ExpectUniqueSample("Previews.DeferAllScript.DenyListMatch",
true, 1);
// Verify UKM entry.
using UkmEntry = ukm::builders::Previews;
auto entries = test_ukm_recorder.GetEntriesByName(UkmEntry::kEntryName);
ASSERT_EQ(1u, entries.size());
auto* entry = entries.at(0);
test_ukm_recorder.ExpectEntryMetric(
entry, UkmEntry::kdefer_all_script_eligibility_reasonName,
static_cast<int>(previews::PreviewsEligibilityReason::DENY_LIST_MATCHED));
EXPECT_EQ(kNonDeferredPageExpectedOutput, GetScriptLog());
}
IN_PROC_BROWSER_TEST_P(
DeferAllScriptBrowserTest,
DISABLE_ON_WIN_MAC_CHROMESOS(DeferAllScriptHttpsNotWhitelisted)) {
GURL url = https_url();
// Whitelist DeferAllScript for the url's host but with nonmatching pattern.
SetDeferAllScriptHintWithPageWithPattern(url, "/NoMatch/");
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder test_ukm_recorder;
// The URL is not whitelisted.
ui_test_utils::NavigateToURL(browser(), url);
RetryForHistogramUntilCountReached(
&histogram_tester, "PageLoad.DocumentTiming.NavigationToLoadEventFired",
1);
EXPECT_EQ(kNonDeferredPageExpectedOutput, GetScriptLog());
histogram_tester.ExpectBucketCount(
"Previews.EligibilityReason.DeferAllScript",
static_cast<int>(previews::PreviewsEligibilityReason::
NOT_ALLOWED_BY_OPTIMIZATION_GUIDE),
1);
histogram_tester.ExpectTotalCount("Previews.PreviewShown.DeferAllScript", 0);
histogram_tester.ExpectTotalCount("Previews.PageEndReason.DeferAllScript", 0);
}
class DeferAllScriptBrowserTestWithCoinFlipHoldback
: public DeferAllScriptBrowserTest {
public:
DeferAllScriptBrowserTestWithCoinFlipHoldback() {
// Holdback the page load from previews and also disable offline previews to
// ensure that only post-commit previews are enabled.
feature_list_.InitWithFeaturesAndParameters(
{{previews::features::kCoinFlipHoldback,
{{"force_coin_flip_always_holdback", "true"}}}},
{previews::features::kOfflinePreviews});
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_P(
DeferAllScriptBrowserTestWithCoinFlipHoldback,
DISABLE_ON_WIN_MAC_CHROMESOS(
DeferAllScriptHttpsWhitelistedButWithCoinFlipHoldback)) {
GURL url = https_url();
// Whitelist DeferAllScript for any path for the url's host.
SetDeferAllScriptHintWithPageWithPattern(url, "*");
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder test_ukm_recorder;
ui_test_utils::NavigateToURL(browser(), url);
RetryForHistogramUntilCountReached(
&histogram_tester, "PageLoad.DocumentTiming.NavigationToLoadEventFired",
1);
EXPECT_EQ(kNonDeferredPageExpectedOutput, GetScriptLog());
histogram_tester.ExpectBucketCount(
"Previews.EligibilityReason.DeferAllScript",
static_cast<int>(previews::PreviewsEligibilityReason::COMMITTED), 1);
histogram_tester.ExpectTotalCount("Previews.PreviewShown.DeferAllScript", 0);
histogram_tester.ExpectTotalCount("Previews.PageEndReason.DeferAllScript", 0);
// Verify UKM entries.
{
using UkmEntry = ukm::builders::Previews;
auto entries = test_ukm_recorder.GetEntriesByName(UkmEntry::kEntryName);
ASSERT_EQ(1u, entries.size());
auto* entry = entries.at(0);
test_ukm_recorder.ExpectEntryMetric(entry, UkmEntry::kpreviews_likelyName,
1);
test_ukm_recorder.ExpectEntryMetric(entry, UkmEntry::kdefer_all_scriptName,
true);
}
{
using UkmEntry = ukm::builders::PreviewsCoinFlip;
auto entries = test_ukm_recorder.GetEntriesByName(UkmEntry::kEntryName);
ASSERT_EQ(1u, entries.size());
auto* entry = entries.at(0);
test_ukm_recorder.ExpectEntryMetric(entry, UkmEntry::kcoin_flip_resultName,
2);
}
}
INSTANTIATE_TEST_SUITE_P(OptimizationGuideKeyedServiceImplementation,
DeferAllScriptBrowserTestWithCoinFlipHoldback,
testing::Bool());
// The client_redirect_url (/client_redirect_base.html) performs a client
// redirect to "/client_redirect_loop_with_defer_all_script.html" which
// peforms a client redirect back to the initial client_redirect_url if
// and only if script execution is deferred. This emulates the navigation
// pattern seen in crbug.com/987062
IN_PROC_BROWSER_TEST_P(
DeferAllScriptBrowserTest,
DISABLE_ON_WIN_MAC_CHROMESOS(DeferAllScriptClientRedirectLoopStopped)) {
PreviewsService* previews_service = PreviewsServiceFactory::GetForProfile(
Profile::FromBrowserContext(browser()
->tab_strip_model()
->GetActiveWebContents()
->GetBrowserContext()));
EXPECT_TRUE(previews_service->IsUrlEligibleForDeferAllScriptPreview(
client_redirect_url()));
EXPECT_TRUE(
previews_service->IsUrlEligibleForDeferAllScriptPreview(https_url()));
// Whitelist DeferAllScript for any path for the url's host.
SetDeferAllScriptHintWithPageWithPattern(https_url(), "*");
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder test_ukm_recorder;
ui_test_utils::NavigateToURL(browser(), client_redirect_url());
RetryForHistogramUntilCountReached(
&histogram_tester, "Navigation.ClientRedirectCycle.RedirectToReferrer",
2);
RetryForHistogramUntilCountReached(
&histogram_tester, "Previews.PageEndReason.DeferAllScript", 3);
// If there is a redirect loop, call to NavigateToURL() would never finish.
// The checks belows are additional checks to ensure that the logic to detect
// redirect loops is being called.
//
// Client redirect loop is broken on 2nd pass around the loop so expect 3
// previews before previews turned off to stop loop.
histogram_tester.ExpectTotalCount(
"Navigation.ClientRedirectCycle.RedirectToReferrer", 2);
histogram_tester.ExpectTotalCount("Previews.PageEndReason.DeferAllScript", 3);
EXPECT_FALSE(previews_service->IsUrlEligibleForDeferAllScriptPreview(
client_redirect_url()));
EXPECT_FALSE(previews_service->IsUrlEligibleForDeferAllScriptPreview(
client_redirect_url_target_url()));
// https_url() is not in redirect chain and should still be eligible for the
// preview.
EXPECT_TRUE(
previews_service->IsUrlEligibleForDeferAllScriptPreview(https_url()));
}
// The server_redirect_url (/server_redirect_url.html) performs a server
// rediect to client_redirect_url() which redirects to
// client_redirect_url_target_url(). Finally,
// client_redirect_url_target_url() performs a client redirect back to
// client_redirect_url() only if script execution is deferred.
IN_PROC_BROWSER_TEST_P(DeferAllScriptBrowserTest,
DISABLE_ON_WIN_MAC_CHROMESOS(
DeferAllScriptServerClientRedirectLoopStopped)) {
PreviewsService* previews_service = PreviewsServiceFactory::GetForProfile(
Profile::FromBrowserContext(browser()
->tab_strip_model()
->GetActiveWebContents()
->GetBrowserContext()));
EXPECT_TRUE(previews_service->IsUrlEligibleForDeferAllScriptPreview(
server_redirect_url()));
EXPECT_TRUE(
previews_service->IsUrlEligibleForDeferAllScriptPreview(https_url()));
// Whitelist DeferAllScript for any path for the url's host.
SetDeferAllScriptHintWithPageWithPattern(https_url(), "*");
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder test_ukm_recorder;
ui_test_utils::NavigateToURL(browser(), server_redirect_url());
// If there is a redirect loop, call to NavigateToURL() would never finish.
// The checks belows are additional checks to ensure that the logic to detect
// redirect loops is being called.
//
// Client redirect loop is broken on 2nd pass around the loop so expect 3
// previews before previews turned off to stop loop.
RetryForHistogramUntilCountReached(
&histogram_tester, "Navigation.ClientRedirectCycle.RedirectToReferrer",
2);
RetryForHistogramUntilCountReached(
&histogram_tester, "Previews.PageEndReason.DeferAllScript", 3);
histogram_tester.ExpectTotalCount(
"Navigation.ClientRedirectCycle.RedirectToReferrer", 2);
histogram_tester.ExpectTotalCount("Previews.PageEndReason.DeferAllScript", 3);
EXPECT_FALSE(previews_service->IsUrlEligibleForDeferAllScriptPreview(
server_redirect_url()));
EXPECT_FALSE(previews_service->IsUrlEligibleForDeferAllScriptPreview(
client_redirect_url()));
EXPECT_FALSE(previews_service->IsUrlEligibleForDeferAllScriptPreview(
client_redirect_url_target_url()));
// https_url() is not in redirect chain and should still be eligible for the
// preview.
EXPECT_TRUE(
previews_service->IsUrlEligibleForDeferAllScriptPreview(https_url()));
}
// server_redirect_base_redirect_to_final_server_redirect()
// performs a server redirect which does a client redirect followed
// by another client redirect (only when defer is enabled) to
// server_redirect_base_redirect_to_final_server_redirect().
IN_PROC_BROWSER_TEST_P(
DeferAllScriptBrowserTest,
DISABLE_ON_WIN_MAC_CHROMESOS(
DeferAllScriptServerClientServerClientServerRedirectLoopStopped)) {
PreviewsService* previews_service = PreviewsServiceFactory::GetForProfile(
Profile::FromBrowserContext(browser()
->tab_strip_model()
->GetActiveWebContents()
->GetBrowserContext()));
EXPECT_TRUE(previews_service->IsUrlEligibleForDeferAllScriptPreview(
server_redirect_base_redirect_to_final_server_redirect()));
EXPECT_TRUE(
previews_service->IsUrlEligibleForDeferAllScriptPreview(https_url()));
// Whitelist DeferAllScript for any path for the url's host.
SetDeferAllScriptHintWithPageWithPattern(https_url(), "*");
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder test_ukm_recorder;
ui_test_utils::NavigateToURL(
browser(), server_redirect_base_redirect_to_final_server_redirect());
// If there is a redirect loop, call to NavigateToURL() would never finish.
// The checks belows are additional checks to ensure that the logic to detect
// redirect loops is being called.
RetryForHistogramUntilCountReached(
&histogram_tester, "Navigation.ClientRedirectCycle.RedirectToReferrer",
1);
RetryForHistogramUntilCountReached(
&histogram_tester, "Previews.PageEndReason.DeferAllScript", 3);
histogram_tester.ExpectTotalCount(
"Navigation.ClientRedirectCycle.RedirectToReferrer", 1);
histogram_tester.ExpectTotalCount("Previews.PageEndReason.DeferAllScript", 3);
EXPECT_FALSE(previews_service->IsUrlEligibleForDeferAllScriptPreview(
server_redirect_base_redirect_to_final_server_redirect()));
// https_url() is not in redirect chain and should still be eligible for the
// preview.
EXPECT_TRUE(
previews_service->IsUrlEligibleForDeferAllScriptPreview(https_url()));
// Verify UKM entry.
using UkmEntry = ukm::builders::Previews;
auto entries = test_ukm_recorder.GetEntriesByName(UkmEntry::kEntryName);
ASSERT_EQ(4u, entries.size());
auto* entry = entries.at(3);
test_ukm_recorder.ExpectEntryMetric(
entry, UkmEntry::kdefer_all_script_eligibility_reasonName,
static_cast<int>(
previews::PreviewsEligibilityReason::REDIRECT_LOOP_DETECTED));
}