blob: 9573d00a0ccfc2a23baadc11286ef62e52c7edfc [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/feature_list.h"
#include "chrome/browser/optimization_guide/blink/blink_optimization_guide_inquirer.h"
#include "chrome/browser/optimization_guide/blink/blink_optimization_guide_web_contents_observer.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.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/optimization_guide/core/optimization_guide_features.h"
#include "components/optimization_guide/proto/delay_async_script_execution_metadata.pb.h"
#include "components/optimization_guide/proto/delay_competing_low_priority_requests_metadata.pb.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/browser/notification_types.h"
#include "content/public/test/browser_test.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "third_party/blink/public/common/features.h"
namespace optimization_guide {
// The base class of various browser tests for the Blink optimization guide.
// This provides the common test utilities.
class BlinkOptimizationGuideBrowserTestBase : public InProcessBrowserTest {
public:
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
}
void TearDownOnMainThread() override {
EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
InProcessBrowserTest::TearDownOnMainThread();
}
BlinkOptimizationGuideWebContentsObserver* GetObserverForActiveWebContents() {
return content::WebContentsUserData<
BlinkOptimizationGuideWebContentsObserver>::
FromWebContents(browser()->tab_strip_model()->GetActiveWebContents());
}
BlinkOptimizationGuideInquirer* GetCurrentInquirer() {
return GetObserverForActiveWebContents()->current_inquirer();
}
GURL GetURLWithMockHost(const std::string& relative_url) const {
// The optimization guide service doesn't work with the localhost. Instead,
// resolve the relative url with the mock host.
return embedded_test_server()->GetURL("mock.host", relative_url);
}
};
// The BlinkOptimizationGuideBrowserTest tests common behavior of optimization
// types for Blink (e.g., DELAY_ASYNC_SCRIPT_EXECUTION).
//
// This is designed to be optimization type independent. Add optimization type
// specific things to helper functions like ConstructionMetadata() instead of
// in the test body.
//
// To add a new optimization type, add cases to all of the switch statements in
// this class that handle optimization-specific values, so the existing tests
// work for the new optimization type. Specifically for the Ukm test:
// - Add a new metric under the Perfect Heuristics Ukm event, and log it when
// the feature is exercised.
// - Add a new test file to chrome/test/data/optimization_guide/ that
// exercises the optimization (when enabled). The test should also expose a
// `WaitForOptimizationToFinish()` async function that lets the Ukm test
// know when to destroy the WebContents, and test that the Ukm was logged
// correctly. See
// chrome/test/data/optimization_guide/delay-async-script-execution.html as
// an example.
class BlinkOptimizationGuideBrowserTest
: public BlinkOptimizationGuideBrowserTestBase,
public testing::WithParamInterface<
std::tuple<proto::OptimizationType, bool>> {
public:
void SetUpOnMainThread() override {
test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
BlinkOptimizationGuideBrowserTestBase::SetUpOnMainThread();
}
BlinkOptimizationGuideBrowserTest() {
std::vector<base::test::ScopedFeatureList::FeatureAndParams>
enabled_features{{features::kOptimizationHints, {{}}}};
std::vector<base::Feature> disabled_features;
// Initialize feature flags based on the optimization type.
switch (GetOptimizationType()) {
case proto::OptimizationType::DELAY_ASYNC_SCRIPT_EXECUTION:
if (IsFeatureFlagEnabled()) {
std::map<std::string, std::string> parameters;
parameters["delay_type"] = "use_optimization_guide";
enabled_features.emplace_back(
blink::features::kDelayAsyncScriptExecution, parameters);
} else {
disabled_features.push_back(
blink::features::kDelayAsyncScriptExecution);
}
break;
case proto::OptimizationType::DELAY_COMPETING_LOW_PRIORITY_REQUESTS:
if (IsFeatureFlagEnabled()) {
std::map<std::string, std::string> parameters;
parameters["until"] = "use_optimization_guide";
enabled_features.emplace_back(
blink::features::kDelayCompetingLowPriorityRequests, parameters);
} else {
disabled_features.push_back(
blink::features::kDelayCompetingLowPriorityRequests);
}
break;
default:
NOTREACHED();
break;
}
scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features,
disabled_features);
}
// Constructs a fake optimization metadata based on the optimization type.
OptimizationMetadata ConstructMetadata() const {
OptimizationMetadata optimization_guide_metadata;
switch (GetOptimizationType()) {
case proto::OptimizationType::DELAY_ASYNC_SCRIPT_EXECUTION: {
proto::DelayAsyncScriptExecutionMetadata metadata;
metadata.set_delay_type(
proto::PerfectHeuristicsDelayType::DELAY_TYPE_FINISHED_PARSING);
optimization_guide_metadata.SetAnyMetadataForTesting(metadata);
break;
}
case proto::OptimizationType::DELAY_COMPETING_LOW_PRIORITY_REQUESTS: {
proto::DelayCompetingLowPriorityRequestsMetadata metadata;
metadata.set_delay_type(
proto::PerfectHeuristicsDelayType::DELAY_TYPE_FIRST_PAINT);
metadata.set_priority_threshold(
proto::PriorityThreshold::PRIORITY_THRESHOLD_MEDIUM);
optimization_guide_metadata.SetAnyMetadataForTesting(metadata);
break;
}
default:
NOTREACHED();
break;
}
return optimization_guide_metadata;
}
// Returns the optimization type provided as the gtest parameter.
proto::OptimizationType GetOptimizationType() const {
return std::get<0>(GetParam());
}
// Returns true if the feature flag for the optimization type is enabled. If
// the optimization type doesn't have the feature flag, returns true. See
// comments on instantiation for details.
bool IsFeatureFlagEnabled() const { return std::get<1>(GetParam()); }
// Returns true if the hints for the optimization type is available.
bool CheckIfHintsAvailable(
blink::mojom::BlinkOptimizationGuideHints& hints) const {
switch (GetOptimizationType()) {
case proto::OptimizationType::DELAY_ASYNC_SCRIPT_EXECUTION:
if (hints.delay_async_script_execution_hints) {
EXPECT_EQ(blink::mojom::DelayAsyncScriptExecutionDelayType::
kFinishedParsing,
hints.delay_async_script_execution_hints->delay_type);
}
return !!hints.delay_async_script_execution_hints;
case proto::OptimizationType::DELAY_COMPETING_LOW_PRIORITY_REQUESTS:
using blink::mojom::DelayCompetingLowPriorityRequestsDelayType;
using blink::mojom::DelayCompetingLowPriorityRequestsPriorityThreshold;
if (hints.delay_competing_low_priority_requests_hints) {
EXPECT_EQ(
DelayCompetingLowPriorityRequestsDelayType::kFirstPaint,
hints.delay_competing_low_priority_requests_hints->delay_type);
EXPECT_EQ(DelayCompetingLowPriorityRequestsPriorityThreshold::kMedium,
hints.delay_competing_low_priority_requests_hints
->priority_threshold);
}
return !!hints.delay_competing_low_priority_requests_hints;
default:
NOTREACHED();
return false;
}
}
GURL GetExperimentSpecificURL() const {
switch (GetOptimizationType()) {
case proto::OptimizationType::DELAY_ASYNC_SCRIPT_EXECUTION:
return GetURLWithMockHost(
"/optimization_guide/delay-async-script-execution.html");
case proto::OptimizationType::DELAY_COMPETING_LOW_PRIORITY_REQUESTS:
return GetURLWithMockHost(
"/optimization_guide/delay-competing-low-priority-requests.html");
default:
NOTREACHED();
return GURL();
}
}
void CheckUkmEntryForOptimization() {
const auto& entries = test_ukm_recorder_->GetEntriesByName(
ukm::builders::PerfectHeuristics::kEntryName);
if (IsFeatureFlagEnabled()) {
switch (GetOptimizationType()) {
case proto::OptimizationType::DELAY_ASYNC_SCRIPT_EXECUTION:
EXPECT_EQ(1u, entries.size());
test_ukm_recorder_->ExpectEntryMetric(
entries.front(),
ukm::builders::PerfectHeuristics::
kdelay_async_script_execution_before_finished_parsingName,
1u);
break;
case proto::OptimizationType::DELAY_COMPETING_LOW_PRIORITY_REQUESTS:
EXPECT_EQ(1u, entries.size());
test_ukm_recorder_->ExpectEntryMetric(
entries.front(),
ukm::builders::PerfectHeuristics::
kDelayCompetingLowPriorityRequestsName,
1u);
break;
default:
NOTREACHED();
return;
}
} else {
EXPECT_EQ(0u, entries.size());
}
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
};
// Instantiates test cases for each optimization type.
INSTANTIATE_TEST_SUITE_P(
All,
BlinkOptimizationGuideBrowserTest,
testing::Combine(
// The optimization type.
testing::Values(
proto::OptimizationType::DELAY_ASYNC_SCRIPT_EXECUTION,
proto::OptimizationType::DELAY_COMPETING_LOW_PRIORITY_REQUESTS),
// Whether the feature flag for the optimization type is enabled.
testing::Bool()));
IN_PROC_BROWSER_TEST_P(BlinkOptimizationGuideBrowserTest, Basic) {
// Set up a fake optimization hints for simple.html.
OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile())
->AddHintForTesting(GetURLWithMockHost("/simple.html"),
GetOptimizationType(), ConstructMetadata());
// Navigation to the URL should see the hints as long as the optimization type
// is enabled.
ui_test_utils::NavigateToURL(browser(), GetURLWithMockHost("/simple.html"));
{
blink::mojom::BlinkOptimizationGuideHintsPtr hints =
GetCurrentInquirer()->GetHints();
if (IsFeatureFlagEnabled()) {
EXPECT_TRUE(CheckIfHintsAvailable(*hints));
} else {
EXPECT_FALSE(CheckIfHintsAvailable(*hints));
}
}
// Navigation to the different URL shouldn't see the hints.
ui_test_utils::NavigateToURL(browser(),
GetURLWithMockHost("/simple.html?different"));
{
blink::mojom::BlinkOptimizationGuideHintsPtr hints =
GetCurrentInquirer()->GetHints();
EXPECT_FALSE(CheckIfHintsAvailable(*hints));
}
// Navigation to the URL again should see the same hints as long as the
// optimization guide is enabled.
ui_test_utils::NavigateToURL(browser(), GetURLWithMockHost("/simple.html"));
{
blink::mojom::BlinkOptimizationGuideHintsPtr hints =
GetCurrentInquirer()->GetHints();
if (IsFeatureFlagEnabled()) {
EXPECT_TRUE(CheckIfHintsAvailable(*hints));
} else {
EXPECT_FALSE(CheckIfHintsAvailable(*hints));
}
}
}
IN_PROC_BROWSER_TEST_P(BlinkOptimizationGuideBrowserTest, NoMetadata) {
// Set up a fake optimization hints without metadata for simple.html.
OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile())
->AddHintForTesting(GetURLWithMockHost("/simple.html"),
GetOptimizationType(), base::nullopt);
// Navigation to the URL shouldn't see the hints.
ui_test_utils::NavigateToURL(browser(), GetURLWithMockHost("/simple.html"));
blink::mojom::BlinkOptimizationGuideHintsPtr hints =
GetCurrentInquirer()->GetHints();
EXPECT_FALSE(CheckIfHintsAvailable(*hints));
}
IN_PROC_BROWSER_TEST_P(BlinkOptimizationGuideBrowserTest, Ukm) {
OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile())
->AddHintForTesting(GetExperimentSpecificURL(), GetOptimizationType(),
ConstructMetadata());
EXPECT_TRUE(
ui_test_utils::NavigateToURL(browser(), GetExperimentSpecificURL()));
EXPECT_EQ("DONE", EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
"WaitForOptimizationToFinish()"));
blink::mojom::BlinkOptimizationGuideHintsPtr hints =
GetCurrentInquirer()->GetHints();
if (IsFeatureFlagEnabled()) {
EXPECT_TRUE(CheckIfHintsAvailable(*hints));
} else {
EXPECT_FALSE(CheckIfHintsAvailable(*hints));
}
// Force navigation to another page, which should force logging of histograms
// persisted at the end of the page load lifetime.
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
CheckUkmEntryForOptimization();
}
// Tests behavior when the optimization guide service is disabled.
class BlinkOptimizationGuideDisabledBrowserTest
: public BlinkOptimizationGuideBrowserTestBase {
public:
BlinkOptimizationGuideDisabledBrowserTest() {
// Disable the optimization guide service.
scoped_feature_list_.InitAndDisableFeature(features::kOptimizationHints);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(BlinkOptimizationGuideDisabledBrowserTest,
OptimizationGuideIsDisabled) {
// The optimization guide service shouldn't be available.
EXPECT_FALSE(OptimizationGuideKeyedServiceFactory::GetForProfile(
browser()->profile()));
ui_test_utils::NavigateToURL(browser(), GetURLWithMockHost("/simple.html"));
// Navigation started, but the web contents observer for the Blink
// optimization guide shouldn't be created.
EXPECT_FALSE(GetObserverForActiveWebContents());
}
} // namespace optimization_guide