blob: a088e2596a7e0db7bb0aa36e139bcaa2a14b08a4 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/timer/elapsed_timer.h"
#include "build/build_config.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/predictors/autocomplete_action_predictor.h"
#include "chrome/browser/predictors/autocomplete_action_predictor_factory.h"
#include "chrome/browser/preloading/chrome_preloading.h"
#include "chrome/browser/preloading/prefetch/search_prefetch/field_trial_settings.h"
#include "chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service.h"
#include "chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service_factory.h"
#include "chrome/browser/preloading/preloading_prefs.h"
#include "chrome/browser/preloading/prerender/prerender_manager.h"
#include "chrome/browser/preloading/scoped_prewarm_feature_list.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "chrome/test/base/platform_browser_test.h"
#include "chrome/test/base/search_test_utils.h"
#include "chrome/test/base/testing_profile.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_service.h"
#include "components/search_engines/template_url_data.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/navigation_handle_observer.h"
#include "content/public/test/preloading_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.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 "services/metrics/public/cpp/ukm_recorder.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/page_transition_types.h"
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/browser.h"
#endif
namespace {
using UkmEntry = ukm::TestUkmRecorder::HumanReadableUkmEntry;
using ukm::builders::Preloading_Attempt;
} // namespace
class OmniboxPrerenderBrowserTest : public PlatformBrowserTest {
public:
OmniboxPrerenderBrowserTest()
: prerender_helper_(base::BindRepeating(
&OmniboxPrerenderBrowserTest::GetActiveWebContents,
base::Unretained(this))) {}
void SetUp() override {
prerender_helper_.RegisterServerRequestMonitor(embedded_test_server());
PlatformBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
embedded_test_server()->ServeFilesFromDirectory(
base::PathService::CheckedGet(chrome::DIR_TEST_DATA));
test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
ukm_entry_builder_ =
std::make_unique<content::test::PreloadingAttemptUkmEntryBuilder>(
chrome_preloading_predictor::kOmniboxDirectURLInput);
ASSERT_TRUE(embedded_test_server()->Start());
}
void TearDownOnMainThread() override {
ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
}
content::WebContents* GetActiveWebContents() {
return chrome_test_utils::GetActiveWebContents(this);
}
content::test::PrerenderTestHelper& prerender_helper() {
return prerender_helper_;
}
Profile* GetProfile() {
#if BUILDFLAG(IS_ANDROID)
return chrome_test_utils::GetProfile(this);
#else
return browser()->profile();
#endif
}
predictors::AutocompleteActionPredictor* GetAutocompleteActionPredictor() {
Profile* profile = GetProfile();
return predictors::AutocompleteActionPredictorFactory::GetForProfile(
profile);
}
ukm::TestAutoSetUkmRecorder* test_ukm_recorder() {
return test_ukm_recorder_.get();
}
const content::test::PreloadingAttemptUkmEntryBuilder& ukm_entry_builder() {
return *ukm_entry_builder_;
}
private:
base::ScopedMockElapsedTimersForTest test_timer_;
content::test::PrerenderTestHelper prerender_helper_;
test::ScopedPrewarmFeatureList scoped_prewarm_feature_list_{
test::ScopedPrewarmFeatureList::PrewarmState::kDisabled};
std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
std::unique_ptr<content::test::PreloadingAttemptUkmEntryBuilder>
ukm_entry_builder_;
// Disable sampling of UKM preloading logs.
content::test::PreloadingConfigOverride preloading_config_override_;
};
// Tests that Prerender2 cannot be triggered when preload setting is disabled.
IN_PROC_BROWSER_TEST_F(OmniboxPrerenderBrowserTest, DisableNetworkPrediction) {
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
content::WebContents* web_contents = GetActiveWebContents();
// Disable network prediction.
PrefService* prefs = GetProfile()->GetPrefs();
prefetch::SetPreloadPagesState(prefs,
prefetch::PreloadPagesState::kNoPreloading);
ASSERT_EQ(prefetch::IsSomePreloadingEnabled(*prefs),
content::PreloadingEligibility::kPreloadingDisabled);
// Navigate to initial URL.
ASSERT_TRUE(content::NavigateToURL(web_contents, kInitialUrl));
// Attempt to prerender a direct URL input.
auto* predictor = GetAutocompleteActionPredictor();
ASSERT_TRUE(predictor);
GURL prerender_url = embedded_test_server()->GetURL("/simple.html");
predictor->StartPrerendering(prerender_url, *web_contents);
// Since preload setting is disabled, prerender shouldn't be triggered.
base::RunLoop().RunUntilIdle();
content::FrameTreeNodeId host_id =
prerender_helper().GetHostForUrl(prerender_url);
EXPECT_TRUE(host_id.is_null());
{
// Navigate to a different URL other than the prerender_url to flush the
// metrics.
GURL kUrl_a = embedded_test_server()->GetURL("a.com", "/title1.html");
ASSERT_TRUE(content::NavigateToURL(web_contents, kUrl_a));
ukm::SourceId ukm_source_id =
web_contents->GetPrimaryMainFrame()->GetPageUkmSourceId();
auto ukm_entries = test_ukm_recorder()->GetEntries(
Preloading_Attempt::kEntryName,
content::test::kPreloadingAttemptUkmMetrics);
EXPECT_EQ(ukm_entries.size(), 1u);
UkmEntry expected_entry = ukm_entry_builder().BuildEntry(
ukm_source_id, content::PreloadingType::kPrerender,
content::PreloadingEligibility::kPreloadingDisabled,
content::PreloadingHoldbackStatus::kUnspecified,
content::PreloadingTriggeringOutcome::kUnspecified,
content::PreloadingFailureReason::kUnspecified,
/*accurate=*/false);
EXPECT_EQ(ukm_entries[0], expected_entry)
<< content::test::ActualVsExpectedUkmEntryToString(ukm_entries[0],
expected_entry);
}
// Re-enable the setting.
prefetch::SetPreloadPagesState(
prefs, prefetch::PreloadPagesState::kStandardPreloading);
ASSERT_EQ(prefetch::IsSomePreloadingEnabled(*prefs),
content::PreloadingEligibility::kEligible);
content::test::PrerenderHostRegistryObserver registry_observer(*web_contents);
// Attempt to trigger prerendering again.
predictor->StartPrerendering(prerender_url, *web_contents);
// Since preload setting is enabled, prerender should be triggered
// successfully.
registry_observer.WaitForTrigger(prerender_url);
host_id = prerender_helper().GetHostForUrl(prerender_url);
EXPECT_TRUE(host_id);
{
// Navigate to prerender_url.
content::NavigationHandleObserver activation_observer(web_contents,
prerender_url);
ASSERT_TRUE(content::NavigateToURL(web_contents, prerender_url));
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
auto ukm_entries = test_ukm_recorder()->GetEntries(
Preloading_Attempt::kEntryName,
content::test::kPreloadingAttemptUkmMetrics);
// There should be 2 entries after we attempt Prerender again.
EXPECT_EQ(ukm_entries.size(), 2u);
UkmEntry expected_entry = ukm_entry_builder().BuildEntry(
ukm_source_id, content::PreloadingType::kPrerender,
content::PreloadingEligibility::kEligible,
content::PreloadingHoldbackStatus::kAllowed,
content::PreloadingTriggeringOutcome::kSuccess,
content::PreloadingFailureReason::kUnspecified,
/*accurate=*/true,
/*ready_time=*/base::ScopedMockElapsedTimersForTest::kMockElapsedTime);
EXPECT_EQ(ukm_entries[1], expected_entry)
<< content::test::ActualVsExpectedUkmEntryToString(ukm_entries[1],
expected_entry);
}
}
class PrerenderOmniboxSearchSuggestionBrowserTest
: public OmniboxPrerenderBrowserTest {
public:
void SetUp() override {
prerender_helper().RegisterServerRequestMonitor(&search_engine_server_);
PlatformBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
OmniboxPrerenderBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
search_engine_server_.SetSSLConfig(
net::test_server::EmbeddedTestServer::CERT_TEST_NAMES);
search_engine_server_.ServeFilesFromDirectory(
base::PathService::CheckedGet(chrome::DIR_TEST_DATA));
ASSERT_TRUE(search_engine_server_.Start());
SetUrlTemplate();
}
void SetNewUrlTemplate(const std::string& prerender_page_target) {
prerender_page_target_ = prerender_page_target;
SetUrlTemplate();
}
protected:
GURL GetCanonicalSearchURL(const GURL& prefetch_url) {
GURL canonical_search_url;
HasCanonicalPreloadingOmniboxSearchURL(prefetch_url,
chrome_test_utils::GetProfile(this),
&canonical_search_url);
return canonical_search_url;
}
void PrerenderQuery(const std::string& search_terms) {
auto* search_prefetch_service =
SearchPrefetchServiceFactory::GetForProfile(GetProfile());
AutocompleteMatch match = CreateSearchSuggestionMatch(search_terms);
auto* template_url_service =
TemplateURLServiceFactory::GetForProfile(GetProfile());
search_prefetch_service->CoordinatePrefetchWithPrerender(
match, GetActiveWebContents(), template_url_service,
GetCanonicalSearchURL(match.destination_url));
}
GURL GetSearchSuggestionUrl(const std::string& search_terms,
bool with_parameter) {
std::string url_template = prerender_page_target_ + "?q=$1$2&type=test";
return search_engine_server_.GetURL(
kSearchDomain,
base::ReplaceStringPlaceholders(
url_template, {search_terms, with_parameter ? "&pf=cs" : ""},
nullptr));
}
void InitializePrerenderManager() {
PrerenderManager::CreateForWebContents(GetActiveWebContents());
prerender_manager_ =
PrerenderManager::FromWebContents(GetActiveWebContents());
ASSERT_TRUE(prerender_manager_);
}
void PrerenderAndActivate(const std::string& search_terms) {
PrerenderQuery(search_terms);
GURL prerendered_url =
GetSearchSuggestionUrl(search_terms, /*with_parameter=*/false);
prerender_helper().WaitForPrerenderLoadCompletion(*GetActiveWebContents(),
prerendered_url);
prerender_helper().NavigatePrimaryPage(
prerendered_url,
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_GENERATED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR));
EXPECT_EQ(GetActiveWebContents()->GetLastCommittedURL(), prerendered_url);
}
PrerenderManager* prerender_manager() { return prerender_manager_; }
private:
AutocompleteMatch CreateSearchSuggestionMatch(
const std::string& search_terms) {
AutocompleteMatch match;
match.search_terms_args = std::make_unique<TemplateURLRef::SearchTermsArgs>(
base::UTF8ToUTF16(search_terms));
match.search_terms_args->original_query = base::UTF8ToUTF16(search_terms);
match.destination_url =
GetSearchSuggestionUrl(search_terms, /*with_parameter=*/true);
match.keyword = base::UTF8ToUTF16(search_terms);
match.RecordAdditionalInfo("should_prerender", "true");
return match;
}
void SetUrlTemplate() {
TemplateURLService* model = TemplateURLServiceFactory::GetForProfile(
chrome_test_utils::GetProfile(this));
ASSERT_TRUE(model);
search_test_utils::WaitForTemplateURLServiceToLoad(model);
ASSERT_TRUE(model->loaded());
TemplateURLData data;
data.SetShortName(kSearchDomain16);
data.SetKeyword(data.short_name());
data.SetURL(search_engine_server_
.GetURL(kSearchDomain,
prerender_page_target_ +
"?q={searchTerms}&{google:assistedQueryStats}{"
"google:prefetchSource}type=test")
.spec());
TemplateURL* template_url = model->Add(std::make_unique<TemplateURL>(data));
ASSERT_TRUE(template_url);
model->SetUserSelectedDefaultSearchProvider(template_url);
}
constexpr static char kSearchDomain[] = "a.test";
constexpr static char16_t kSearchDomain16[] = u"a.test";
raw_ptr<PrerenderManager, AcrossTasksDanglingUntriaged> prerender_manager_;
net::test_server::EmbeddedTestServer search_engine_server_{
net::test_server::EmbeddedTestServer::TYPE_HTTPS};
std::string prerender_page_target_ = "/title1.html";
};
class PrerenderOmniboxSearchSuggestionExpiryBrowserTest
: public PrerenderOmniboxSearchSuggestionBrowserTest {
public:
PrerenderOmniboxSearchSuggestionExpiryBrowserTest() {
feature_list_.InitWithFeaturesAndParameters(
{{kSearchPrefetchServicePrefetching,
{{"device_memory_threshold_MB", "0"}}}},
{});
}
// TODO(crbug.com/40285326): This fails with the field trial testing config.
void SetUpCommandLine(base::CommandLine* command_line) override {
PrerenderOmniboxSearchSuggestionBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch("disable-field-trial-config");
}
protected:
void PrerenderQueryAndWaitForExpiring(const std::string& search_terms) {
content::test::PrerenderHostRegistryObserver registry_observer(
*GetActiveWebContents());
PrerenderQuery(search_terms);
GURL prerendered_url =
GetSearchSuggestionUrl(search_terms, /*with_parameter=*/false);
registry_observer.WaitForTrigger(prerendered_url);
content::FrameTreeNodeId host_id =
prerender_helper().GetHostForUrl(prerendered_url);
ASSERT_TRUE(host_id);
content::test::PrerenderHostObserver prerender_observer(
*GetActiveWebContents(), host_id);
// The prerender will be destroyed automatically for expiry.
auto* search_prefetch_service =
SearchPrefetchServiceFactory::GetForProfile(GetProfile());
search_prefetch_service->FireAllExpiryTimerForTesting();
prerender_observer.WaitForDestroyed();
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Tests that an ongoing prerender which loads an SRP should be canceled
// automatically after the expiry duration.
IN_PROC_BROWSER_TEST_F(PrerenderOmniboxSearchSuggestionExpiryBrowserTest,
SearchPrerenderExpiry) {
base::HistogramTester histogram_tester;
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
ASSERT_TRUE(GetActiveWebContents());
ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl));
InitializePrerenderManager();
std::string search_query = "prerender2";
PrerenderQueryAndWaitForExpiring("prerender222");
histogram_tester.ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
"DefaultSearchEngine",
/*PrerenderFinalStatus::kTriggerDestroyed*/ 16, 1);
// The prediction should be treated as cancelled.
histogram_tester.ExpectUniqueSample(
internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine,
PrerenderPredictionStatus::kCancelled, 1);
}
// Tests that kCanceled is correctly recorded in the case that PrerenderManager
// receives a new suggestion. Note: kCancel should only recorded when
// PrerenderManager receives a new suggestion or on primary-page changed.
// Otherwise one prediction might be recorded twice.
IN_PROC_BROWSER_TEST_F(PrerenderOmniboxSearchSuggestionExpiryBrowserTest,
DifferentSuggestionAfterPrerenderExpired) {
base::HistogramTester histogram_tester;
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
ASSERT_TRUE(GetActiveWebContents());
ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl));
InitializePrerenderManager();
// Prerender the first query, and wait for it to be deleted.
PrerenderQueryAndWaitForExpiring("prerender222");
histogram_tester.ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
"DefaultSearchEngine",
/*PrerenderFinalStatus::kTriggerDestroyed*/ 16, 1);
// The prediction should be treated as cancelled.
histogram_tester.ExpectUniqueSample(
internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine,
PrerenderPredictionStatus::kCancelled, 1);
// Prerender another term and activate it.
PrerenderAndActivate("prerender233");
// The prediction is correct, so kHitFinished should be recorded.
histogram_tester.ExpectBucketCount(
internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine,
PrerenderPredictionStatus::kHitFinished, 1);
// Two predictions, two samples.
histogram_tester.ExpectTotalCount(
internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, 2);
}