blob: 8ac92ba22f9b5411a7fe2a36e72dfc4f7b5cdce7 [file] [log] [blame] [edit]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/omnibox/browser/autocomplete_controller.h"
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "base/json/json_reader.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/omnibox/browser/actions/omnibox_answer_action.h"
#include "components/omnibox/browser/autocomplete_controller.h"
#include "components/omnibox/browser/autocomplete_enums.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/autocomplete_match_test_util.h"
#include "components/omnibox/browser/autocomplete_match_type.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/omnibox/browser/fake_autocomplete_controller.h"
#include "components/omnibox/browser/fake_autocomplete_provider.h"
#include "components/omnibox/browser/fake_autocomplete_provider_client.h"
#include "components/omnibox/browser/fake_tab_matcher.h"
#include "components/omnibox/browser/keyword_provider.h"
#include "components/omnibox/browser/omnibox_field_trial.h"
#include "components/omnibox/browser/test_scheme_classifier.h"
#include "components/omnibox/common/omnibox_feature_configs.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/search_engines/enterprise/enterprise_search_manager.h"
#include "components/search_engines/template_url_data.h"
#include "components/search_engines/template_url_starter_pack_data.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/omnibox_proto/answer_type.pb.h"
#include "third_party/omnibox_proto/rich_answer_template.pb.h"
using ::testing::ElementsAre;
using ::testing::Pair;
using ::testing::WhenSorted;
class AutocompleteControllerTest : public testing::Test {
public:
AutocompleteControllerTest() : controller_(&task_environment_) {}
void SetUp() override {
EnterpriseSearchManager::RegisterProfilePrefs(pref_service()->registry());
}
void SetAutocompleteMatches(const std::vector<AutocompleteMatch>& matches) {
controller_.internal_result_.ClearMatches();
controller_.internal_result_.AppendMatches(matches);
}
void UpdateSearchboxStats() {
controller_.UpdateSearchboxStats(&controller_.internal_result_);
}
void UpdateShownInSession() {
controller_.UpdateShownInSession(&controller_.internal_result_);
}
void MaybeRemoveCompanyEntityImages() {
controller_.MaybeRemoveCompanyEntityImages(&controller_.internal_result_);
}
bool ImageURLAndImageDominantColorIsEmpty(size_t index) {
return controller_.internal_result_.match_at(index)->image_url.is_empty() &&
controller_.internal_result_.match_at(index)
->image_dominant_color.empty();
}
FakeAutocompleteProviderClient* provider_client() {
return static_cast<FakeAutocompleteProviderClient*>(
controller_.autocomplete_provider_client());
}
sync_preferences::TestingPrefServiceSyncable* pref_service() {
return static_cast<sync_preferences::TestingPrefServiceSyncable*>(
provider_client()->GetPrefs());
}
protected:
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
FakeAutocompleteController controller_;
};
TEST_F(AutocompleteControllerTest, UpdateShownInSessionOmitAsyncMatches) {
std::vector<AutocompleteMatch> matches;
AutocompleteInput input(u"abc", 3u, metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
input.set_omit_asynchronous_matches(true);
controller_.input_ = input;
matches.push_back(CreateSearchMatch(u"abc"));
SetAutocompleteMatches(matches);
UpdateShownInSession();
for (size_t i = 0; i < controller_.internal_result_.size(); i++) {
const auto* match = controller_.internal_result_.match_at(i);
ASSERT_FALSE(match->session);
}
}
TEST_F(AutocompleteControllerTest, UpdateShownInSessionTypedThenZeroPrefix) {
std::vector<AutocompleteMatch> matches;
AutocompleteInput typed_input(u"abc", 3u, metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
controller_.input_ = typed_input;
matches.push_back(CreateSearchMatch(u"abc"));
SetAutocompleteMatches(matches);
UpdateSearchboxStats();
UpdateShownInSession();
for (size_t i = 0; i < controller_.internal_result_.size(); i++) {
const auto* match = controller_.internal_result_.match_at(i);
ASSERT_FALSE(match->session->zero_prefix_suggestions_shown_in_session);
ASSERT_FALSE(
match->session->zero_prefix_search_suggestions_shown_in_session);
ASSERT_FALSE(match->session->zero_prefix_url_suggestions_shown_in_session);
ASSERT_TRUE(match->session->typed_suggestions_shown_in_session);
ASSERT_TRUE(match->session->typed_search_suggestions_shown_in_session);
ASSERT_FALSE(match->session->typed_url_suggestions_shown_in_session);
}
matches.push_back(
CreateHistoryURLMatch(/*destination_url=*/"https://www.abc.com/"));
SetAutocompleteMatches(matches);
UpdateSearchboxStats();
UpdateShownInSession();
for (size_t i = 0; i < controller_.internal_result_.size(); i++) {
const auto* match = controller_.internal_result_.match_at(i);
ASSERT_FALSE(match->session->zero_prefix_suggestions_shown_in_session);
ASSERT_FALSE(
match->session->zero_prefix_search_suggestions_shown_in_session);
ASSERT_FALSE(match->session->zero_prefix_url_suggestions_shown_in_session);
ASSERT_TRUE(match->session->typed_suggestions_shown_in_session);
ASSERT_TRUE(match->session->typed_search_suggestions_shown_in_session);
ASSERT_TRUE(match->session->typed_url_suggestions_shown_in_session);
}
matches.clear();
AutocompleteInput zero_prefix_input(
u"", 0u, metrics::OmniboxEventProto::OTHER, TestSchemeClassifier());
zero_prefix_input.set_focus_type(
metrics::OmniboxFocusType::INTERACTION_FOCUS);
controller_.input_ = zero_prefix_input;
matches.push_back(CreateZeroPrefixSearchMatch(u"abc"));
SetAutocompleteMatches(matches);
UpdateSearchboxStats();
UpdateShownInSession();
for (size_t i = 0; i < controller_.internal_result_.size(); i++) {
const auto* match = controller_.internal_result_.match_at(i);
ASSERT_TRUE(match->session->zero_prefix_suggestions_shown_in_session);
ASSERT_TRUE(
match->session->zero_prefix_search_suggestions_shown_in_session);
ASSERT_FALSE(match->session->zero_prefix_url_suggestions_shown_in_session);
ASSERT_TRUE(match->session->typed_suggestions_shown_in_session);
ASSERT_TRUE(match->session->typed_search_suggestions_shown_in_session);
ASSERT_TRUE(match->session->typed_url_suggestions_shown_in_session);
}
matches.push_back(CreateHistoryURLMatch(
/*destination_url=*/"https://www.abc.com/", /*is_zero_prefix=*/true));
SetAutocompleteMatches(matches);
UpdateSearchboxStats();
UpdateShownInSession();
for (size_t i = 0; i < controller_.internal_result_.size(); i++) {
const auto* match = controller_.internal_result_.match_at(i);
ASSERT_TRUE(match->session->zero_prefix_suggestions_shown_in_session);
ASSERT_TRUE(
match->session->zero_prefix_search_suggestions_shown_in_session);
ASSERT_TRUE(match->session->zero_prefix_url_suggestions_shown_in_session);
ASSERT_TRUE(match->session->typed_suggestions_shown_in_session);
ASSERT_TRUE(match->session->typed_search_suggestions_shown_in_session);
ASSERT_TRUE(match->session->typed_url_suggestions_shown_in_session);
}
}
TEST_F(AutocompleteControllerTest, UpdateShownInSessionZeroPrefixThenTyped) {
std::vector<AutocompleteMatch> matches;
AutocompleteInput zero_prefix_input(
u"", 0u, metrics::OmniboxEventProto::OTHER, TestSchemeClassifier());
zero_prefix_input.set_focus_type(
metrics::OmniboxFocusType::INTERACTION_FOCUS);
controller_.input_ = zero_prefix_input;
matches.push_back(CreateZeroPrefixSearchMatch(u"abc"));
SetAutocompleteMatches(matches);
UpdateSearchboxStats();
UpdateShownInSession();
for (size_t i = 0; i < controller_.internal_result_.size(); i++) {
const auto* match = controller_.internal_result_.match_at(i);
ASSERT_TRUE(match->session->zero_prefix_suggestions_shown_in_session);
ASSERT_TRUE(
match->session->zero_prefix_search_suggestions_shown_in_session);
ASSERT_FALSE(match->session->zero_prefix_url_suggestions_shown_in_session);
ASSERT_FALSE(match->session->typed_suggestions_shown_in_session);
ASSERT_FALSE(match->session->typed_search_suggestions_shown_in_session);
ASSERT_FALSE(match->session->typed_url_suggestions_shown_in_session);
}
matches.push_back(CreateHistoryURLMatch(
/*destination_url=*/"https://www.abc.com/", /*is_zero_prefix=*/true));
SetAutocompleteMatches(matches);
UpdateSearchboxStats();
UpdateShownInSession();
for (size_t i = 0; i < controller_.internal_result_.size(); i++) {
const auto* match = controller_.internal_result_.match_at(i);
ASSERT_TRUE(match->session->zero_prefix_suggestions_shown_in_session);
ASSERT_TRUE(
match->session->zero_prefix_search_suggestions_shown_in_session);
ASSERT_TRUE(match->session->zero_prefix_url_suggestions_shown_in_session);
ASSERT_FALSE(match->session->typed_suggestions_shown_in_session);
ASSERT_FALSE(match->session->typed_search_suggestions_shown_in_session);
ASSERT_FALSE(match->session->typed_url_suggestions_shown_in_session);
}
matches.clear();
AutocompleteInput typed_input(u"abc", 3u, metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
controller_.input_ = typed_input;
matches.push_back(CreateSearchMatch(u"abc"));
SetAutocompleteMatches(matches);
UpdateSearchboxStats();
UpdateShownInSession();
for (size_t i = 0; i < controller_.internal_result_.size(); i++) {
const auto* match = controller_.internal_result_.match_at(i);
ASSERT_TRUE(match->session->zero_prefix_suggestions_shown_in_session);
ASSERT_TRUE(
match->session->zero_prefix_search_suggestions_shown_in_session);
ASSERT_TRUE(match->session->zero_prefix_url_suggestions_shown_in_session);
ASSERT_TRUE(match->session->typed_suggestions_shown_in_session);
ASSERT_TRUE(match->session->typed_search_suggestions_shown_in_session);
ASSERT_FALSE(match->session->typed_url_suggestions_shown_in_session);
}
matches.push_back(
CreateHistoryURLMatch(/*destination_url=*/"https://www.abc.com/"));
SetAutocompleteMatches(matches);
UpdateSearchboxStats();
UpdateShownInSession();
for (size_t i = 0; i < controller_.internal_result_.size(); i++) {
const auto* match = controller_.internal_result_.match_at(i);
ASSERT_TRUE(match->session->zero_prefix_suggestions_shown_in_session);
ASSERT_TRUE(
match->session->zero_prefix_search_suggestions_shown_in_session);
ASSERT_TRUE(match->session->zero_prefix_url_suggestions_shown_in_session);
ASSERT_TRUE(match->session->typed_suggestions_shown_in_session);
ASSERT_TRUE(match->session->typed_search_suggestions_shown_in_session);
ASSERT_TRUE(match->session->typed_url_suggestions_shown_in_session);
}
}
TEST_F(AutocompleteControllerTest, RemoveCompanyEntityImage) {
base::HistogramTester histogram_tester;
std::vector<AutocompleteMatch> matches;
// To ablate entity image the historical match must be the first and the
// company entity can be in any other slot.
matches.push_back(
CreateHistoryURLMatch(/*destination_url=*/"https://www.wellsfargo.com/"));
matches.push_back(CreateSearchMatch());
matches.push_back(
CreateCompanyEntityMatch(/*website_uri=*/"https://www.wellsfargo.com/"));
SetAutocompleteMatches(matches);
ASSERT_FALSE(ImageURLAndImageDominantColorIsEmpty(/*index=*/2));
MaybeRemoveCompanyEntityImages();
ASSERT_TRUE(ImageURLAndImageDominantColorIsEmpty(/*index=*/2));
histogram_tester.ExpectBucketCount("Omnibox.CompanyEntityImageAblated", true,
1);
}
TEST_F(AutocompleteControllerTest, CompanyEntityImageNotRemoved) {
// History match is not the first suggestion. Entity's image should not be
// removed.
{
base::HistogramTester histogram_tester;
std::vector<AutocompleteMatch> matches;
matches.push_back(CreateCompanyEntityMatch(
/*website_uri=*/"https://www.wellsfargo.com/"));
matches.push_back(CreateHistoryURLMatch(
/*destination_url=*/"https://www.wellsfargo.com/"));
matches.push_back(CreateSearchMatch());
SetAutocompleteMatches(matches);
ASSERT_FALSE(ImageURLAndImageDominantColorIsEmpty(/*index=*/0));
MaybeRemoveCompanyEntityImages();
// The entity's image_url should remain as is.
ASSERT_FALSE(ImageURLAndImageDominantColorIsEmpty(/*index=*/0));
histogram_tester.ExpectBucketCount("Omnibox.CompanyEntityImageAblated",
false, 1);
}
// History match is the first suggestion, but there isn't a matching company
// entity.
{
base::HistogramTester histogram_tester;
std::vector<AutocompleteMatch> matches;
matches.push_back(CreateHistoryURLMatch(
/*destination_url=*/"https://www.wellsfargo.com/"));
matches.push_back(
CreateCompanyEntityMatch(/*website_uri=*/"https://www.weather.com/"));
SetAutocompleteMatches(matches);
ASSERT_FALSE(ImageURLAndImageDominantColorIsEmpty(/*index=*/1));
MaybeRemoveCompanyEntityImages();
// The entity's image_url should remain as is.
ASSERT_FALSE(ImageURLAndImageDominantColorIsEmpty(/*index=*/1));
histogram_tester.ExpectBucketCount("Omnibox.CompanyEntityImageAblated",
false, 1);
}
// There is a company entity, but no history match.
{
base::HistogramTester histogram_tester;
std::vector<AutocompleteMatch> matches;
matches.push_back(CreateSearchMatch());
matches.push_back(
CreateCompanyEntityMatch(/*website_uri=*/"https://www.weather.com/"));
SetAutocompleteMatches(matches);
ASSERT_FALSE(ImageURLAndImageDominantColorIsEmpty(/*index=*/1));
MaybeRemoveCompanyEntityImages();
// The entity's image_url should remain as is.
ASSERT_FALSE(ImageURLAndImageDominantColorIsEmpty(/*index=*/1));
histogram_tester.ExpectBucketCount("Omnibox.CompanyEntityImageAblated",
false, 1);
}
}
// Desktop has some special handling for bare '@' inputs.
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
TEST_F(AutocompleteControllerTest, FilterMatchesForInstantKeywordWithBareAt) {
SetAutocompleteMatches({
CreateSearchMatch(u"@"),
CreateCompanyEntityMatch("https://example.com"),
CreateHistoryURLMatch("https://example.com"),
CreateStarterPackMatch(u"@bookmarks"),
CreateStarterPackMatch(u"@history"),
CreateStarterPackMatch(u"@tabs"),
CreateFeaturedEnterpriseSearch(u"@work"),
});
AutocompleteInput input(u"@", 1u, metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
controller_.MaybeCleanSuggestionsForKeywordMode(
input, &controller_.internal_result_);
EXPECT_EQ(controller_.internal_result_.size(), 5u);
EXPECT_TRUE(std::all_of(
controller_.internal_result_.begin(), controller_.internal_result_.end(),
[](const auto& match) {
return match.type == AutocompleteMatchType::STARTER_PACK ||
match.type ==
AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH ||
match.contents == u"@";
}));
}
#endif
TEST_F(AutocompleteControllerTest, UpdateResult_SyncAnd2Async) {
auto sync_match = CreateSearchMatch("sync", true, 1300);
auto async_match1 = CreateSearchMatch("async_1", true, 1200);
auto async_match2 = CreateSearchMatch("async_2", true, 1250);
{
SCOPED_TRACE("Sync pass.");
EXPECT_THAT(controller_.SimulateAutocompletePass(true, false, {sync_match}),
testing::ElementsAreArray({
"sync",
}));
}
{
SCOPED_TRACE("1st async pass.");
EXPECT_THAT(controller_.SimulateAutocompletePass(
false, false, {sync_match, async_match1}),
testing::ElementsAreArray({
"sync",
"async_1",
}));
}
{
SCOPED_TRACE(
"Last async pass. Verify the correct matches are shown ranked by "
"relevance.");
EXPECT_THAT(controller_.SimulateAutocompletePass(
false, true, {async_match1, async_match2}),
testing::ElementsAreArray({
"async_2",
"async_1",
}));
}
}
TEST_F(AutocompleteControllerTest, UpdateResult_TransferringOldMatches) {
auto pass1_match1 = CreateSearchMatch("pass1_match1", true, 1300);
auto pass1_match2 = CreateSearchMatch("pass1_match2", true, 1200);
auto pass1_match3 = CreateSearchMatch("pass1_match3", true, 1100);
auto pass2_match1 = CreateSearchMatch("pass2_match1", true, 1000);
auto pass3_match2 = CreateSearchMatch("pass3_match2", true, 900);
auto pass3_match3 = CreateSearchMatch("pass3_match3", true, 800);
auto pass3_match4 = CreateSearchMatch("pass3_match4", true, 700);
auto pass4_match1 = CreateSearchMatch("pass4_match1", true, 600);
auto pass5_match1 = CreateSearchMatch("pass5_match1", true, 500);
EXPECT_THAT(controller_.SimulateAutocompletePass(
true, false, {pass1_match1, pass1_match2, pass1_match3}),
testing::ElementsAreArray({
"pass1_match1",
"pass1_match2",
"pass1_match3",
}));
// # of matches decreased from 3 to 2. So 1 match should be transferred. The
// lowest ranked match should be transferred. It should keep its score and be
// ranked above the new non-transferred match.
EXPECT_THAT(controller_.SimulateAutocompletePass(
false, false, {pass1_match1, pass2_match1}),
testing::ElementsAreArray({
"pass1_match1",
"pass1_match3",
"pass2_match1",
}));
// # of matches remained 3. So no matches should be transferred.
EXPECT_THAT(controller_.SimulateAutocompletePass(
false, false, {pass3_match2, pass3_match3, pass3_match4}),
testing::ElementsAreArray({
"pass3_match2",
"pass3_match3",
"pass3_match4",
}));
// Transferred matches should not be allowed to be default.
EXPECT_THAT(
controller_.SimulateAutocompletePass(false, false, {pass4_match1}),
testing::ElementsAreArray({
"pass4_match1",
"pass3_match3",
"pass3_match4",
}));
// Lowest ranked match should be transferred. But old matches still present
// shouldn't count, and the next lowest match should be transferred.
// Transferred match scores should be capped to the new default, therefore,
// the transferred `pass3_match3` should be demoted to last even though it
// originally outscored `pass3_match4`.
EXPECT_THAT(controller_.SimulateAutocompletePass(
false, false, {pass4_match1, pass3_match4}),
testing::ElementsAreArray({
"pass4_match1",
"pass3_match4",
"pass3_match3",
}));
// Sync updates should also transfer old matches. Lowest ranked, not
// necessarily lowest scored, match should be transferred.
EXPECT_THAT(controller_.SimulateAutocompletePass(true, false, {pass5_match1}),
testing::ElementsAreArray({
"pass5_match1",
"pass3_match3",
"pass3_match4",
}));
// Expire updates should not transfer old matches.
EXPECT_THAT(controller_.SimulateExpirePass(), testing::ElementsAreArray({
"pass5_match1",
}));
// Async updates after the expire update should transfer matches.
EXPECT_THAT(controller_.SimulateAutocompletePass(false, false, {}),
testing::ElementsAreArray({
"pass5_match1",
}));
// The last async pass shouldn't transfer matches.
EXPECT_THAT(controller_.SimulateAutocompletePass(true, true, {}),
testing::ElementsAreArray<std::string>({}));
}
TEST_F(AutocompleteControllerTest, UpdateResult_PreservingDefault) {
auto match1 = CreateSearchMatch("match1", true, 100);
auto match2 = CreateSearchMatch("match2", true, 200);
auto match3 = CreateSearchMatch("match3", true, 300);
auto match4 = CreateSearchMatch("match4", true, 400);
auto match5 = CreateSearchMatch("match5", true, 500);
auto match6 = CreateSearchMatch("match6", true, 600);
auto match7 = CreateSearchMatch("match7", true, 700);
auto match8 = CreateSearchMatch("match8", true, 800);
auto match9 = CreateSearchMatch("match9", true, 900);
{
SCOPED_TRACE("Load a default suggestion.");
EXPECT_THAT(controller_.SimulateAutocompletePass(true, true, {match1}),
testing::ElementsAreArray({
"match1",
}));
}
{
SCOPED_TRACE("Don't preserve default on sync pass with short inputs.");
EXPECT_THAT(controller_.SimulateAutocompletePass(
true, false, {match1, match2},
FakeAutocompleteController::CreateInput(u"x")),
testing::ElementsAreArray({
"match2",
"match1",
}));
}
{
SCOPED_TRACE("Preserve default on async pass with short inputs.");
EXPECT_THAT(
controller_.SimulateAutocompletePass(false, false, {match2, match3}),
testing::ElementsAreArray({
"match2",
"match3",
}));
// Preserve default on last async pass with short inputs. Preserve default
}
{
SCOPED_TRACE("across multiple passes.");
EXPECT_THAT(
controller_.SimulateAutocompletePass(false, true, {match2, match4}),
testing::ElementsAreArray({
"match2",
"match4",
}));
// Preserve default on sync pass with long inputs. Preserve default across
}
{
SCOPED_TRACE("multiple inputs.");
EXPECT_THAT(
controller_.SimulateAutocompletePass(true, false, {match2, match5}),
testing::ElementsAreArray({
"match2",
"match5",
}));
}
{
SCOPED_TRACE("Preserve default on async pass with long inputs.");
EXPECT_THAT(
controller_.SimulateAutocompletePass(false, false, {match2, match6}),
testing::ElementsAreArray({
"match2",
"match6",
}));
}
{
SCOPED_TRACE("Don't preserve default if it's transferred.");
EXPECT_THAT(
controller_.SimulateAutocompletePass(false, false, {match7, match8}),
testing::ElementsAreArray({
"match8",
"match7",
}));
}
{
SCOPED_TRACE("Preserve default on last async pass with long inputs.");
EXPECT_THAT(
controller_.SimulateAutocompletePass(false, true, {match8, match9}),
testing::ElementsAreArray({
"match8",
"match9",
}));
}
}
TEST_F(AutocompleteControllerTest, UpdateResult_Ranking) {
// Higher scored suggestions are ranked higher.
// Clear results between each test to avoid default preserving applying.
EXPECT_THAT(controller_.SimulateCleanAutocompletePass({
CreateSearchMatch("500", true, 500),
CreateSearchMatch("800", true, 800),
}),
testing::ElementsAreArray({
"800",
"500",
}));
// Default suggestion must be allowed to be default.
EXPECT_THAT(controller_.SimulateCleanAutocompletePass({
CreateSearchMatch("500", true, 500),
CreateSearchMatch("800", false, 800),
}),
testing::ElementsAreArray({
"500",
"800",
}));
// Android and iOS don't use the same grouping logic as desktop
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
// Searches should be grouped above non-shortcut-boosted URLs.
EXPECT_THAT(controller_.SimulateCleanAutocompletePass({
CreateSearchMatch("search900", true, 900),
CreateHistoryUrlMlScoredMatch("history800", true, 800, 1),
CreateSearchMatch("search700", true, 700),
CreateHistoryUrlMlScoredMatch("history600", true, 600, 1),
CreateSearchMatch("search500", true, 500),
CreateHistoryUrlMlScoredMatch("history400", true, 400, 1),
}),
testing::ElementsAreArray({
"search900",
"search700",
"search500",
"history800",
"history600",
"history400",
}));
// Default can be a non-search if it's scored higher than all the searches.
EXPECT_THAT(controller_.SimulateCleanAutocompletePass({
CreateSearchMatch("search900", true, 900),
CreateHistoryUrlMlScoredMatch("history800", true, 800, 1),
CreateSearchMatch("search700", true, 700),
CreateHistoryUrlMlScoredMatch("history600", true, 600, 1),
CreateSearchMatch("search500", true, 500),
CreateHistoryUrlMlScoredMatch("history400", true, 400, 1),
CreateHistoryUrlMlScoredMatch("history1000", true, 1000, 1),
}),
testing::ElementsAreArray({
"history1000",
"search900",
"search700",
"search500",
"history800",
"history600",
"history400",
}));
// Shortcut boosting is re-distributed when ML Scoring is enabled. That is
// tested in the `MlRanking` test below.
OmniboxFieldTrial::ScopedMLConfigForTesting scoped_config;
if (!scoped_config.GetMLConfig().ml_url_scoring) {
// Shortcut boosted suggestions should be ranked above searches, even if
// they're scored lower.
EXPECT_THAT(controller_.SimulateCleanAutocompletePass({
CreateHistoryUrlMlScoredMatch("history800", true, 800, 1),
CreateHistoryUrlMlScoredMatch("history850", true, 850, 1),
CreateSearchMatch("search700", true, 700),
CreateSearchMatch("search750", true, 750),
CreateBoostedShortcutMatch("shortcut600", 600, 1),
CreateBoostedShortcutMatch("shortcut650", 650, 1),
}),
testing::ElementsAreArray({
"history850",
"shortcut650",
"shortcut600",
"search750",
"search700",
"history800",
}));
}
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
}
TEST_F(AutocompleteControllerTest, UpdateResult_ZPSEnabledAndShownInSession) {
// Populate TemplateURLService with a keyword.
TemplateURLData turl_data;
turl_data.SetShortName(u"Keyword");
turl_data.SetKeyword(u"keyword");
turl_data.SetURL("https://google.com/search?q={searchTerms}");
controller_.template_url_service_->Add(
std::make_unique<TemplateURL>(turl_data));
// Create a zero-suggest input.
auto zps_input = FakeAutocompleteController::CreateInput(u"");
zps_input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS);
{
SCOPED_TRACE("Zero-prefix suggestions are offered synchronously");
EXPECT_THAT(controller_.SimulateAutocompletePass(
/*sync=*/true, /*done=*/false,
{
CreatePersonalizedZeroPrefixMatch("zps_1", 1450),
CreatePersonalizedZeroPrefixMatch("zps_2", 1449),
},
zps_input),
testing::ElementsAreArray({
"zps_1",
"zps_2",
}));
// Whether zero-suggest was enabled and the number of zero-prefix
// suggestions shown in the session are updated in the internal result set.
EXPECT_TRUE(controller_.internal_result_.zero_prefix_enabled_in_session());
EXPECT_EQ(controller_.internal_result_
.num_zero_prefix_suggestions_shown_in_session(),
2u);
// Published result set does not get the session data.
EXPECT_FALSE(
controller_.published_result_.zero_prefix_enabled_in_session());
EXPECT_EQ(controller_.published_result_
.num_zero_prefix_suggestions_shown_in_session(),
0u);
// Published matches contain the relevant session data in searchboxstats.
EXPECT_TRUE(controller_.published_result_.match_at(0)
->search_terms_args->searchbox_stats.zero_prefix_enabled());
EXPECT_EQ(controller_.published_result_.match_at(0)
->search_terms_args->searchbox_stats
.num_zero_prefix_suggestions_shown(),
2u);
}
{
SCOPED_TRACE("More zero-prefix suggestions are offered asynchronously");
EXPECT_THAT(controller_.SimulateAutocompletePass(
/*sync=*/false, /*done=*/false,
{
CreatePersonalizedZeroPrefixMatch("zps_1", 1450),
CreatePersonalizedZeroPrefixMatch("zps_2", 1449),
CreatePersonalizedZeroPrefixMatch("zps_3", 1448),
CreatePersonalizedZeroPrefixMatch("zps_4", 1447),
},
zps_input),
testing::ElementsAreArray({
"zps_1",
"zps_2",
"zps_3",
"zps_4",
}));
// If zero-prefix suggestions are offered multiple times in the session, the
// most recent count is logged.
EXPECT_TRUE(controller_.internal_result_.zero_prefix_enabled_in_session());
EXPECT_EQ(controller_.internal_result_
.num_zero_prefix_suggestions_shown_in_session(),
4u);
// Published result set does not get the session data.
EXPECT_FALSE(
controller_.published_result_.zero_prefix_enabled_in_session());
EXPECT_EQ(controller_.published_result_
.num_zero_prefix_suggestions_shown_in_session(),
0u);
// Published matches contain the relevant session data in searchboxstats.
EXPECT_TRUE(controller_.published_result_.match_at(0)
->search_terms_args->searchbox_stats.zero_prefix_enabled());
EXPECT_EQ(controller_.published_result_.match_at(0)
->search_terms_args->searchbox_stats
.num_zero_prefix_suggestions_shown(),
4u);
}
{
SCOPED_TRACE("Stop with clear_result=false is called due to user idleness");
controller_.Stop(AutocompleteStopReason::kInteraction);
// Stop with clear_result=false does not clear the internal result set and
// does not notify `OnResultChanged()`.
EXPECT_FALSE(controller_.internal_result_.empty());
EXPECT_FALSE(controller_.published_result_.empty());
// Whether zero-suggest was enabled and the number of zero-prefix
// suggestions shown in the session are unchanged in the internal result
// set.
EXPECT_TRUE(controller_.internal_result_.zero_prefix_enabled_in_session());
EXPECT_EQ(controller_.internal_result_
.num_zero_prefix_suggestions_shown_in_session(),
4u);
// Published result set does not get the session data.
EXPECT_FALSE(
controller_.published_result_.zero_prefix_enabled_in_session());
EXPECT_EQ(controller_.published_result_
.num_zero_prefix_suggestions_shown_in_session(),
0u);
// Published matches contain the relevant session data in searchboxstats.
EXPECT_TRUE(controller_.published_result_.match_at(0)
->search_terms_args->searchbox_stats.zero_prefix_enabled());
EXPECT_EQ(controller_.published_result_.match_at(0)
->search_terms_args->searchbox_stats
.num_zero_prefix_suggestions_shown(),
4u);
}
{
SCOPED_TRACE("Prefix suggestions are offered synchronously");
EXPECT_THAT(controller_.SimulateAutocompletePass(
/*sync=*/true, /*done=*/true,
{
CreateSearchMatch("search_1", true, 900),
}),
testing::ElementsAreArray({
"search_1",
}));
// Whether zero-suggest was enabled and the number of zero-prefix
// suggestions shown in the session are unchanged in the internal result
// set.
EXPECT_TRUE(controller_.internal_result_.zero_prefix_enabled_in_session());
EXPECT_EQ(controller_.internal_result_
.num_zero_prefix_suggestions_shown_in_session(),
4u);
// Published result set does not get the session data.
EXPECT_FALSE(
controller_.published_result_.zero_prefix_enabled_in_session());
EXPECT_EQ(controller_.published_result_
.num_zero_prefix_suggestions_shown_in_session(),
0u);
// Published matches contain the relevant session data in searchboxstats.
EXPECT_TRUE(controller_.published_result_.match_at(0)
->search_terms_args->searchbox_stats.zero_prefix_enabled());
EXPECT_EQ(controller_.published_result_.match_at(0)
->search_terms_args->searchbox_stats
.num_zero_prefix_suggestions_shown(),
4u);
}
{
SCOPED_TRACE("Stop with clear_result=true is called due to popup closing");
controller_.Stop(AutocompleteStopReason::kClobbered);
// Stop with clear_result=true clears the internal result set and notifies
// `OnResultChanged()`.
EXPECT_TRUE(controller_.internal_result_.empty());
EXPECT_TRUE(controller_.published_result_.empty());
// Whether zero-suggest was enabled and the number of zero-prefix
// suggestions shown in the session are reset in the internal result set.
EXPECT_FALSE(controller_.internal_result_.zero_prefix_enabled_in_session());
EXPECT_EQ(controller_.internal_result_
.num_zero_prefix_suggestions_shown_in_session(),
0u);
// Published result set does not get the session data.
EXPECT_FALSE(
controller_.published_result_.zero_prefix_enabled_in_session());
EXPECT_EQ(controller_.published_result_
.num_zero_prefix_suggestions_shown_in_session(),
0u);
}
}
// Android and iOS aren't ready for ML and won't pass this test because they
// have their own grouping code.
#if BUILDFLAG(BUILD_WITH_TFLITE_LIB) && !BUILDFLAG(IS_ANDROID) && \
!BUILDFLAG(IS_IOS)
TEST_F(AutocompleteControllerTest, MlRanking) {
OmniboxFieldTrial::ScopedMLConfigForTesting scoped_ml_config;
scoped_ml_config.GetMLConfig().ml_url_scoring = true;
scoped_ml_config.GetMLConfig().url_scoring_model = true;
EXPECT_THAT(controller_.SimulateCleanAutocompletePass({}),
testing::ElementsAre());
// Even if ML ranks a URL 0, it should still use traditional scores.
EXPECT_THAT(controller_.SimulateCleanAutocompletePass({
CreateHistoryUrlMlScoredMatch("history", true, 1400, 0),
CreateSearchMatch("search", true, 1300),
}),
testing::ElementsAreArray({
"history",
"search",
}));
// Simple case of redistributing ranking among only URLs.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
CreateHistoryUrlMlScoredMatch("history 1350 .5", true, 1350, .5),
CreateSearchMatch("search 1400", false, 1400),
CreateSearchMatch("search 800", true, 800),
CreateSearchMatch("search 600", false, 600),
CreateHistoryUrlMlScoredMatch("history 1200 .9", true, 1200, .9),
CreateHistoryUrlMlScoredMatch("history 1100 .1", false, 1100, .1),
CreateHistoryUrlMlScoredMatch("history 500 .2", true, 500, .2),
}),
testing::ElementsAreArray({
"history 1200 .9",
"search 1400",
"search 800",
"search 600",
"history 1350 .5",
"history 500 .2",
"history 1100 .1",
}));
// When multiple URL suggestions have been assigned the same score by the ML
// model, those suggestions which were top-ranked according to legacy scoring
// should continue to be top-ranked once ML scoring has run.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
CreateHistoryUrlMlScoredMatch("history A 1350 .2", true, 1350, .2),
CreateHistoryUrlMlScoredMatch("history B 1200 .2", true, 1200, .2),
CreateHistoryUrlMlScoredMatch("history C 1100 .2", false, 1100, .2),
CreateHistoryUrlMlScoredMatch("history D 300 .2", true, 300, .2),
CreateHistoryUrlMlScoredMatch("history E 200 .2", true, 200, .2),
CreateHistoryUrlMlScoredMatch("history F 100 .2", true, 100, .2),
}),
testing::ElementsAreArray({
"history A 1350 .2",
"history B 1200 .2",
"history C 1100 .2",
"history D 300 .2",
"history E 200 .2",
"history F 100 .2",
}));
// Can change the default suggestion from 1 history to another.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
CreateHistoryUrlMlScoredMatch("history 1400 .5", true, 1400, .5),
CreateSearchMatch("search", true, 1300),
CreateHistoryUrlMlScoredMatch("history 1200 .9", true, 1200, .9),
}),
testing::ElementsAreArray({
"history 1200 .9",
"search",
"history 1400 .5",
}));
// Can change the default from search to history.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
CreateSearchMatch("search 1300", true, 1300),
CreateHistoryUrlMlScoredMatch("history 1400 .5", false, 1400, .5),
CreateHistoryUrlMlScoredMatch("history 1200 .9", true, 1200, .9),
}),
testing::ElementsAreArray({
"history 1200 .9",
"search 1300",
"history 1400 .5",
}));
// Can change the default from history to search.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
CreateHistoryUrlMlScoredMatch("history 1400 .5", true, 1400, .5),
CreateSearchMatch("search 1300", true, 1300),
CreateHistoryUrlMlScoredMatch("history 1200 .9", false, 1200, .9),
}),
testing::ElementsAreArray({
"search 1300",
"history 1200 .9",
"history 1400 .5",
}));
// Can redistribute shortcut boosting to non-shortcuts.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
CreateSearchMatch("search 1300", true, 1300),
CreateBoostedShortcutMatch("shortcut 1000 .1", 1000, .1),
CreateSearchMatch("search 1200", true, 1200),
CreateHistoryUrlMlScoredMatch("history 1400 .9", false, 1400, .9),
CreateHistoryUrlMlScoredMatch("history 1100 .5", true, 1100, .5),
}),
testing::ElementsAreArray({
"search 1300",
"history 1400 .9",
"search 1200",
"history 1100 .5",
"shortcut 1000 .1",
}));
// Can 'consume' shortcut boosting by assigning it to a match that's becoming
// default anyways.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
CreateSearchMatch("search 1300", true, 1300),
CreateBoostedShortcutMatch("shortcut 1000 .1", 1000, .1),
CreateSearchMatch("search 1200", true, 1200),
CreateHistoryUrlMlScoredMatch("history 1400 .5", false, 1400, .5),
CreateHistoryUrlMlScoredMatch("history 1100 .9", true, 1100, .9),
}),
testing::ElementsAreArray({
"history 1100 .9",
"search 1300",
"search 1200",
"history 1400 .5",
"shortcut 1000 .1",
}));
// Can increase the number of URLs above searches.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
CreateSearchMatch("search 1300", true, 1300),
CreateBoostedShortcutMatch("shortcut 1000 .7", 1000, .7),
CreateSearchMatch("search 1200", true, 1200),
CreateHistoryUrlMlScoredMatch("history 1400 .5", false, 1400, .5),
CreateHistoryUrlMlScoredMatch("history 1350 .2", false, 1350, .2),
CreateHistoryUrlMlScoredMatch("history 1100 .8", true, 1100, .8),
CreateHistoryUrlMlScoredMatch("history 1050 .9", false, 1050, .9),
}),
testing::ElementsAreArray({
"history 1100 .8",
"history 1050 .9",
"search 1300",
"search 1200",
"shortcut 1000 .7",
"history 1400 .5",
"history 1350 .2",
}));
// Can increase the number of URLs above searches even when the default was a
// URL.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
CreateBoostedShortcutMatch("shortcut 1450 .7", 1450, .7),
CreateSearchMatch("search 1200", true, 1200),
CreateHistoryUrlMlScoredMatch("history 1400 .9", false, 1400, .9),
}),
testing::ElementsAreArray({
"shortcut 1450 .7",
"history 1400 .9",
"search 1200",
}));
// Can decrease the number of URLs above searches.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
CreateHistoryUrlMlScoredMatch("history 1400 .5", true, 1400, .5),
CreateBoostedShortcutMatch("shortcut 1000 .1", 1000, .1),
CreateSearchMatch("search 1300", true, 1300),
CreateSearchMatch("search 1200", true, 1200),
CreateHistoryUrlMlScoredMatch("history 1100 .9", true, 1100, .9),
}),
testing::ElementsAreArray({
"history 1100 .9",
"search 1300",
"search 1200",
"history 1400 .5",
"shortcut 1000 .1",
}));
// When transferring matches, culls the lowest ML ranked matches, rather than
// the lowest traditional ranked matches.
controller_.internal_result_.ClearMatches();
EXPECT_THAT(
controller_.SimulateAutocompletePass(
true, false,
{
CreateSearchMatch("search 1270", true, 1270),
CreateSearchMatch("search 1260", true, 1260),
CreateSearchMatch("search 1250", true, 1250),
CreateSearchMatch("search 1240", true, 1240),
CreateSearchMatch("search 1230", true, 1230),
CreateSearchMatch("search 1220", true, 1220),
CreateSearchMatch("search 1210", true, 1210),
CreateHistoryUrlMlScoredMatch("history 1100 .5", true, 1100, .5),
CreateHistoryUrlMlScoredMatch("history 1000 .9", true, 1000, .9),
}),
testing::ElementsAreArray({
"search 1270",
"search 1260",
"search 1250",
"search 1240",
"search 1230",
"search 1220",
"search 1210",
"history 1000 .9",
}));
// When not transferring matches, like above, culls the lowest ML ranked
// matches, rather than the lowest traditional ranked matches.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
CreateSearchMatch("search 1270", true, 1270),
CreateSearchMatch("search 1260", true, 1260),
CreateSearchMatch("search 1250", true, 1250),
CreateSearchMatch("search 1240", true, 1240),
CreateSearchMatch("search 1230", true, 1230),
CreateSearchMatch("search 1220", true, 1220),
CreateSearchMatch("search 1210", true, 1210),
CreateHistoryUrlMlScoredMatch("history 1100 .5", true, 1100, .5),
CreateHistoryUrlMlScoredMatch("history 1000 .9", true, 1000, .9),
}),
testing::ElementsAreArray({
"search 1270",
"search 1260",
"search 1250",
"search 1240",
"search 1230",
"search 1220",
"search 1210",
"history 1000 .9",
}));
}
TEST_F(AutocompleteControllerTest, MlRanking_ApplyPiecewiseScoringTransform) {
const std::vector<std::pair<double, int>> break_points = {
{0, 500}, {0.25, 1000}, {0.75, 1300}, {1, 1500}};
float ml_score = 0;
EXPECT_EQ(AutocompleteController::ApplyPiecewiseScoringTransform(
ml_score, break_points),
500);
ml_score = 0.186;
EXPECT_EQ(AutocompleteController::ApplyPiecewiseScoringTransform(
ml_score, break_points),
872);
ml_score = 0.25;
EXPECT_EQ(AutocompleteController::ApplyPiecewiseScoringTransform(
ml_score, break_points),
1000);
ml_score = 0.473;
EXPECT_EQ(AutocompleteController::ApplyPiecewiseScoringTransform(
ml_score, break_points),
1133);
ml_score = 0.75;
EXPECT_EQ(AutocompleteController::ApplyPiecewiseScoringTransform(
ml_score, break_points),
1300);
ml_score = 0.914;
EXPECT_EQ(AutocompleteController::ApplyPiecewiseScoringTransform(
ml_score, break_points),
1431);
ml_score = 1;
EXPECT_EQ(AutocompleteController::ApplyPiecewiseScoringTransform(
ml_score, break_points),
1500);
}
TEST_F(AutocompleteControllerTest, MlRanking_PiecewiseMappedSearchBlending) {
OmniboxFieldTrial::ScopedMLConfigForTesting scoped_ml_config;
scoped_ml_config.GetMLConfig().ml_url_scoring = true;
scoped_ml_config.GetMLConfig().url_scoring_model = true;
scoped_ml_config.GetMLConfig().piecewise_mapped_search_blending = true;
scoped_ml_config.GetMLConfig().piecewise_mapped_search_blending_break_points =
"0,500;0.25,1000;0.75,1300;1,1500";
scoped_ml_config.GetMLConfig()
.piecewise_mapped_search_blending_grouping_threshold = 1100;
scoped_ml_config.GetMLConfig()
.piecewise_mapped_search_blending_relevance_bias = 0;
EXPECT_THAT(controller_.SimulateCleanAutocompletePass({}),
testing::ElementsAre());
// If a (remote) document suggestion has a traditional score of zero, then the
// final relevance score should remain zero (instead of using the piecewise ML
// score mapping function to overwrite the relevance score). This will result
// in the document suggestion getting culled from the final list of
// suggestions.
const auto type = AutocompleteMatchType::DOCUMENT_SUGGESTION;
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
// Final score: 1000
CreateMlScoredMatch("document 1400 0.25", type, false, 1400, 0.25),
// Final score: 0 (!= 1500)
CreateMlScoredMatch("document 0 0.95", type, false, 0, 1),
// Final score: 1300
CreateMlScoredMatch("document 1200 0.75", type, false, 1200, 0.75),
}),
testing::ElementsAreArray({
"document 1200 0.75",
"document 1400 0.25",
}));
scoped_ml_config.GetMLConfig().enable_ml_scoring_for_searches = true;
// Calculator and Answer suggestions should not be ML scored at this time,
// since the ML model doesn't assign accurate scores to such suggestions
// (due to the fact that they have a low click-through rate).
std::string answer_json =
"{ \"l\": ["
" { \"il\": { \"t\": [{ \"t\": \"text\", \"tt\": 8 }] } }, "
" { \"il\": { \"t\": [{ \"t\": \"sunny with a chance of hail\", "
"\"tt\": "
"5 }] } }] }";
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
// Final score: 1100 (!= 1300)
CreateAnswerMlScoredMatch("answer 1100 0.75",
omnibox::ANSWER_TYPE_WEATHER, answer_json,
false, 1100, 0.75),
// Final score: 1000 (!= 1500)
CreateMlScoredMatch("calculator 1000 0.95",
AutocompleteMatchType::CALCULATOR, false, 1000,
1),
// Final score: 1431
CreateHistoryUrlMlScoredMatch("history 500 0.914", true, 500, 0.914),
}),
testing::ElementsAreArray({
"history 500 0.914",
"answer 1100 0.75",
"calculator 1000 0.95",
}));
scoped_ml_config.GetMLConfig().enable_ml_scoring_for_searches = false;
// Simple case of ranking with piecewise score mapping. The ML
// scores used here are the same as those specified in the
// `MlRanking_ApplyPiecewiseScoringTransform` test for the sake of simplicity.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
// Final score: 1133
CreateHistoryUrlMlScoredMatch("history 1350 .473", true, 1350, .473),
// Final score: 1431
CreateHistoryUrlMlScoredMatch("history 1200 .914", true, 1200, .914),
// Final score: 872
CreateHistoryUrlMlScoredMatch("history 1100 .186", false, 1100, .186),
// Final score: 1000
CreateHistoryUrlMlScoredMatch("history 500 .25", true, 500, .25),
}),
testing::ElementsAreArray({
"history 1200 .914",
"history 1350 .473",
"history 500 .25",
"history 1100 .186",
}));
scoped_refptr<FakeAutocompleteProvider> shortcut_provider =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_SHORTCUTS);
auto shortcut_match = CreateMlScoredMatch(
"shortcut 600 0.75", AutocompleteMatchType::HISTORY_URL, true, 600, 0.75);
shortcut_match.provider = shortcut_provider.get();
// Non-boosted shortcut suggestions should be ranked BELOW searches.
shortcut_match.scoring_signals->set_visit_count(0);
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
// Final score: 1431
CreateHistoryUrlMlScoredMatch("history 1200 .914", true, 1200, .914),
// Final score: 700
CreateSearchMatch("search 700", true, 700),
// Final score: 1300
shortcut_match,
}),
testing::ElementsAreArray({
"history 1200 .914",
"search 700",
"shortcut 600 0.75",
}));
// Boosted shortcut suggestions should be ranked ABOVE searches.
shortcut_match.scoring_signals->set_visit_count(5);
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
// Final score: 1431
CreateHistoryUrlMlScoredMatch("history 1200 .914", true, 1200, .914),
// Final score: 700
CreateSearchMatch("search 700", true, 700),
// Final score: 1300
shortcut_match,
}),
testing::ElementsAreArray({
"history 1200 .914",
"shortcut 600 0.75",
"search 700",
}));
// ...unless their final relevance score (obtained via piecewise ML scoring)
// is below the "grouping threshold".
shortcut_match = CreateMlScoredMatch(
"shortcut 600 0.25", AutocompleteMatchType::HISTORY_URL, true, 600, 0.25);
shortcut_match.provider = shortcut_provider.get();
shortcut_match.scoring_signals->set_visit_count(5);
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
// Final score: 1431
CreateHistoryUrlMlScoredMatch("history 1200 .914", true, 1200, .914),
// Final score: 700
CreateSearchMatch("search 700", true, 700),
// Final score: 1000
shortcut_match,
}),
testing::ElementsAreArray({
"history 1200 .914",
"search 700",
"shortcut 600 0.25",
}));
// In general, URL suggestions are NOT "shortcut boosted" above searches even
// when they're scored higher via ML scoring.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
// Final score: 1431
CreateHistoryUrlMlScoredMatch("history 1200 .914", true, 1200, .914),
// Final score: 700
CreateSearchMatch("search 700", true, 700),
// Final score: 1300
CreateHistoryUrlMlScoredMatch("history 1100 .75", true, 1100, .75),
}),
testing::ElementsAreArray({
"history 1200 .914",
"search 700",
"history 1100 .75",
}));
// When multiple URL suggestions have been assigned the same score by the ML
// model, those suggestions which were top-ranked according to legacy scoring
// should continue to be top-ranked once ML scoring has run.
EXPECT_THAT(
// Each of the below URL suggestions are assigned an initial relevance
// score of 1300. After initial assignment,
// score adjustment logic is applied in order to generate the final
// relevance scores (which are guaranteed to be distinct).
controller_.SimulateCleanAutocompletePass({
// Final score: 1299
CreateHistoryUrlMlScoredMatch("history B 1200 .75", true, 1200, .75),
// Final score: 1296
CreateHistoryUrlMlScoredMatch("history E 200 .75", true, 200, .75),
// Final score: 1300
CreateHistoryUrlMlScoredMatch("history A 1350 .75", true, 1350, .75),
// Final score: 1297
CreateHistoryUrlMlScoredMatch("history D 300 .75", true, 300, .75),
// Final score: 1298
CreateHistoryUrlMlScoredMatch("history C 1100 .75", false, 1100, .75),
// Final score: 1295
CreateHistoryUrlMlScoredMatch("history F 100 .75", true, 100, .75),
}),
testing::ElementsAreArray({
"history A 1350 .75",
"history B 1200 .75",
"history C 1100 .75",
"history D 300 .75",
"history E 200 .75",
"history F 100 .75",
}));
// Can change the default suggestion from 1 history to another.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
// Final score: 1000
CreateHistoryUrlMlScoredMatch("history 1400 .25", true, 1400, .25),
CreateSearchMatch("search", true, 1100),
// Final score: 1300
CreateHistoryUrlMlScoredMatch("history 1200 .75", true, 1200, .75),
}),
testing::ElementsAreArray({
"history 1200 .75",
"search",
"history 1400 .25",
}));
// Can change the default from search to history.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
CreateSearchMatch("search 1200", true, 1200),
// Final score: 1000
CreateHistoryUrlMlScoredMatch("history 1400 .25", false, 1400, .25),
// Final score: 1300
CreateHistoryUrlMlScoredMatch("history 1100 .75", true, 1100, .75),
}),
testing::ElementsAreArray({
"history 1100 .75",
"search 1200",
"history 1400 .25",
}));
// Can change the default from history to search.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
// Final score: 1000
CreateHistoryUrlMlScoredMatch("history 1400 .25", true, 1400, .25),
CreateSearchMatch("search 1300", true, 1300),
// Final score: 872
CreateHistoryUrlMlScoredMatch("history 1200 .186", false, 1200, .186),
}),
testing::ElementsAreArray({
"search 1300",
"history 1400 .25",
"history 1200 .186",
}));
// When transferring matches, culls the lowest ML ranked matches, rather than
// the lowest traditional ranked matches.
controller_.internal_result_.Reset();
EXPECT_THAT(
controller_.SimulateAutocompletePass(
true, false,
{
CreateSearchMatch("search 1270", true, 1270),
CreateSearchMatch("search 1260", true, 1260),
CreateSearchMatch("search 1250", true, 1250),
CreateSearchMatch("search 1240", true, 1240),
CreateSearchMatch("search 1230", true, 1230),
CreateSearchMatch("search 1220", true, 1220),
CreateSearchMatch("search 1210", true, 1210),
CreateHistoryUrlMlScoredMatch("history 1100 .1", true, 1100, .1),
CreateHistoryUrlMlScoredMatch("history 1000 .2", true, 1000, .2),
}),
testing::ElementsAreArray({
"search 1270",
"search 1260",
"search 1250",
"search 1240",
"search 1230",
"search 1220",
"search 1210",
"history 1000 .2",
}));
// When not transferring matches, like above, culls the lowest ML ranked
// matches, rather than the lowest traditional ranked matches.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
CreateSearchMatch("search 1270", true, 1270),
CreateSearchMatch("search 1260", true, 1260),
CreateSearchMatch("search 1250", true, 1250),
CreateSearchMatch("search 1240", true, 1240),
CreateSearchMatch("search 1230", true, 1230),
CreateSearchMatch("search 1220", true, 1220),
CreateSearchMatch("search 1210", true, 1210),
CreateHistoryUrlMlScoredMatch("history 1100 .1", true, 1100, .1),
CreateHistoryUrlMlScoredMatch("history 1000 .2", true, 1000, .2),
}),
testing::ElementsAreArray({
"search 1270",
"search 1260",
"search 1250",
"search 1240",
"search 1230",
"search 1220",
"search 1210",
"history 1000 .2",
}));
}
TEST_F(AutocompleteControllerTest, MlRanking_MappedSearchBlending) {
OmniboxFieldTrial::ScopedMLConfigForTesting scoped_ml_config;
scoped_ml_config.GetMLConfig().ml_url_scoring = true;
scoped_ml_config.GetMLConfig().url_scoring_model = true;
scoped_ml_config.GetMLConfig().mapped_search_blending = true;
scoped_ml_config.GetMLConfig().mapped_search_blending_min = 600;
scoped_ml_config.GetMLConfig().mapped_search_blending_max = 2800;
scoped_ml_config.GetMLConfig().mapped_search_blending_grouping_threshold =
1400;
EXPECT_THAT(controller_.SimulateCleanAutocompletePass({}),
testing::ElementsAre());
// If ML ranks a URL 0, then the final relevance score should be set to the
// value of `mapped_search_blending_min` (since ML scores are mapped using the
// formula "final_score = min + ml_score * (max - min))".
EXPECT_THAT(controller_.SimulateCleanAutocompletePass({
CreateHistoryUrlMlScoredMatch("history", true, 1400, 0),
CreateSearchMatch("search", true, 1300),
}),
testing::ElementsAreArray({
"search",
"history",
}));
// If a (remote) document suggestion has a traditional score of zero, then the
// final relevance score should remain zero (instead of using the formula
// "final_score = min + ml_score * (max - min)" to overwrite the score). This
// will result in the document suggestion getting culled from the final list
// of suggestions.
const auto type = AutocompleteMatchType::DOCUMENT_SUGGESTION;
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
// Final score: 1150 (== 600 + 0.25 * (2800 - 600))
CreateMlScoredMatch("document 1400 0.25", type, false, 1400, 0.25),
// Final score: 0 (!= 600 + 0.95 * (2800 - 600))
CreateMlScoredMatch("document 0 0.95", type, false, 0, 0.95),
// Final score: 2250 (== 600 + 0.75 * (2800 - 600))
CreateMlScoredMatch("document 1200 0.75", type, false, 1200, 0.75),
}),
testing::ElementsAreArray({
"document 1200 0.75",
"document 1400 0.25",
}));
// Simple case of ranking with linear score mapping.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
// Final score: 1700 (== 600 + 0.5 * (2800 - 600))
CreateHistoryUrlMlScoredMatch("history 1350 .5", true, 1350, .5),
// Final score: 2580 (== 600 + 0.9 * (2800 - 600))
CreateHistoryUrlMlScoredMatch("history 1200 .9", true, 1200, .9),
// Final score: 820 (== 600 + 0.1 * (2800 - 600))
CreateHistoryUrlMlScoredMatch("history 1100 .1", false, 1100, .1),
// Final score: 1040 (== 600 + 0.2 * (2800 - 600))
CreateHistoryUrlMlScoredMatch("history 500 .2", true, 500, .2),
}),
testing::ElementsAreArray({
"history 1200 .9",
"history 1350 .5",
"history 500 .2",
"history 1100 .1",
}));
// Verify that URLs are grouped above searches if their final score is
// greater than `grouping_threshold` (i.e. "shortcut boosting").
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
// Final score: 1700 (== 600 + 0.5 * (2800 - 600))
CreateHistoryUrlMlScoredMatch("history 1350 .5", true, 1350, .5),
CreateSearchMatch("search 1400", false, 1400),
CreateSearchMatch("search 800", true, 800),
CreateSearchMatch("search 600", false, 600),
// Final score: 2580 (== 600 + 0.9 * (2800 - 600))
CreateHistoryUrlMlScoredMatch("history 1200 .9", true, 1200, .9),
// Final score: 820 (== 600 + 0.1 * (2800 - 600))
CreateHistoryUrlMlScoredMatch("history 1100 .1", false, 1100, .1),
// Final score: 1040 (== 600 + 0.2 * (2800 - 600))
CreateHistoryUrlMlScoredMatch("history 500 .2", true, 500, .2),
}),
testing::ElementsAreArray({
"history 1200 .9",
"history 1350 .5",
"search 1400",
"search 800",
"search 600",
"history 500 .2",
"history 1100 .1",
}));
// When multiple URL suggestions have been assigned the same score by the ML
// model, those suggestions which were top-ranked according to legacy scoring
// should continue to be top-ranked once ML scoring has run.
EXPECT_THAT(
// Each of the below URL suggestions are assigned an initial relevance
// score of 1040 (== 600 + 0.2 * (2800 - 600)). After initial assignment,
// score adjustment logic is applied in order to generate the final
// relevance scores (which are guaranteed to be distinct).
controller_.SimulateCleanAutocompletePass({
// Final score: 1039
CreateHistoryUrlMlScoredMatch("history B 1200 .2", true, 1200, .2),
// Final score: 1036
CreateHistoryUrlMlScoredMatch("history E 200 .2", true, 200, .2),
// Final score: 1040
CreateHistoryUrlMlScoredMatch("history A 1350 .2", true, 1350, .2),
// Final score: 1037
CreateHistoryUrlMlScoredMatch("history D 300 .2", true, 300, .2),
// Final score: 1038
CreateHistoryUrlMlScoredMatch("history C 1100 .2", false, 1100, .2),
// Final score: 1035
CreateHistoryUrlMlScoredMatch("history F 100 .2", true, 100, .2),
}),
testing::ElementsAreArray({
"history A 1350 .2",
"history B 1200 .2",
"history C 1100 .2",
"history D 300 .2",
"history E 200 .2",
"history F 100 .2",
}));
// Can change the default suggestion from 1 history to another.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
// Final score: 1040 (== 600 + 0.2 * (2800 - 600))
CreateHistoryUrlMlScoredMatch("history 1400 .2", true, 1400, .2),
CreateSearchMatch("search", true, 1100),
// Final score: 1260 (== 600 + 0.3 * (2800 - 600))
CreateHistoryUrlMlScoredMatch("history 1200 .3", true, 1200, .3),
}),
testing::ElementsAreArray({
"history 1200 .3",
"search",
"history 1400 .2",
}));
// Can change the default from search to history.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
CreateSearchMatch("search 1200", true, 1200),
// Final score: 1040 (== 600 + 0.2 * (2800 - 600))
CreateHistoryUrlMlScoredMatch("history 1400 .2", false, 1400, .2),
// Final score: 1260 (== 600 + 0.3 * (2800 - 600))
CreateHistoryUrlMlScoredMatch("history 1100 .3", true, 1100, .3),
}),
testing::ElementsAreArray({
"history 1100 .3",
"search 1200",
"history 1400 .2",
}));
// Can change the default from history to search.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
// Final score: 1040 (== 600 + 0.2 * (2800 - 600))
CreateHistoryUrlMlScoredMatch("history 1400 .2", true, 1400, .2),
CreateSearchMatch("search 1300", true, 1300),
// Final score: 820 (== 600 + 0.1 * (2800 - 600))
CreateHistoryUrlMlScoredMatch("history 1200 .1", false, 1200, .1),
}),
testing::ElementsAreArray({
"search 1300",
"history 1400 .2",
"history 1200 .1",
}));
// When transferring matches, culls the lowest ML ranked matches, rather than
// the lowest traditional ranked matches.
controller_.internal_result_.Reset();
EXPECT_THAT(
controller_.SimulateAutocompletePass(
true, false,
{
CreateSearchMatch("search 1270", true, 1270),
CreateSearchMatch("search 1260", true, 1260),
CreateSearchMatch("search 1250", true, 1250),
CreateSearchMatch("search 1240", true, 1240),
CreateSearchMatch("search 1230", true, 1230),
CreateSearchMatch("search 1220", true, 1220),
CreateSearchMatch("search 1210", true, 1210),
CreateHistoryUrlMlScoredMatch("history 1100 .1", true, 1100, .1),
CreateHistoryUrlMlScoredMatch("history 1000 .2", true, 1000, .2),
}),
testing::ElementsAreArray({
"search 1270",
"search 1260",
"search 1250",
"search 1240",
"search 1230",
"search 1220",
"search 1210",
"history 1000 .2",
}));
// When not transferring matches, like above, culls the lowest ML ranked
// matches, rather than the lowest traditional ranked matches.
EXPECT_THAT(
controller_.SimulateCleanAutocompletePass({
CreateSearchMatch("search 1270", true, 1270),
CreateSearchMatch("search 1260", true, 1260),
CreateSearchMatch("search 1250", true, 1250),
CreateSearchMatch("search 1240", true, 1240),
CreateSearchMatch("search 1230", true, 1230),
CreateSearchMatch("search 1220", true, 1220),
CreateSearchMatch("search 1210", true, 1210),
CreateHistoryUrlMlScoredMatch("history 1100 .1", true, 1100, .1),
CreateHistoryUrlMlScoredMatch("history 1000 .2", true, 1000, .2),
}),
testing::ElementsAreArray({
"search 1270",
"search 1260",
"search 1250",
"search 1240",
"search 1230",
"search 1220",
"search 1210",
"history 1000 .2",
}));
}
TEST_F(AutocompleteControllerTest, UpdateResult_MLRanking_PreserveDefault) {
OmniboxFieldTrial::ScopedMLConfigForTesting scoped_ml_config;
scoped_ml_config.GetMLConfig().ml_url_scoring = true;
scoped_ml_config.GetMLConfig().url_scoring_model = true;
scoped_ml_config.GetMLConfig().mapped_search_blending = true;
scoped_ml_config.GetMLConfig().mapped_search_blending_min = 600;
scoped_ml_config.GetMLConfig().mapped_search_blending_max = 2800;
scoped_ml_config.GetMLConfig().mapped_search_blending_grouping_threshold =
1400;
// ML ranking should preserve search defaults.
// In other words, if the autocomplete controller starts off by listing
// "search 1300" as the (top-ranked) default suggestion, then "search 1300"
// should remain the default suggestion (even though "history 1400" has been
// added to the list of suggestions).
EXPECT_THAT(controller_.SimulateAutocompletePass(
true, true,
{
CreateSearchMatch("search 1300", true, 1300),
}),
testing::ElementsAreArray({
"search 1300",
}));
EXPECT_THAT(
controller_.SimulateAutocompletePass(
true, true,
{
CreateHistoryUrlMlScoredMatch("history 1400", true, 1400, 1),
CreateSearchMatch("search 1300", true, 1300),
}),
testing::ElementsAreArray({
"search 1300",
"history 1400",
}));
// ML ranking should preserve non-search defaults.
// In other words, if the autocomplete controller starts off by listing
// "history 1300" as the (top-ranked) default suggestion, then "history 1300"
// should remain the default suggestion (even though "history 1500" and
// "search 1400" have been added to the list of suggestions).
EXPECT_THAT(
controller_.SimulateAutocompletePass(
true, true,
{
CreateHistoryUrlMlScoredMatch("history 1300", true, 1300, 1),
}),
testing::ElementsAreArray({
"history 1300",
}));
EXPECT_THAT(
controller_.SimulateAutocompletePass(
true, true,
{
CreateHistoryUrlMlScoredMatch("history 1500", true, 1500, 1),
CreateSearchMatch("search 1400", true, 1400),
CreateHistoryUrlMlScoredMatch("history 1300", true, 1300, .1),
}),
testing::ElementsAreArray({
"history 1300",
"history 1500",
"search 1400",
}));
}
TEST_F(AutocompleteControllerTest, UpdateResult_MLRanking_AllMatches) {
OmniboxFieldTrial::ScopedMLConfigForTesting scoped_ml_config;
scoped_ml_config.GetMLConfig().ml_url_scoring = true;
scoped_ml_config.GetMLConfig().url_scoring_model = true;
scoped_ml_config.GetMLConfig().mapped_search_blending = true;
scoped_ml_config.GetMLConfig().mapped_search_blending_min = 600;
scoped_ml_config.GetMLConfig().mapped_search_blending_max = 2800;
scoped_ml_config.GetMLConfig().mapped_search_blending_grouping_threshold =
1400;
EXPECT_THAT(
controller_.SimulateAutocompletePass(
true, false,
{
CreateHistoryUrlMlScoredMatch("history 100", true, 100, 1),
CreateHistoryUrlMlScoredMatch("history 200", true, 200, 1),
CreateHistoryUrlMlScoredMatch("history 300", true, 300, 1),
CreateHistoryUrlMlScoredMatch("history 400", true, 400, 1),
CreateHistoryUrlMlScoredMatch("history 500", true, 500, 1),
CreateHistoryUrlMlScoredMatch("history 600", true, 600, 1),
CreateHistoryUrlMlScoredMatch("history 700", true, 700, 1),
CreateHistoryUrlMlScoredMatch("history 800", true, 800, 1),
CreateHistoryUrlMlScoredMatch("history 900", true, 900, 1),
CreateHistoryUrlMlScoredMatch("history 1000", true, 1000, 1),
}),
testing::ElementsAreArray({
"history 1000",
"history 900",
"history 800",
"history 700",
"history 600",
"history 500",
"history 400",
"history 300",
}));
EXPECT_THAT(
controller_.SimulateAutocompletePass(
true, false,
{
CreateHistoryUrlMlScoredMatch("history 100 .9", true, 100, .9),
CreateHistoryUrlMlScoredMatch("history 200 .8", true, 200, .8),
CreateHistoryUrlMlScoredMatch("history 300 .7", true, 300, .7),
CreateHistoryUrlMlScoredMatch("history 400 .6", true, 400, .6),
CreateHistoryUrlMlScoredMatch("history 500 .5", true, 500, .5),
CreateHistoryUrlMlScoredMatch("history 600 .4", true, 600, .4),
CreateHistoryUrlMlScoredMatch("history 700 .3", true, 700, .3),
CreateHistoryUrlMlScoredMatch("history 800 .2", true, 800, .2),
CreateHistoryUrlMlScoredMatch("history 900 .1", true, 900, .1),
CreateHistoryUrlMlScoredMatch("history 1000 0", true, 1000, 0),
}),
testing::ElementsAreArray({
"history 100 .9",
"history 200 .8",
"history 300 .7",
"history 400 .6",
"history 500 .5",
"history 600 .4",
"history 700 .3",
"history 800 .2",
}));
}
#endif // BUILDFLAG(BUILD_WITH_TFLITE_LIB) && !BUILDFLAG(IS_ANDROID) &&
// !BUILDFLAG(IS_IOS)
TEST_F(AutocompleteControllerTest, UpdateResult_NotifyingAndTimers) {
{
SCOPED_TRACE("Expect immediate notification after sync pass.");
controller_.GetFakeProvider().done_ = false;
controller_.Start(FakeAutocompleteController::CreateInput(u"test"));
controller_.ExpectOnResultChanged(
0, AutocompleteController::UpdateType::kSyncPass);
}
{
SCOPED_TRACE("Expect debounced notification after async pass.");
controller_.GetFakeProvider().done_ = false;
controller_.OnProviderUpdate(true, &controller_.GetFakeProvider());
controller_.ExpectOnResultChanged(
200, AutocompleteController::UpdateType::kAsyncPass);
}
{
SCOPED_TRACE("Expect debouncing to reset after each async passes.");
controller_.GetFakeProvider().done_ = false;
controller_.OnProviderUpdate(true, &controller_.GetFakeProvider());
task_environment_.FastForwardBy(base::Milliseconds(150));
controller_.OnProviderUpdate(true, &controller_.GetFakeProvider());
task_environment_.FastForwardBy(base::Milliseconds(150));
controller_.OnProviderUpdate(true, &controller_.GetFakeProvider());
controller_.ExpectOnResultChanged(
200, AutocompleteController::UpdateType::kAsyncPass);
}
{
SCOPED_TRACE("Expect delayed notification after expiration.");
controller_.UpdateResult(AutocompleteController::UpdateType::kExpirePass);
controller_.ExpectOnResultChanged(
200, AutocompleteController::UpdateType::kExpirePass);
}
{
SCOPED_TRACE("Expect immediate notification after the last async pass.");
controller_.GetFakeProvider().done_ = true;
controller_.OnProviderUpdate(true, &controller_.GetFakeProvider());
controller_.ExpectOnResultChanged(
0, AutocompleteController::UpdateType::kLastAsyncPass);
}
{
SCOPED_TRACE("Expect no stop update after the last async pass.");
controller_.ExpectNoNotificationOrStop();
}
{
SCOPED_TRACE(
"Expect immediate notification after the last async pass, even if a "
"debounced notification is pending.");
controller_.GetFakeProvider().done_ = false;
controller_.Start(FakeAutocompleteController::CreateInput(u"test"));
controller_.ExpectOnResultChanged(
0, AutocompleteController::UpdateType::kSyncPass);
controller_.OnProviderUpdate(true, &controller_.GetFakeProvider());
controller_.GetFakeProvider().done_ = true;
task_environment_.FastForwardBy(base::Milliseconds(10));
controller_.OnProviderUpdate(true, &controller_.GetFakeProvider());
controller_.ExpectOnResultChanged(
0, AutocompleteController::UpdateType::kLastAsyncPass);
}
{
SCOPED_TRACE("Expect no stop update after the last async pass (2).");
controller_.ExpectNoNotificationOrStop();
}
{
SCOPED_TRACE("Expect no stop update after a sync only pass.");
controller_.GetFakeProvider().done_ = true;
controller_.Start(FakeAutocompleteController::CreateInput(u"test"));
controller_.ExpectOnResultChanged(
0, AutocompleteController::UpdateType::kSyncPassOnly);
controller_.ExpectNoNotificationOrStop();
}
{
SCOPED_TRACE(
"Expect a stop update if the async passes takes too long. Expect no "
"notification.");
controller_.GetFakeProvider().done_ = false;
controller_.Start(FakeAutocompleteController::CreateInput(u"test"));
controller_.ExpectOnResultChanged(
0, AutocompleteController::UpdateType::kSyncPass);
controller_.ExpectStopAfter(1500);
}
{
SCOPED_TRACE(
"Expect a stop update to flush any pending notification for completed "
"non-final async passes.");
controller_.GetFakeProvider().done_ = false;
controller_.Start(FakeAutocompleteController::CreateInput(u"test"));
controller_.ExpectOnResultChanged(
0, AutocompleteController::UpdateType::kSyncPass);
for (size_t i = 0; i < 9; ++i) {
task_environment_.FastForwardBy(base::Milliseconds(150));
controller_.OnProviderUpdate(true, &controller_.GetFakeProvider());
}
controller_.ExpectStopAfter(150);
controller_.ExpectNoNotificationOrStop();
}
{
SCOPED_TRACE("Expect debounced expire notification.");
controller_.GetFakeProvider().done_ = false;
AutocompleteMatch transferred_match{
nullptr, 1000, false, AutocompleteMatchType::URL_WHAT_YOU_TYPED};
transferred_match.from_previous = true;
controller_.GetFakeProvider().matches_ = {transferred_match};
controller_.Start(FakeAutocompleteController::CreateInput(u"test"));
controller_.ExpectOnResultChanged(
0, AutocompleteController::UpdateType::kSyncPass);
// Expire timer is 500ms. Debounce delay is 200ms.
controller_.ExpectOnResultChanged(
700, AutocompleteController::UpdateType::kExpirePass);
controller_.ExpectStopAfter(800);
controller_.ExpectNoNotificationOrStop();
}
}
TEST_F(AutocompleteControllerTest, ExplicitStop) {
// Besides the `Stop()` fired by the timer, which is tested in
// `UpdateResult_NotifyingAndTimers`, there's also user triggered `Stop()`s
// tests here.
auto matches = {CreateSearchMatch("search", true, 900)};
{
SCOPED_TRACE(
"Stop with `kInteraction` and no pending changes should not notify "
"`OnResultChanged()` - there's no change to notify of.");
controller_.SimulateAutocompletePass(true, false, matches);
controller_.Stop(AutocompleteStopReason::kInteraction);
controller_.ExpectStopAfter(0, true);
EXPECT_FALSE(controller_.published_result_.empty());
controller_.ExpectNoNotificationOrStop();
}
{
SCOPED_TRACE(
"Stop with `kInteraction` and pending changes should not notify "
"`OnResultChanged()` - the last pending change should be "
"abandoned to avoid changes as the user's e.g. down arrowing.");
controller_.SimulateAutocompletePass(true, false, matches);
controller_.SimulateAutocompletePass(false, false, matches);
controller_.Stop(AutocompleteStopReason::kInteraction);
EXPECT_FALSE(controller_.published_result_.empty());
controller_.ExpectStopAfter(0, true);
controller_.ExpectNoNotificationOrStop();
}
{
SCOPED_TRACE(
"Stop with `kClobbered` and no pending notifications should notify "
"`OnResultChanged()` - observers should know the results were "
"cleared.");
controller_.SimulateAutocompletePass(true, false, matches);
controller_.observer_->last_default_match_changed = true;
controller_.Stop(AutocompleteStopReason::kClobbered);
EXPECT_TRUE(controller_.published_result_.empty());
controller_.ExpectOnResultChanged(
0, AutocompleteController::UpdateType::kStop);
EXPECT_FALSE(controller_.observer_->last_default_match_changed);
controller_.ExpectNoNotificationOrStop();
}
{
SCOPED_TRACE(
"Stop with `kClobbered` and pending notifications should notify "
"`OnResultChanged()` - observers should know the results were cleared."
"cleared.");
controller_.SimulateAutocompletePass(true, false, matches);
controller_.SimulateAutocompletePass(false, false, matches);
controller_.observer_->last_default_match_changed = true;
controller_.Stop(AutocompleteStopReason::kClobbered);
EXPECT_TRUE(controller_.published_result_.empty());
controller_.ExpectOnResultChanged(
0, AutocompleteController::UpdateType::kStop);
EXPECT_FALSE(controller_.observer_->last_default_match_changed);
controller_.ExpectNoNotificationOrStop();
}
}
TEST_F(AutocompleteControllerTest, UpdateResult_ForceAllowedToBeDefault) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndDisableFeature(omnibox::kGroupingFrameworkForNonZPS);
auto set_feature = [](bool enabled) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatureState(
omnibox_feature_configs::ForceAllowedToBeDefault::
kForceAllowedToBeDefault,
enabled);
return omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::ForceAllowedToBeDefault>();
};
{
// When disabled, a not-defaultable history match should not be default.
SCOPED_TRACE("Disabled");
auto disabled_config = set_feature(false);
EXPECT_THAT(controller_.SimulateCleanAutocompletePass({
CreateSearchMatch("search", true, 200),
CreateHistoryUrlMlScoredMatch("history", false, 1400, .5),
}),
testing::ElementsAreArray({
"search",
"history",
}));
}
{
// An initially not-defaultable history match can be made defaultable.
SCOPED_TRACE("Enabled");
auto enabled_config = set_feature(true);
EXPECT_THAT(controller_.SimulateCleanAutocompletePass({
CreateSearchMatch("search", true, 200),
CreateHistoryUrlMlScoredMatch("history", false, 1400, .5),
}),
testing::ElementsAreArray({
"history",
"search",
}));
}
{
// Initially defaultable matches should not be made non-defaultable even if
// they don't qualify for forcing defaultable.
SCOPED_TRACE("Enabled defaultable");
auto enabled_config = set_feature(true);
EXPECT_THAT(controller_.SimulateCleanAutocompletePass({
CreateSearchMatch("search", true, 200),
CreateHistoryUrlMlScoredMatch("history", true, 300, .5),
}),
testing::ElementsAreArray({
"history",
"search",
}));
}
{
// Keyword matches shouldn't be made defaultable.
SCOPED_TRACE("Enabled keyword");
auto enabled_config = set_feature(true);
EXPECT_THAT(controller_.SimulateCleanAutocompletePass({
CreateSearchMatch("search", true, 200),
CreateKeywordHintMatch("keyword", 1000),
}),
testing::ElementsAreArray({
"search",
"keyword",
}));
}
{
// Should not force default when `prevent_inline_autocomplete_` is true.
SCOPED_TRACE("Enabled prevent inline autocomplete");
auto enabled_config = set_feature(true);
controller_.internal_result_.ClearMatches();
EXPECT_THAT(
controller_.SimulateAutocompletePass(
true, true,
{
CreateSearchMatch("search", true, 200),
CreateAutocompleteMatch("history",
AutocompleteMatchType::HISTORY_CLUSTER,
false, false, 1000, std::nullopt),
},
FakeAutocompleteController::CreateInput(u"test", false, true)),
testing::ElementsAreArray({
"search",
"history",
}));
}
}
// Feature not enabled on Android and iOS.
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
TEST_F(AutocompleteControllerTest, UpdateResult_ContextualSuggestionsAndLens) {
// Enable contextual suggestions.
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::ContextualSearch>
contextual_search_config;
contextual_search_config.Get().contextual_zps_limit = 3;
contextual_search_config.Get().show_open_lens_action = true;
contextual_search_config.Get().use_apc_paywall_signal = true;
// Populate TemplateURLService with a keyword.
TemplateURLData turl_data;
turl_data.SetShortName(u"Keyword");
turl_data.SetKeyword(u"keyword");
turl_data.SetURL("https://google.com/search?q={searchTerms}");
controller_.template_url_service_->Add(
std::make_unique<TemplateURL>(turl_data));
// Create a zero-suggest input.
AutocompleteInput zps_input(u"", 0u, metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
zps_input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS);
std::vector<AutocompleteMatch> provider_matches = {
CreatePersonalizedZeroPrefixMatch("zps_base", 1450),
CreateContextualSearchMatch(u"zps_contextual 1"),
CreateContextualSearchMatch(u"zps_contextual 2"),
CreateLensActionMatch(u"lens")};
// Helper to check results
auto check_results = [&](bool expect_contextual, bool expect_lens) {
bool actual_contextual = false;
bool actual_lens = false;
for (const auto& match : controller_.published_result_) {
if (match.subtypes.count(omnibox::SUBTYPE_CONTEXTUAL_SEARCH)) {
actual_contextual = true;
}
if (match.takeover_action &&
match.takeover_action->ActionId() ==
OmniboxActionId::CONTEXTUAL_SEARCH_OPEN_LENS) {
actual_lens = true;
}
}
EXPECT_EQ(actual_contextual, expect_contextual);
EXPECT_EQ(actual_lens, expect_lens);
};
// Lens is active. No contextual suggestions nor Lens entrypoint.
{
SCOPED_TRACE("Lens is active");
EXPECT_CALL(*provider_client(), AreLensEntrypointsVisible())
.WillRepeatedly(testing::Return(false));
EXPECT_CALL(*provider_client(), IsPagePaywalled())
.WillRepeatedly(testing::Return(false));
controller_.SimulateAutocompletePass(/*sync=*/true, /*done=*/true,
provider_matches, zps_input);
check_results(/*expect_contextual=*/false, /*expect_lens=*/false);
}
// Lens is inactive. Contextual suggestions and Lens entrypoint.
{
SCOPED_TRACE("Lens is inactive");
EXPECT_CALL(*provider_client(), AreLensEntrypointsVisible())
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(*provider_client(), IsPagePaywalled())
.WillRepeatedly(testing::Return(false));
controller_.SimulateAutocompletePass(/*sync=*/true, /*done=*/true,
provider_matches, zps_input);
check_results(/*expect_contextual=*/true, /*expect_lens=*/true);
}
// Page is paywalled. No contextual suggestions but has Lens entrypoint.
{
SCOPED_TRACE("Page is paywalled");
EXPECT_CALL(*provider_client(), AreLensEntrypointsVisible())
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(*provider_client(), IsPagePaywalled())
.WillRepeatedly(testing::Return(true));
controller_.SimulateAutocompletePass(/*sync=*/true, /*done=*/true,
provider_matches, zps_input);
check_results(/*expect_contextual=*/false, /*expect_lens=*/true);
}
// Paywall is unknown. No contextual suggestions but has Lens entrypoint.
{
SCOPED_TRACE("Paywall statis is unknown");
EXPECT_CALL(*provider_client(), AreLensEntrypointsVisible())
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(*provider_client(), IsPagePaywalled())
.WillRepeatedly(testing::Return(std::nullopt));
controller_.SimulateAutocompletePass(/*sync=*/true, /*done=*/true,
provider_matches, zps_input);
check_results(/*expect_contextual=*/false, /*expect_lens=*/true);
}
}
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
TEST_F(AutocompleteControllerTest, ExtraHeaders) {
// Populate TemplateURLService with a keyword.
{
TemplateURLData turl_data;
turl_data.SetShortName(u"Keyword");
turl_data.SetKeyword(u"keyword");
turl_data.SetURL("https://google.com/search?q={searchTerms}");
controller_.template_url_service_->Add(
std::make_unique<TemplateURL>(turl_data));
}
// Populate template URL service with starter pack entries.
for (auto& turl_data :
template_url_starter_pack_data::GetStarterPackEngines()) {
controller_.template_url_service_->Add(
std::make_unique<TemplateURL>(std::move(*turl_data)));
}
const std::string expected_gemini_url =
"https://gemini.google.com/prompt?"
"utm_source=chrome_omnibox&utm_medium=owned&utm_campaign=gemini_shortcut";
{
SCOPED_TRACE("@gemini starter pack match gets an extra header.");
auto match = CreateStarterPackMatch(u"@gemini");
// search_terms_args need to have been set.
match.search_terms_args =
std::make_unique<TemplateURLRef::SearchTermsArgs>(u"search term");
controller_.SetMatchDestinationURL(&match);
EXPECT_THAT(
match.extra_headers,
WhenSorted(ElementsAre(Pair("X-Omnibox-Gemini", "search%20term"))));
EXPECT_EQ(match.destination_url, expected_gemini_url);
}
{
SCOPED_TRACE("@gemini starter pack match with url override");
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
omnibox::kStarterPackExpansion,
{{"StarterPackGeminiUrlOverride", "https://example.com/"}});
auto match = CreateStarterPackMatch(u"@gemini");
// search_terms_args need to have been set.
match.search_terms_args =
std::make_unique<TemplateURLRef::SearchTermsArgs>(u"search term?");
controller_.SetMatchDestinationURL(&match);
EXPECT_THAT(
match.extra_headers,
WhenSorted(ElementsAre(Pair("X-Omnibox-Gemini", "search%20term%3F"))));
EXPECT_EQ(match.destination_url, "https://example.com/");
}
{
SCOPED_TRACE("@gemini starter pack with invalid non-encoded input");
auto match = CreateStarterPackMatch(u"@gemini");
// search_terms_args need to have been set.
match.search_terms_args =
std::make_unique<TemplateURLRef::SearchTermsArgs>(u"search term\n");
controller_.SetMatchDestinationURL(&match);
EXPECT_THAT(
match.extra_headers,
WhenSorted(ElementsAre(Pair("X-Omnibox-Gemini", "search%20term%0A"))));
EXPECT_EQ(match.destination_url, expected_gemini_url);
}
{
SCOPED_TRACE("@gemini starter pack with url in the input");
auto match = CreateStarterPackMatch(u"@gemini");
// search_terms_args need to have been set.
match.search_terms_args = std::make_unique<TemplateURLRef::SearchTermsArgs>(
u"what is http://example.com for?");
controller_.SetMatchDestinationURL(&match);
EXPECT_THAT(match.extra_headers,
WhenSorted(ElementsAre(
Pair("X-Omnibox-Gemini",
"what%20is%20http%3A%2F%2Fexample.com%20for%3F"))));
EXPECT_EQ(match.destination_url, expected_gemini_url);
}
{
SCOPED_TRACE("@gemini starter pack with non-ascii input");
auto match = CreateStarterPackMatch(u"@gemini");
// search_terms_args need to have been set.
match.search_terms_args =
std::make_unique<TemplateURLRef::SearchTermsArgs>(u"こんにちは\n");
controller_.SetMatchDestinationURL(&match);
EXPECT_THAT(match.extra_headers,
WhenSorted(ElementsAre(
Pair("X-Omnibox-Gemini",
"%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF%0A"))));
EXPECT_EQ(match.destination_url, expected_gemini_url);
}
{
SCOPED_TRACE("@bookmarks starter pack match does not get an extra header.");
auto match = CreateStarterPackMatch(u"@bookmarks");
// search_terms_args need to have been set.
match.search_terms_args =
std::make_unique<TemplateURLRef::SearchTermsArgs>(u"search term");
controller_.SetMatchDestinationURL(&match);
EXPECT_TRUE(match.extra_headers.empty());
EXPECT_EQ(match.destination_url, "chrome://bookmarks/?q=search+term");
}
{
SCOPED_TRACE("search match does not get an extra header.");
auto match = CreateSearchMatch("search term", true, 1300);
controller_.SetMatchDestinationURL(&match);
EXPECT_TRUE(match.extra_headers.empty());
EXPECT_EQ(match.destination_url, "https://google.com/search?q=search+term");
}
}
TEST_F(AutocompleteControllerTest, ShouldRunProvider_StarterPack) {
std::set<AutocompleteProvider::Type> expected_provider_types;
AutocompleteInput input(u"a", 1u, metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
controller_.input_ = input;
// Populate template URL service with starter pack entries.
std::vector<std::unique_ptr<TemplateURLData>> turls =
template_url_starter_pack_data::GetStarterPackEngines();
for (auto& turl : turls) {
controller_.template_url_service_->Add(
std::make_unique<TemplateURL>(std::move(*turl)));
}
// Not in keyword mode, run all providers except open tab provider.
for (auto& provider : controller_.providers()) {
EXPECT_EQ(controller_.ShouldRunProvider(provider.get()),
provider->type() != AutocompleteProvider::TYPE_OPEN_TAB)
<< "Provider Type: "
<< AutocompleteProvider::TypeToString(provider->type());
}
// Enter keyword mode.
controller_.input_.set_keyword_mode_entry_method(
metrics::OmniboxEventProto_KeywordModeEntryMethod_TAB);
// In @tabs, run search, keyword, and open tab provider only.
controller_.input_.UpdateText(u"@tabs", 0, {});
expected_provider_types = {AutocompleteProvider::TYPE_KEYWORD,
AutocompleteProvider::TYPE_SEARCH,
AutocompleteProvider::TYPE_OPEN_TAB};
for (auto& provider : controller_.providers()) {
EXPECT_EQ(controller_.ShouldRunProvider(provider.get()),
expected_provider_types.contains(provider->type()))
<< "Provider Type: "
<< AutocompleteProvider::TypeToString(provider->type());
}
// In @bookmarks, run search, keyword, and bookmarks only.
controller_.input_.UpdateText(u"@bookmarks", 0, {});
expected_provider_types = {AutocompleteProvider::TYPE_KEYWORD,
AutocompleteProvider::TYPE_SEARCH,
AutocompleteProvider::TYPE_BOOKMARK};
for (auto& provider : controller_.providers()) {
EXPECT_EQ(controller_.ShouldRunProvider(provider.get()),
expected_provider_types.contains(provider->type()))
<< "Provider Type: "
<< AutocompleteProvider::TypeToString(provider->type());
}
// In @history, run search, keyword, and history providers only.
controller_.input_.UpdateText(u"@history", 0, {});
expected_provider_types = {AutocompleteProvider::TYPE_KEYWORD,
AutocompleteProvider::TYPE_SEARCH,
AutocompleteProvider::TYPE_HISTORY_QUICK,
AutocompleteProvider::TYPE_HISTORY_URL};
for (auto& provider : controller_.providers()) {
EXPECT_EQ(controller_.ShouldRunProvider(provider.get()),
expected_provider_types.contains(provider->type()))
<< "Provider Type: "
<< AutocompleteProvider::TypeToString(provider->type());
}
}
TEST_F(AutocompleteControllerTest,
ShouldRunProvider_LimitKeywordModeSuggestions) {
std::set<AutocompleteProvider::Type> excluded_provider_types;
AutocompleteInput input(u"a", 1u, metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
controller_.input_ = input;
// Populate template URL service with an entry for drive.google.com (to test
// document provider) and a generic keyword entry.
TemplateURLData drive_turl_data;
drive_turl_data.SetShortName(u"Google Drive");
drive_turl_data.SetKeyword(u"drive.google.com");
drive_turl_data.SetURL("https://drive.google.com/search?q={searchTerms}");
controller_.template_url_service_->Add(
std::make_unique<TemplateURL>(drive_turl_data));
TemplateURLData turl_data;
turl_data.SetShortName(u"Test Keyword");
turl_data.SetKeyword(u"keyword");
turl_data.SetURL("https://google.com/search?q={searchTerms}");
controller_.template_url_service_->Add(
std::make_unique<TemplateURL>(turl_data));
// Not in keyword mode, run all providers except open tab provider.
excluded_provider_types = {AutocompleteProvider::TYPE_OPEN_TAB};
for (auto& provider : controller_.providers()) {
EXPECT_NE(controller_.ShouldRunProvider(provider.get()),
excluded_provider_types.contains(provider->type()))
<< "Provider Type: "
<< AutocompleteProvider::TypeToString(provider->type());
}
// In keyword mode, all limit provider params on by default, limit document
// and history cluster suggestions as well.
controller_.input_.UpdateText(u"keyword", 0, {});
controller_.input_.set_keyword_mode_entry_method(
metrics::OmniboxEventProto_KeywordModeEntryMethod_TAB);
excluded_provider_types = {
AutocompleteProvider::TYPE_OPEN_TAB,
AutocompleteProvider::TYPE_HISTORY_CLUSTER_PROVIDER,
AutocompleteProvider::TYPE_DOCUMENT,
AutocompleteProvider::TYPE_ON_DEVICE_HEAD};
for (auto& provider : controller_.providers()) {
EXPECT_NE(controller_.ShouldRunProvider(provider.get()),
excluded_provider_types.contains(provider->type()))
<< "Provider Type: "
<< AutocompleteProvider::TypeToString(provider->type());
}
// For drive.google.com, run document provider.
controller_.input_.UpdateText(u"drive.google.com", 0, {});
excluded_provider_types = {
AutocompleteProvider::TYPE_OPEN_TAB,
AutocompleteProvider::TYPE_HISTORY_CLUSTER_PROVIDER,
AutocompleteProvider::TYPE_ON_DEVICE_HEAD};
for (auto& provider : controller_.providers()) {
EXPECT_NE(controller_.ShouldRunProvider(provider.get()),
excluded_provider_types.contains(provider->type()))
<< "Provider Type: "
<< AutocompleteProvider::TypeToString(provider->type());
}
}
TEST_F(AutocompleteControllerTest, ShouldRunProvider_LensSearchbox) {
// Run all providers except open tab provider.
std::set<AutocompleteProvider::Type> excluded_provider_types = {
AutocompleteProvider::TYPE_OPEN_TAB};
controller_.input_ = AutocompleteInput(
u"a", 1u, metrics::OmniboxEventProto::OTHER, TestSchemeClassifier());
for (auto& provider : controller_.providers()) {
EXPECT_NE(controller_.ShouldRunProvider(provider.get()),
excluded_provider_types.contains(provider->type()))
<< "Provider Type: "
<< AutocompleteProvider::TypeToString(provider->type());
}
// For Lens searchboxes, run search provider only.
std::set<AutocompleteProvider::Type> expected_provider_types = {
AutocompleteProvider::TYPE_SEARCH};
controller_.input_ = AutocompleteInput(
u"a", 1u, metrics::OmniboxEventProto::CONTEXTUAL_SEARCHBOX,
TestSchemeClassifier());
for (auto& provider : controller_.providers()) {
EXPECT_EQ(controller_.ShouldRunProvider(provider.get()),
expected_provider_types.contains(provider->type()))
<< "Provider Type: "
<< AutocompleteProvider::TypeToString(provider->type());
}
controller_.input_ = AutocompleteInput(
u"a", 1u, metrics::OmniboxEventProto::SEARCH_SIDE_PANEL_SEARCHBOX,
TestSchemeClassifier());
for (auto& provider : controller_.providers()) {
EXPECT_EQ(controller_.ShouldRunProvider(provider.get()),
expected_provider_types.contains(provider->type()))
<< "Provider Type: "
<< AutocompleteProvider::TypeToString(provider->type());
}
controller_.input_ = AutocompleteInput(
u"a", 1u, metrics::OmniboxEventProto::LENS_SIDE_PANEL_SEARCHBOX,
TestSchemeClassifier());
for (auto& provider : controller_.providers()) {
EXPECT_EQ(controller_.ShouldRunProvider(provider.get()),
expected_provider_types.contains(provider->type()))
<< "Provider Type: "
<< AutocompleteProvider::TypeToString(provider->type());
}
}
// The EnterpriseSearchAggregatorProvider is only run on desktop.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
BUILDFLAG(IS_CHROMEOS)
TEST_F(AutocompleteControllerTest,
ShouldRunProvider_EnterpriseSearchAggregator) {
// Populate template URL service.
auto add_template_url = [&](const std::string& name,
TemplateURLData::PolicyOrigin policy_origin,
bool featured) {
TemplateURLData data;
data.SetShortName(base::UTF8ToUTF16(name));
data.SetKeyword(base::UTF8ToUTF16(name));
data.SetURL("https://" + name + ".com/q={searchTerms}");
data.policy_origin = policy_origin;
data.featured_by_policy = featured;
controller_.template_url_service_->Add(std::make_unique<TemplateURL>(data));
};
add_template_url("site_search_not_featured",
TemplateURLData::PolicyOrigin::kSiteSearch, false);
add_template_url("site_search_featured",
TemplateURLData::PolicyOrigin::kSiteSearch, true);
add_template_url("aggregator_not_featured",
TemplateURLData::PolicyOrigin::kSearchAggregator, false);
add_template_url("aggregator_featured",
TemplateURLData::PolicyOrigin::kSearchAggregator, true);
// Setup the providers.
auto aggregator_provider = base::MakeRefCounted<FakeAutocompleteProvider>(
AutocompleteProvider::Type::TYPE_ENTERPRISE_SEARCH_AGGREGATOR);
controller_.providers_.push_back(aggregator_provider);
auto document_provider = base::MakeRefCounted<FakeAutocompleteProvider>(
AutocompleteProvider::Type::TYPE_DOCUMENT);
controller_.providers_.push_back(document_provider);
// In unscoped mode (not keyword mode), aggregator is run when
// `require_shortcut` policy field is false, and is not run when
// `require_shortcut` policy field is true. When it is run, the document
// provider should not be run and vice versa.
controller_.input_ = AutocompleteInput(
u"query", 1u, metrics::OmniboxEventProto::OTHER, TestSchemeClassifier());
EXPECT_TRUE(controller_.ShouldRunProvider(aggregator_provider.get()));
EXPECT_FALSE(controller_.ShouldRunProvider(document_provider.get()));
pref_service()->SetManagedPref(
EnterpriseSearchManager::
kEnterpriseSearchAggregatorSettingsRequireShortcutPrefName,
base::Value(true));
EXPECT_FALSE(controller_.ShouldRunProvider(aggregator_provider.get()));
EXPECT_TRUE(controller_.ShouldRunProvider(document_provider.get()));
// If the feature param `disable_drive` is false, then the document provider
// should run regardless of whether the aggregator provider is ran.
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::SearchAggregatorProvider>
scoped_config;
scoped_config.Get().disable_drive = false;
pref_service()->SetManagedPref(
EnterpriseSearchManager::
kEnterpriseSearchAggregatorSettingsRequireShortcutPrefName,
base::Value(false));
EXPECT_TRUE(controller_.ShouldRunProvider(aggregator_provider.get()));
EXPECT_TRUE(controller_.ShouldRunProvider(document_provider.get()));
pref_service()->SetManagedPref(
EnterpriseSearchManager::
kEnterpriseSearchAggregatorSettingsRequireShortcutPrefName,
base::Value(true));
EXPECT_FALSE(controller_.ShouldRunProvider(aggregator_provider.get()));
EXPECT_TRUE(controller_.ShouldRunProvider(document_provider.get()));
// Enter keyword mode.
controller_.input_.set_keyword_mode_entry_method(
metrics::OmniboxEventProto_KeywordModeEntryMethod_TAB);
// Aggregator not ran when in site search mode, regardless of
// `enterprise_search_aggregator_settings.require_shortcut` pref value.
controller_.input_.UpdateText(u"site_search_not_featured", 0, {});
EXPECT_FALSE(controller_.ShouldRunProvider(aggregator_provider.get()));
controller_.input_.UpdateText(u"site_search_featured", 0, {});
EXPECT_FALSE(controller_.ShouldRunProvider(aggregator_provider.get()));
pref_service()->SetManagedPref(
EnterpriseSearchManager::
kEnterpriseSearchAggregatorSettingsRequireShortcutPrefName,
base::Value(false));
controller_.input_.UpdateText(u"site_search_not_featured", 0, {});
EXPECT_FALSE(controller_.ShouldRunProvider(aggregator_provider.get()));
controller_.input_.UpdateText(u"site_search_featured", 0, {});
EXPECT_FALSE(controller_.ShouldRunProvider(aggregator_provider.get()));
// Only search, keyword, and aggregator providers ran when in aggregator mode.
std::set<AutocompleteProvider::Type> expected_provider_types = {
AutocompleteProvider::TYPE_SEARCH, AutocompleteProvider::TYPE_KEYWORD,
AutocompleteProvider::Type::TYPE_ENTERPRISE_SEARCH_AGGREGATOR};
controller_.input_.UpdateText(u"aggregator_not_featured", 0, {});
for (auto& provider : controller_.providers()) {
EXPECT_EQ(controller_.ShouldRunProvider(provider.get()),
expected_provider_types.contains(provider->type()))
<< "Provider Type: "
<< AutocompleteProvider::TypeToString(provider->type());
}
controller_.input_.UpdateText(u"aggregator_featured", 0, {});
for (auto& provider : controller_.providers()) {
EXPECT_EQ(controller_.ShouldRunProvider(provider.get()),
expected_provider_types.contains(provider->type()))
<< "Provider Type: "
<< AutocompleteProvider::TypeToString(provider->type());
}
}
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
// BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_ANDROID)
TEST_F(AutocompleteControllerTest, ShouldRunProvider_AndroidHubSearch) {
// Include bookmarks and history as default providers for hub search.
std::set<AutocompleteProvider::Type> expected_provider_types = {
AutocompleteProvider::TYPE_SEARCH, AutocompleteProvider::TYPE_OPEN_TAB,
AutocompleteProvider::TYPE_BOOKMARK,
AutocompleteProvider::TYPE_HISTORY_QUICK};
controller_.input_ =
AutocompleteInput(u"a", 1u, metrics::OmniboxEventProto::ANDROID_HUB,
TestSchemeClassifier());
for (auto& provider : controller_.providers()) {
EXPECT_EQ(controller_.ShouldRunProvider(provider.get()),
expected_provider_types.contains(provider->type()))
<< "Provider Type: "
<< AutocompleteProvider::TypeToString(provider->type());
}
}
#endif
TEST_F(AutocompleteControllerTest, UpdateSearchboxStatsForAnswerAction) {
// Populate TemplateURLService with a keyword.
TemplateURLData turl_data;
turl_data.SetShortName(u"Keyword");
turl_data.SetKeyword(u"keyword");
turl_data.SetURL("https://google.com/search?q={searchTerms}");
controller_.template_url_service_->Add(
std::make_unique<TemplateURL>(turl_data));
omnibox::SuggestionEnhancement enhancement;
enhancement.set_display_text("Similar and opposite words");
auto answer_action = base::MakeRefCounted<OmniboxAnswerAction>(
std::move(enhancement), TemplateURLRef::SearchTermsArgs(),
omnibox::ANSWER_TYPE_DICTIONARY);
AutocompleteMatch match1 = CreateSearchMatch("match1", true, 1300);
match1.actions.push_back(answer_action);
controller_.Stop(AutocompleteStopReason::kClobbered);
EXPECT_THAT(controller_.SimulateAutocompletePass(
/*sync=*/true, /*done=*/true,
{match1, CreateSearchMatch("match2", true, 1200),
CreateSearchMatch("match3", true, 1100)}),
testing::ElementsAreArray({
"match1",
"match2",
"match3",
}));
EXPECT_EQ(
answer_action->search_terms_args.searchbox_stats.SerializeAsString(),
controller_.published_result_.match_at(0)
->search_terms_args->searchbox_stats.SerializeAsString());
}
// Anroid and iOS have different handling for pedals.
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
TEST_F(AutocompleteControllerTest, NoActionsAttachedToLensSearchboxMatches) {
std::unordered_map<OmniboxPedalId, scoped_refptr<OmniboxPedal>> pedals;
const auto add = [&](OmniboxPedal* pedal) {
pedals.insert(
std::make_pair(pedal->PedalId(), base::WrapRefCounted(pedal)));
};
add(new TestOmniboxPedalClearBrowsingData());
provider_client()->set_pedal_provider(std::make_unique<OmniboxPedalProvider>(
*provider_client(), std::move(pedals)));
EXPECT_NE(nullptr, provider_client()->GetPedalProvider());
// Create input with lens searchbox page classification.
controller_.input_ = AutocompleteInput(
u"Clear History", metrics::OmniboxEventProto::LENS_SIDE_PANEL_SEARCHBOX,
TestSchemeClassifier());
SetAutocompleteMatches(
{CreateSearchMatch(u"Clear History"), CreateSearchMatch(u"search 1"),
CreateSearchMatch(u"search 2"),
CreateHistoryURLMatch(
/*destination_url=*/"http://this-site-matches.com")});
static_cast<FakeTabMatcher&>(
const_cast<TabMatcher&>(provider_client()->GetTabMatcher()))
.set_url_substring_match("matches");
controller_.AttachActions();
// For a Lens Searchbox, AttachActions should not attach a pedal to the
// first match, and therefore it won't get split out into a separate pedal
// match. It also shouldn't attach a switch to this tab action to the last
// match.
EXPECT_EQ(nullptr, controller_.internal_result_.match_at(1)->takeover_action);
EXPECT_FALSE(
controller_.internal_result_.match_at(3)->has_tab_match.value_or(false));
controller_.input_ =
AutocompleteInput(u"Clear History", metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
SetAutocompleteMatches(
{CreateSearchMatch(u"Clear History"),
CreateHistoryURLMatch(
/*destination_url=*/"http://this-site-matches.com"),
CreateSearchMatch(u"search 1"), CreateSearchMatch(u"search 2")});
controller_.AttachActions();
// For any other page classification, AttachActions should attach a pedal
// and a switch to this tab action to the relevant matches.
EXPECT_EQ(
OmniboxActionId::PEDAL,
controller_.internal_result_.match_at(1)->takeover_action->ActionId());
EXPECT_TRUE(
controller_.internal_result_.match_at(2)->has_tab_match.value_or(false));
}
#endif
// Feature not enabled on Android and iOS.
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
TEST_F(AutocompleteControllerTest,
ContextualSearchActionAttachedPageKeywordMode) {
// Create a pedal provider to ensure that the contextual search action takes
// precedence over the pedal.
std::unordered_map<OmniboxPedalId, scoped_refptr<OmniboxPedal>> pedals;
const auto add = [&](OmniboxPedal* pedal) {
pedals.insert(
std::make_pair(pedal->PedalId(), base::WrapRefCounted(pedal)));
};
add(new TestOmniboxPedalClearBrowsingData());
provider_client()->set_pedal_provider(std::make_unique<OmniboxPedalProvider>(
*provider_client(), std::move(pedals)));
EXPECT_NE(nullptr, provider_client()->GetPedalProvider());
// Populate template URL service with starter pack entries.
for (auto& turl_data :
template_url_starter_pack_data::GetStarterPackEngines()) {
controller_.template_url_service_->Add(
std::make_unique<TemplateURL>(std::move(*turl_data)));
}
// Create input with lens searchbox page classification.
controller_.input_ =
AutocompleteInput(u"@page Summar", metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
controller_.input_.set_keyword_mode_entry_method(
metrics::OmniboxEventProto::SPACE_AT_END);
SetAutocompleteMatches({CreateContextualSearchMatch(u"Summary"),
CreateContextualSearchMatch(u"Summarize this page")});
static_cast<FakeTabMatcher&>(
const_cast<TabMatcher&>(provider_client()->GetTabMatcher()))
.set_url_substring_match("matches");
controller_.AttachActions();
// The takeover action should be for the contextual search action, not pedals.
ASSERT_TRUE(controller_.internal_result_.match_at(0)->takeover_action);
EXPECT_EQ(
OmniboxActionId::CONTEXTUAL_SEARCH_FULFILLMENT,
controller_.internal_result_.match_at(0)->takeover_action->ActionId());
ASSERT_TRUE(controller_.internal_result_.match_at(1)->takeover_action);
EXPECT_EQ(
OmniboxActionId::CONTEXTUAL_SEARCH_FULFILLMENT,
controller_.internal_result_.match_at(1)->takeover_action->ActionId());
}
TEST_F(AutocompleteControllerTest,
ContextualSearchActionAttachedInZeroSuggest) {
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::ContextualSearch>
contextual_search_config;
contextual_search_config.Get().contextual_zero_suggest_lens_fulfillment =
true;
EXPECT_CALL(*provider_client(), IsLensEnabled())
.WillRepeatedly(testing::Return(true));
// Create a pedal provider to ensure that the contextual search action takes
// precedence over the pedal.
std::unordered_map<OmniboxPedalId, scoped_refptr<OmniboxPedal>> pedals;
const auto add = [&](OmniboxPedal* pedal) {
pedals.insert(
std::make_pair(pedal->PedalId(), base::WrapRefCounted(pedal)));
};
add(new TestOmniboxPedalClearBrowsingData());
provider_client()->set_pedal_provider(std::make_unique<OmniboxPedalProvider>(
*provider_client(), std::move(pedals)));
EXPECT_NE(nullptr, provider_client()->GetPedalProvider());
// Create input for zero suggest.
controller_.input_ = AutocompleteInput(u"", metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
controller_.input_.set_focus_type(
metrics::OmniboxFocusType::INTERACTION_FOCUS);
// Create ZPS matches.
auto contextual_search_match_1 =
CreatePersonalizedZeroPrefixMatch("contextual search match 1", 1450);
contextual_search_match_1.subtypes.insert(omnibox::SUBTYPE_CONTEXTUAL_SEARCH);
auto contextual_search_match_2 =
CreatePersonalizedZeroPrefixMatch("contextual search match 2", 1450);
contextual_search_match_2.subtypes.insert(omnibox::SUBTYPE_CONTEXTUAL_SEARCH);
SetAutocompleteMatches(
{CreatePersonalizedZeroPrefixMatch("normal zps match 1", 1200),
contextual_search_match_1, contextual_search_match_2,
CreatePersonalizedZeroPrefixMatch("noormal zps match 1", 1550)});
static_cast<FakeTabMatcher&>(
const_cast<TabMatcher&>(provider_client()->GetTabMatcher()))
.set_url_substring_match("matches");
controller_.AttachActions();
// The takeover action should be for the contextual suggestions, but not
// others.
EXPECT_FALSE(controller_.internal_result_.match_at(0)->takeover_action);
EXPECT_FALSE(controller_.internal_result_.match_at(3)->takeover_action);
ASSERT_TRUE(controller_.internal_result_.match_at(1)->takeover_action);
EXPECT_EQ(
OmniboxActionId::CONTEXTUAL_SEARCH_FULFILLMENT,
controller_.internal_result_.match_at(1)->takeover_action->ActionId());
ASSERT_TRUE(controller_.internal_result_.match_at(2)->takeover_action);
EXPECT_EQ(
OmniboxActionId::CONTEXTUAL_SEARCH_FULFILLMENT,
controller_.internal_result_.match_at(2)->takeover_action->ActionId());
}
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
TEST_F(AutocompleteControllerTest, UpdateAssociatedKeywords) {
controller_.keyword_provider_ =
new KeywordProvider(provider_client(), nullptr);
controller_.providers_.push_back(controller_.keyword_provider_.get());
auto add_keyword = [&](std::u16string keyword, bool is_starter_pack = false,
bool is_featured_enterprise_search = false) {
TemplateURLData turl_data;
turl_data.SetShortName(u"name");
turl_data.SetURL("https://google.com/search?q={searchTerms}");
turl_data.is_active = TemplateURLData::ActiveStatus::kTrue;
turl_data.SetKeyword(keyword);
if (is_starter_pack) {
turl_data.starter_pack_id = 1;
} else if (is_featured_enterprise_search) {
turl_data.featured_by_policy = true;
}
controller_.template_url_service_->Add(
std::make_unique<TemplateURL>(turl_data));
};
struct MatchData {
std::u16string fill_into_edit;
AutocompleteMatchType::Type type;
};
auto test = [&](const std::u16string input_text,
const std::u16string input_keyword,
std::vector<MatchData> match_datas,
bool is_zero_suggest = false) {
controller_.input_ = FakeAutocompleteController::CreateInput(input_text);
if (is_zero_suggest) {
controller_.input_.set_focus_type(
metrics::OmniboxFocusType::INTERACTION_FOCUS);
}
AutocompleteResult result;
for (const auto& match_data : match_datas) {
AutocompleteMatch match;
match.fill_into_edit = match_data.fill_into_edit;
match.type = match_data.type;
result.AppendMatches({match});
}
if (!input_keyword.empty()) {
result.match_at(0)->keyword = input_keyword;
result.match_at(0)->transition = ui::PAGE_TRANSITION_KEYWORD;
}
controller_.UpdateAssociatedKeywords(&result);
std::vector<std::u16string> attached_keywords;
for (const auto& match : result) {
attached_keywords.push_back(
match.associated_keyword ? match.associated_keyword->keyword : u"");
}
return attached_keywords;
};
add_keyword(u"keyword_0");
add_keyword(u"keyword_1");
add_keyword(u"keyword_starter_pack", true);
add_keyword(u"keyword_featured_enterprise_search", false, true);
// When the input text's 1st word matches a keyword, the keyword hint is added
// to the 1st match regardless of which match is similar to the keyword. Only
// 1 keyword is added even if there's another match matching the keyword.
EXPECT_THAT(test(u"keyword_0", u"", {{u"bing.com"}, {u"keyword_0"}}),
testing::ElementsAreArray({u"keyword_0", u""}));
EXPECT_THAT(
test(u"keyword_0 more words", u"", {{u"bing.com"}, {u"keyword_0"}}),
testing::ElementsAreArray({u"keyword_0", u""}));
// Only 1 keyword is added even if there're 2 non-exact matches matching the
// keyword.
EXPECT_THAT(
test(u"input", u"", {{u"bing.com"}, {u"keyword_0"}, {u"keyword_0"}}),
testing::ElementsAreArray({u"", u"keyword_0", u""}));
// When the user is in a keyword mode, don't show keyword hints for that
// keyword, but do still show other keywords.
EXPECT_THAT(test(u"keyword_0", u"keyword_0",
{{u"keyword_0"}, {u"keyword_0"}, {u"keyword_1"}}),
testing::ElementsAreArray({u"", u"", u"keyword_1"}));
// Starter pack and featured enterprise matches should always have keywords,
// regardless of the input or the match position.
EXPECT_THAT(test(u"input", u"",
{{u"keyword_starter_pack",
AutocompleteMatchType::Type::STARTER_PACK},
{u"keyword_featured_enterprise_search",
AutocompleteMatchType::Type::FEATURED_ENTERPRISE_SEARCH},
{u"keyword_0"},
{u"keywo"}}),
testing::ElementsAreArray({u"keyword_starter_pack",
u"keyword_featured_enterprise_search",
u"keyword_0", u""}));
// Normal matches should not have keyword hints for starter pack or featured
// enterprise keywords.
EXPECT_THAT(test(u"input", u"",
{{u"keyword_starter_pack"},
{u"keyword_featured_enterprise_search"}}),
testing::ElementsAreArray({u"", u""}));
// Normal matches should not have keyword hints for starter pack or featured
// enterprise keywords, even if the input is an exact keyword match.
EXPECT_THAT(test(u"keyword_starter_pack", u"", {{u"keyword_starter_pack"}}),
testing::ElementsAreArray({u""}));
EXPECT_THAT(test(u"keyword_featured_enterprise_search", u"",
{{u"keyword_featured_enterprise_search"}}),
testing::ElementsAreArray({u""}));
// Keywords are added if the 1st word of the match text matches, even if the
// match text has more non-matching words after. Keywords are not added if the
// 1st word of the match text is a prefix of or prefixed by the keyword.
EXPECT_THAT(
test(u"input", u"",
{{u"keywo"}, {u"keyword_0_underscore"}, {u"keyword_0 space"}}),
testing::ElementsAreArray({u"", u"", u"keyword_0"}));
EXPECT_THAT(test(u"", u"",
{{u"keywo", AutocompleteMatchType::Type::NAVSUGGEST},
{u"keyword_0_underscore"},
{u"keyword_0 space"}},
/*is_zero_suggest=*/true),
testing::ElementsAreArray({u"", u"", u""}));
}
// Helper function to create a basic AutocompleteMatch for testing default match
// changes.
AutocompleteMatch CreateDefaultMatch(std::u16string fill_into_edit,
GURL icon_url,
std::u16string associated_keyword,
std::u16string keyword) {
AutocompleteMatch match;
match.fill_into_edit = fill_into_edit;
match.icon_url = icon_url;
if (!associated_keyword.empty()) {
match.associated_keyword = std::make_unique<AutocompleteMatch>();
match.associated_keyword->keyword = associated_keyword;
}
match.keyword = keyword;
// Set other fields to make it a plausible default match
match.relevance = 1000;
match.allowed_to_be_default_match = true;
match.destination_url =
GURL("https://foo.com/" + base::UTF16ToUTF8(match.fill_into_edit));
return match;
}
TEST_F(AutocompleteControllerTest, CheckWhetherDefaultMatchChanged) {
// Helper lambda to set the internal default match
auto set_current_default = [&](std::optional<AutocompleteMatch> match) {
controller_.internal_result_.ClearMatches(); // Clear previous matches
if (match) {
controller_.internal_result_.AppendMatches({*match});
}
};
// Helper lambda to call the private method under test
auto check_change =
[&](std::optional<AutocompleteMatch> last_default_match,
const std::u16string& last_default_associated_keyword) {
// Reset timestamp before check
controller_.last_time_default_match_changed_ = base::TimeTicks();
bool changed = controller_.CheckWhetherDefaultMatchChanged(
last_default_match, last_default_associated_keyword);
// Check if timestamp was updated only if a change was detected
if (changed) {
EXPECT_NE(controller_.last_time_default_match_changed_,
base::TimeTicks());
} else {
EXPECT_EQ(controller_.last_time_default_match_changed_,
base::TimeTicks());
}
return changed;
};
{
// No change: Both null
set_current_default(std::nullopt);
EXPECT_FALSE(check_change(std::nullopt, u""));
}
{
// No change: Both exist and are identical
auto match = CreateDefaultMatch(u"test1", GURL("https://www.foo.com/icon1"),
u"assoc1", u"key1");
set_current_default(match);
EXPECT_FALSE(check_change(match, u"assoc1"));
}
{
// No change: Irrelevant fields differ (e.g., relevance)
auto match = CreateDefaultMatch(u"test1", GURL("https://www.foo.com/icon1"),
u"assoc1", u"key1");
auto match_different_relevance = match;
match_different_relevance.relevance = 900;
set_current_default(match);
EXPECT_FALSE(check_change(match_different_relevance, u"assoc1"));
}
{
// Change: Existence (last had value, current doesn't)
auto match = CreateDefaultMatch(u"test1", GURL("https://www.foo.com/icon1"),
u"assoc1", u"key1");
set_current_default(std::nullopt);
EXPECT_TRUE(check_change(match, u"assoc1"));
}
{
// Change: Existence (last didn't have value, current does)
auto match = CreateDefaultMatch(u"test1", GURL("https://www.foo.com/icon1"),
u"assoc1", u"key1");
set_current_default(match);
EXPECT_TRUE(check_change(std::nullopt, u""));
}
{
// Change: fill_into_edit differs
auto match = CreateDefaultMatch(u"test1", GURL("https://www.foo.com/icon1"),
u"assoc1", u"key1");
auto match_different_fill_into_edit = CreateDefaultMatch(
u"test2", GURL("https://www.foo.com/icon1"), u"assoc1", u"key1");
set_current_default(match_different_fill_into_edit);
EXPECT_TRUE(check_change(match, u"assoc1"));
}
{
// Change: icon_url differs
auto match = CreateDefaultMatch(u"test1", GURL("https://www.foo.com/icon1"),
u"assoc1", u"key1");
auto match_different_icon_url = CreateDefaultMatch(
u"test1", GURL("https://www.foo.com/icon2"), u"assoc1", u"key1");
set_current_default(match_different_icon_url);
EXPECT_TRUE(check_change(match, u"assoc1"));
}
{
// Change: associated_keyword existence differs (last had, current doesn't)
auto match = CreateDefaultMatch(u"test1", GURL("https://www.foo.com/icon1"),
u"assoc1", u"key1");
auto match_no_associated_keyword = CreateDefaultMatch(
u"test1", GURL("https://www.foo.com/icon1"), u"", u"key1");
set_current_default(match_no_associated_keyword);
EXPECT_TRUE(check_change(match, u"assoc1"));
}
{
// Change: associated_keyword existence differs (last didn't, current does)
auto match = CreateDefaultMatch(u"test1", GURL("https://www.foo.com/icon1"),
u"assoc1", u"key1");
auto match_no_associated_keyword = CreateDefaultMatch(
u"test1", GURL("https://www.foo.com/icon1"), u"", u"key1");
set_current_default(match);
EXPECT_TRUE(
check_change(match_no_associated_keyword, u"")); // double check this
}
{
// Change: associated_keyword differs
auto match = CreateDefaultMatch(u"test1", GURL("https://www.foo.com/icon1"),
u"assoc1", u"key1");
auto match_different_associated_keyword = CreateDefaultMatch(
u"test1", GURL("https://www.foo.com/icon1"), u"assoc2", u"key1");
set_current_default(match_different_associated_keyword);
EXPECT_TRUE(check_change(match, u"assoc1"));
}
{
// No change: associated_keyword same
auto match = CreateDefaultMatch(u"test1", GURL("https://www.foo.com/icon1"),
u"assoc1", u"key1");
set_current_default(match);
EXPECT_FALSE(check_change(match, u"assoc1"));
}
{
// Change: keyword differs
auto match = CreateDefaultMatch(u"test1", GURL("https://www.foo.com/icon1"),
u"assoc1", u"key1");
auto match_different_keyword = CreateDefaultMatch(
u"test1", GURL("https://www.foo.com/icon1"), u"assoc1", u"key2");
set_current_default(match_different_keyword);
EXPECT_TRUE(check_change(match, u"assoc1"));
}
{
// No change: keyword same
auto match = CreateDefaultMatch(u"test1", GURL("https://www.foo.com/icon1"),
u"assoc1", u"key1");
set_current_default(match);
EXPECT_FALSE(check_change(match, u"assoc1"));
}
}
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
TEST_F(AutocompleteControllerTest,
AttachContextualSearchOpenLensActionToMatches) {
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::ContextualSearch>
contextual_search_config;
contextual_search_config.Get().contextual_zero_suggest_lens_fulfillment =
true;
contextual_search_config.Get().suggestions_fulfilled_by_lens_supported = true;
// Create a zero-suggest input.
controller_.input_ = AutocompleteInput(u"", metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
controller_.input_.set_focus_type(
metrics::OmniboxFocusType::INTERACTION_FOCUS);
ACMatches matches;
// Match 1: Contextual search suggestion with Lens action.
AutocompleteMatch match1;
match1.subtypes.insert(omnibox::SuggestSubtype::SUBTYPE_CONTEXTUAL_SEARCH);
match1.suggest_template = omnibox::SuggestTemplateInfo();
auto* action1 = match1.suggest_template->add_action_suggestions();
action1->set_action_type(
omnibox::SuggestTemplateInfo_TemplateAction_ActionType_CHROME_LENS);
matches.push_back(match1);
// Match 2: Contextual search suggestion without Lens action.
AutocompleteMatch match2;
match2.subtypes.insert(omnibox::SuggestSubtype::SUBTYPE_CONTEXTUAL_SEARCH);
matches.push_back(match2);
// Match 3: Non-contextual search suggestion with Lens action.
AutocompleteMatch match3;
match3.suggest_template = omnibox::SuggestTemplateInfo();
auto* action3 = match3.suggest_template->add_action_suggestions();
action3->set_action_type(
omnibox::SuggestTemplateInfo_TemplateAction_ActionType_CHROME_LENS);
matches.push_back(match3);
// Match 4: Non-contextual search suggestion without Lens action.
AutocompleteMatch match4;
matches.push_back(match4);
SetAutocompleteMatches(matches);
controller_.AttachActions();
ASSERT_EQ(4u, controller_.internal_result_.size());
// Match 1 should have the open Lens takeover action.
EXPECT_TRUE(controller_.internal_result_.match_at(0)->takeover_action);
EXPECT_EQ(
controller_.internal_result_.match_at(0)->takeover_action->ActionId(),
OmniboxActionId::CONTEXTUAL_SEARCH_OPEN_LENS);
// Others should not.
EXPECT_FALSE(
controller_.internal_result_.match_at(1)->takeover_action->ActionId() ==
OmniboxActionId::CONTEXTUAL_SEARCH_OPEN_LENS);
EXPECT_FALSE(controller_.internal_result_.match_at(2)->takeover_action);
EXPECT_FALSE(controller_.internal_result_.match_at(3)->takeover_action);
}
TEST_F(AutocompleteControllerTest,
ContextualSearchOpenLensActionAttachedPageKeywordMode) {
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::ContextualSearch>
contextual_search_config;
contextual_search_config.Get().suggestions_fulfilled_by_lens_supported = true;
// Create a pedal provider to ensure that the contextual search action takes
// precedence over the pedal.
std::unordered_map<OmniboxPedalId, scoped_refptr<OmniboxPedal>> pedals;
const auto add = [&](OmniboxPedal* pedal) {
pedals.insert(
std::make_pair(pedal->PedalId(), base::WrapRefCounted(pedal)));
};
add(new TestOmniboxPedalClearBrowsingData());
provider_client()->set_pedal_provider(std::make_unique<OmniboxPedalProvider>(
*provider_client(), std::move(pedals)));
EXPECT_NE(nullptr, provider_client()->GetPedalProvider());
// Populate template URL service with starter pack entries.
for (auto& turl_data :
template_url_starter_pack_data::GetStarterPackEngines()) {
controller_.template_url_service_->Add(
std::make_unique<TemplateURL>(std::move(*turl_data)));
}
// Create input with lens searchbox page classification.
controller_.input_ =
AutocompleteInput(u"@page Summar", metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
controller_.input_.set_keyword_mode_entry_method(
metrics::OmniboxEventProto::SPACE_AT_END);
AutocompleteMatch match1 = CreateContextualSearchMatch(u"Summary");
match1.suggest_template = omnibox::SuggestTemplateInfo();
auto* action1 = match1.suggest_template->add_action_suggestions();
action1->set_action_type(
omnibox::SuggestTemplateInfo_TemplateAction_ActionType_CHROME_LENS);
AutocompleteMatch match2 =
CreateContextualSearchMatch(u"Summarize this page");
match2.suggest_template = omnibox::SuggestTemplateInfo();
auto* action2 = match2.suggest_template->add_action_suggestions();
action2->set_action_type(
omnibox::SuggestTemplateInfo_TemplateAction_ActionType_CHROME_LENS);
SetAutocompleteMatches({match1, match2});
static_cast<FakeTabMatcher&>(
const_cast<TabMatcher&>(provider_client()->GetTabMatcher()))
.set_url_substring_match("matches");
controller_.AttachActions();
// The takeover action should be for the contextual search action, not pedals.
ASSERT_TRUE(controller_.internal_result_.match_at(0)->takeover_action);
EXPECT_EQ(
OmniboxActionId::CONTEXTUAL_SEARCH_OPEN_LENS,
controller_.internal_result_.match_at(0)->takeover_action->ActionId());
ASSERT_TRUE(controller_.internal_result_.match_at(1)->takeover_action);
EXPECT_EQ(
OmniboxActionId::CONTEXTUAL_SEARCH_OPEN_LENS,
controller_.internal_result_.match_at(1)->takeover_action->ActionId());
}
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)