| // Copyright 2024 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/featured_search_provider.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/format_macros.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "components/history_embeddings/history_embeddings_features.h" |
| #include "components/omnibox/browser/autocomplete_input.h" |
| #include "components/omnibox/browser/autocomplete_match.h" |
| #include "components/omnibox/browser/autocomplete_match_type.h" |
| #include "components/omnibox/browser/autocomplete_result.h" |
| #include "components/omnibox/browser/fake_autocomplete_provider_client.h" |
| #include "components/omnibox/browser/omnibox_prefs.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/prefs/pref_service.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "components/search_engines/template_url.h" |
| #include "components/search_engines/template_url_data.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "components/search_engines/template_url_starter_pack_data.h" |
| #include "components/sync_preferences/testing_pref_service_syncable.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/metrics_proto/omnibox_event.pb.h" |
| #include "third_party/metrics_proto/omnibox_focus_type.pb.h" |
| #include "url/gurl.h" |
| |
| namespace { |
| |
| constexpr char16_t kBookmarksKeyword[] = u"@bookmarks"; |
| constexpr char16_t kHistoryKeyword[] = u"@history"; |
| constexpr char16_t kTabsKeyword[] = u"@tabs"; |
| constexpr char16_t kGeminiKeyword[] = u"@gemini"; |
| |
| const std::string kBookmarksUrl = |
| template_url_starter_pack_data::bookmarks.destination_url; |
| const std::string kHistoryUrl = |
| template_url_starter_pack_data::history.destination_url; |
| const std::string kTabsUrl = |
| template_url_starter_pack_data::tabs.destination_url; |
| const std::string kGeminiUrl = |
| template_url_starter_pack_data::gemini.destination_url; |
| const std::string kPageUrl = |
| template_url_starter_pack_data::page.destination_url; |
| const std::string kAiModeUrl = |
| template_url_starter_pack_data::ai_mode.destination_url; |
| |
| struct TestData { |
| const std::u16string input; |
| const std::vector<std::string> output; |
| }; |
| |
| struct IphData { |
| const IphType iph_type; |
| const std::u16string iph_contents; |
| const std::u16string iph_link_text; |
| const GURL iph_link_url; |
| }; |
| |
| std::u16string FeaturedKeywordN(int n) { |
| return base::UTF8ToUTF16("@featured" + base::NumberToString(n)); |
| } |
| |
| std::string FeaturedUrlN(int n) { |
| return "https://featured" + base::NumberToString(n) + ".com/q={searchTerms}"; |
| } |
| |
| } // namespace |
| |
| class FeaturedSearchProviderTest : public testing::Test { |
| public: |
| FeaturedSearchProviderTest(const FeaturedSearchProviderTest&) = delete; |
| FeaturedSearchProviderTest& operator=(const FeaturedSearchProviderTest&) = |
| delete; |
| |
| protected: |
| FeaturedSearchProviderTest() = default; |
| ~FeaturedSearchProviderTest() override = default; |
| |
| void SetUp() override { |
| toolbelt_scoped_config_.Get().enabled = true; |
| client_ = std::make_unique<FakeAutocompleteProviderClient>(); |
| ON_CALL(*client_, IsAimEligible()).WillByDefault(testing::Return(true)); |
| provider_ = new FeaturedSearchProvider(client_.get()); |
| omnibox::RegisterProfilePrefs( |
| static_cast<sync_preferences::TestingPrefServiceSyncable*>( |
| client_->GetPrefs()) |
| ->registry()); |
| } |
| void TearDown() override { provider_ = nullptr; } |
| |
| void RunTest(const std::vector<TestData> cases) { |
| ACMatches matches; |
| for (size_t i = 0; i < cases.size(); ++i) { |
| SCOPED_TRACE(base::StringPrintf( |
| "case %" PRIuS ": %s", i, base::UTF16ToUTF8(cases[i].input).c_str())); |
| AutocompleteInput input(cases[i].input, metrics::OmniboxEventProto::OTHER, |
| TestSchemeClassifier()); |
| input.set_allow_exact_keyword_match(false); |
| provider_->Start(input, false); |
| EXPECT_TRUE(provider_->done()); |
| matches = provider_->matches(); |
| ASSERT_EQ(cases[i].output.size(), matches.size()); |
| for (size_t j = 0; j < cases[i].output.size(); ++j) { |
| EXPECT_EQ(matches[j].destination_url, GURL(cases[i].output[j])); |
| } |
| } |
| } |
| |
| void RunAndVerifyIph(const AutocompleteInput& input, |
| const std::vector<IphData> expected_iphs) { |
| provider_->Start(input, false); |
| EXPECT_TRUE(provider_->done()); |
| ACMatches matches = provider_->matches(); |
| if (matches.size() == expected_iphs.size()) { |
| for (size_t j = 0; j < expected_iphs.size(); ++j) { |
| EXPECT_EQ(matches[j].iph_type, expected_iphs[j].iph_type); |
| EXPECT_EQ(matches[j].contents, expected_iphs[j].iph_contents); |
| EXPECT_EQ(matches[j].iph_link_text, expected_iphs[j].iph_link_text); |
| EXPECT_EQ(matches[j].iph_link_url, expected_iphs[j].iph_link_url); |
| } |
| } else { |
| EXPECT_EQ(matches.size(), expected_iphs.size()); |
| } |
| } |
| |
| void RunAndVerifyIphTypes(const AutocompleteInput& input, |
| const std::vector<IphType> expected_iph_types) { |
| provider_->Start(input, false); |
| EXPECT_TRUE(provider_->done()); |
| ACMatches matches = provider_->matches(); |
| if (matches.size() == expected_iph_types.size()) { |
| for (size_t j = 0; j < expected_iph_types.size(); ++j) |
| EXPECT_EQ(matches[j].iph_type, expected_iph_types[j]); |
| } else { |
| EXPECT_EQ(matches.size(), expected_iph_types.size()); |
| } |
| } |
| |
| // Populate the TemplateURLService with starter pack entries. |
| void AddStarterPackEntriesToTemplateUrlService() { |
| std::vector<std::unique_ptr<TemplateURLData>> turls = |
| template_url_starter_pack_data::GetStarterPackEngines(); |
| for (auto& turl : turls) { |
| client_->GetTemplateURLService()->Add( |
| std::make_unique<TemplateURL>(std::move(*turl))); |
| } |
| } |
| |
| // Add a new featured search engine to the TemplateURLService. |
| void AddFeaturedEnterpriseSearchEngine( |
| const std::u16string& keyword, |
| const std::string& url, |
| const TemplateURLData::PolicyOrigin& policy_origin, |
| const TemplateURLData::ActiveStatus& is_active = |
| TemplateURLData::ActiveStatus::kTrue) { |
| TemplateURLData template_url_data; |
| template_url_data.SetKeyword(keyword); |
| template_url_data.SetShortName(keyword + u" Name"); |
| template_url_data.SetURL(url); |
| template_url_data.policy_origin = policy_origin; |
| template_url_data.enforced_by_policy = true; |
| template_url_data.featured_by_policy = true; |
| template_url_data.safe_for_autoreplace = false; |
| template_url_data.is_active = is_active; |
| |
| client_->GetTemplateURLService()->Add( |
| std::make_unique<TemplateURL>(template_url_data)); |
| } |
| |
| omnibox_feature_configs::ScopedConfigForTesting< |
| omnibox_feature_configs::Toolbelt> |
| toolbelt_scoped_config_; |
| std::unique_ptr<MockAutocompleteProviderClient> client_; |
| scoped_refptr<FeaturedSearchProvider> provider_; |
| }; |
| |
| TEST_F(FeaturedSearchProviderTest, NonAtPrefix) { |
| std::vector<TestData> test_cases = { |
| // Typing text that doesn't start with "@" should give nothing. |
| {u"g@rb@g3", {}}, |
| {u"www.google.com", {}}, |
| {u"http:www.google.com", {}}, |
| {u"http://www.google.com", {}}, |
| {u"file:filename", {}}, |
| {u"chrome:", {}}, |
| {u"chrome://", {}}, |
| {u"chrome://version", {}}, |
| }; |
| |
| RunTest(test_cases); |
| } |
| |
| TEST_F(FeaturedSearchProviderTest, DoesNotSupportMatchesOnFocus) { |
| history_embeddings::ScopedFeatureParametersForTesting feature_parameters( |
| base::BindOnce([](history_embeddings::FeatureParameters& parameters) { |
| parameters.omnibox_scoped = false; |
| })); |
| base::test::ScopedFeatureList features; |
| features.InitWithFeaturesAndParameters( |
| {{history_embeddings::kHistoryEmbeddings, {}}}, |
| {omnibox::kStarterPackIPH}); |
| |
| AutocompleteInput input(u"@tabs", metrics::OmniboxEventProto::OTHER, |
| TestSchemeClassifier()); |
| input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS); |
| provider_->Start(input, false); |
| EXPECT_TRUE(provider_->matches().empty()); |
| } |
| |
| TEST_F(FeaturedSearchProviderTest, StarterPack) { |
| base::test::ScopedFeatureList features; |
| features.InitAndDisableFeature(omnibox::kStarterPackExpansion); |
| |
| AddStarterPackEntriesToTemplateUrlService(); |
| |
| std::vector<TestData> typing_scheme_cases = { |
| // Typing the keyword without '@' or past the keyword shouldn't produce |
| // results. |
| {u"b", {}}, |
| {u"bookmarks", {}}, |
| {u"his", {}}, |
| {u"history", {}}, |
| {u"@historyasdjflk", {}}, |
| {u"@bookmarksasld", {}}, |
| {u"tabs", {}}, |
| |
| // With the expansion flag disabled, typing the `@gemini` keyword should |
| // not provide the Gemini suggestion. |
| {u"@gemini", {}}, |
| |
| // Typing '@' should give all the starter pack suggestions. |
| {u"@", {kAiModeUrl, kBookmarksUrl, kHistoryUrl, kTabsUrl}}, |
| |
| // Typing a portion of "@bookmarks" should give the bookmarks suggestion. |
| {std::u16string(kBookmarksKeyword, 0, 3), {kBookmarksUrl}}, |
| {kBookmarksKeyword, {kBookmarksUrl}}, |
| |
| // Typing a portion of "@history" should give the default urls. |
| {std::u16string(kHistoryKeyword, 0, 3), {kHistoryUrl}}, |
| {kHistoryKeyword, {kHistoryUrl}}, |
| |
| // Typing a portion of "@tabs" should give the default urls. |
| {std::u16string(kTabsKeyword, 0, 3), {kTabsUrl}}, |
| {kTabsKeyword, {kTabsUrl}}, |
| }; |
| |
| RunTest(typing_scheme_cases); |
| } |
| |
| TEST_F(FeaturedSearchProviderTest, StarterPackExpansion) { |
| base::test::ScopedFeatureList features; |
| features.InitAndEnableFeature(omnibox::kStarterPackExpansion); |
| |
| AddStarterPackEntriesToTemplateUrlService(); |
| std::vector<TestData> typing_scheme_cases = { |
| // Typing the keyword without '@' or past the keyword shouldn't produce |
| // results. |
| {u"b", {}}, |
| {u"bookmarks", {}}, |
| {u"his", {}}, |
| {u"history", {}}, |
| {u"@historyasdjflk", {}}, |
| {u"@bookmarksasld", {}}, |
| {u"tabs", {}}, |
| {u"gemi", {}}, |
| |
| // Typing '@' should give all the starter pack suggestions. |
| {u"@", {kAiModeUrl, kBookmarksUrl, kGeminiUrl, kHistoryUrl, kTabsUrl}}, |
| |
| // Typing a portion of "@bookmarks" should give the bookmarks suggestion. |
| {std::u16string(kBookmarksKeyword, 0, 3), {kBookmarksUrl}}, |
| {kBookmarksKeyword, {kBookmarksUrl}}, |
| |
| // Typing a portion of "@history" should give the default urls. |
| {std::u16string(kHistoryKeyword, 0, 3), {kHistoryUrl}}, |
| {kHistoryKeyword, {kHistoryUrl}}, |
| |
| // Typing a portion of "@tabs" should give the default urls. |
| {std::u16string(kTabsKeyword, 0, 3), {kTabsUrl}}, |
| {kTabsKeyword, {kTabsUrl}}, |
| |
| // Typing a portion of "@gemini" should give the default urls. |
| {std::u16string(kGeminiKeyword, 0, 3), {kGeminiUrl}}, |
| {kGeminiKeyword, {kGeminiUrl}}, |
| }; |
| |
| RunTest(typing_scheme_cases); |
| } |
| |
| TEST_F(FeaturedSearchProviderTest, StarterPackExpansionRelevance) { |
| base::test::ScopedFeatureList features; |
| features.InitWithFeatures({omnibox::kStarterPackExpansion}, {}); |
| omnibox_feature_configs::ScopedConfigForTesting< |
| omnibox_feature_configs::ContextualSearch> |
| scoped_config; |
| scoped_config.Get().starter_pack_page = true; |
| |
| AddStarterPackEntriesToTemplateUrlService(); |
| |
| AutocompleteInput input(u"@", metrics::OmniboxEventProto::OTHER, |
| TestSchemeClassifier()); |
| input.set_prevent_inline_autocomplete(true); |
| provider_->Start(input, false); |
| EXPECT_TRUE(provider_->done()); |
| ACMatches matches = provider_->matches(); |
| ASSERT_EQ(template_url_starter_pack_data::GetStarterPackEngines().size(), |
| matches.size()); |
| |
| // Sort the matches according to relevances (in descending order), and make |
| // sure that the matches are in the expected order. |
| std::sort(matches.begin(), matches.end(), [](const auto& x, const auto& y) { |
| return x.relevance > y.relevance; |
| }); |
| |
| auto expected_match_order = std::vector<std::string>{ |
| kAiModeUrl, kGeminiUrl, kHistoryUrl, kBookmarksUrl, kPageUrl, kTabsUrl, |
| }; |
| ASSERT_EQ(matches.size(), expected_match_order.size()); |
| for (size_t i = 0; i < matches.size(); i++) { |
| EXPECT_EQ(matches[i].destination_url, GURL(expected_match_order[i])) << i; |
| } |
| } |
| |
| TEST_F(FeaturedSearchProviderTest, FeaturedEnterpriseSearch) { |
| base::test::ScopedFeatureList features; |
| features.InitWithFeatures({omnibox::kStarterPackExpansion}, {}); |
| |
| AddStarterPackEntriesToTemplateUrlService(); |
| |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(2), FeaturedUrlN(2), |
| TemplateURLData::PolicyOrigin::kSiteSearch); |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(1), FeaturedUrlN(1), |
| TemplateURLData::PolicyOrigin::kSiteSearch); |
| // Inactive featured enterprise keywords should not be shown. |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(3), FeaturedUrlN(3), |
| TemplateURLData::PolicyOrigin::kSiteSearch, |
| TemplateURLData::ActiveStatus::kFalse); |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(5), FeaturedUrlN(5), |
| TemplateURLData::PolicyOrigin::kSiteSearch); |
| AddFeaturedEnterpriseSearchEngine( |
| FeaturedKeywordN(4), FeaturedUrlN(4), |
| TemplateURLData::PolicyOrigin::kSearchAggregator); |
| // At most 4 featured enterprise keywords should be shown. |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(6), FeaturedUrlN(6), |
| TemplateURLData::PolicyOrigin::kSiteSearch); |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(7), FeaturedUrlN(7), |
| TemplateURLData::PolicyOrigin::kSiteSearch); |
| |
| std::vector<TestData> typing_scheme_cases = { |
| // Typing the keyword without '@' or past the keyword shouldn't produce |
| // results. |
| {u"f", {}}, |
| {u"feat", {}}, |
| {u"featured1", {}}, |
| {u"featured2abs", {}}, |
| {u"@featured1xxa", {}}, |
| |
| // Typing '@' should give all featured search engines and all the starter |
| // pack suggestions. The provider does not change the order given |
| // by TemplateURLService (which returns all keywords with the prefix in |
| // alphabetical order). Re-ordering by relevance will be made |
| // later on. |
| {u"@", |
| {kAiModeUrl, kBookmarksUrl, FeaturedUrlN(1), FeaturedUrlN(2), |
| FeaturedUrlN(4), FeaturedUrlN(5), kGeminiUrl, kHistoryUrl, kTabsUrl}}, |
| |
| // Typing a portion of "@featured" should give the featured engine |
| // suggestions. |
| {std::u16string(FeaturedKeywordN(1), 0, 3), |
| {FeaturedUrlN(1), FeaturedUrlN(2), FeaturedUrlN(4), FeaturedUrlN(5)}}, |
| {FeaturedKeywordN(1), {FeaturedUrlN(1)}}, |
| }; |
| |
| RunTest(typing_scheme_cases); |
| } |
| |
| TEST_F(FeaturedSearchProviderTest, ZeroSuggestStarterPackIPHSuggestion) { |
| base::test::ScopedFeatureList features; |
| features.InitWithFeaturesAndParameters( |
| {{omnibox::kStarterPackExpansion, {}}, {omnibox::kStarterPackIPH, {}}}, |
| {}); |
| |
| // "Focus" omnibox with zero input to put us in Zero suggest mode. |
| AutocompleteInput input; |
| input.set_focus_type(metrics::INTERACTION_FOCUS); |
| |
| // Run the provider, there should be one match corresponding to IPH for |
| // Starter Pack. |
| provider_->Start(input, false); |
| ACMatches matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 1u); |
| EXPECT_EQ(matches[0].type, AutocompleteMatchType::NULL_RESULT_MESSAGE); |
| EXPECT_EQ(matches[0].iph_type, IphType::kGemini); |
| |
| // Not in ZPS, the IPH should not be provided. |
| input.set_focus_type(metrics::INTERACTION_DEFAULT); |
| provider_->Start(input, false); |
| matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 0u); |
| |
| // "@" state - Confirm expected starter pack is still shown but no ZPS. |
| AddStarterPackEntriesToTemplateUrlService(); |
| std::vector<TestData> typing_scheme_cases = { |
| // Typing '@' should give all the starter pack suggestions, and no IPH. |
| {u"@", {kAiModeUrl, kBookmarksUrl, kGeminiUrl, kHistoryUrl, kTabsUrl}}}; |
| RunTest(typing_scheme_cases); |
| } |
| |
| TEST_F(FeaturedSearchProviderTest, |
| ZeroSuggestStarterPackIPHSuggestion_DeleteMatch) { |
| history_embeddings::ScopedFeatureParametersForTesting feature_parameters( |
| base::BindOnce([](history_embeddings::FeatureParameters& parameters) { |
| parameters.omnibox_scoped = false; |
| })); |
| base::test::ScopedFeatureList features; |
| features.InitWithFeaturesAndParameters( |
| {{history_embeddings::kHistoryEmbeddings, {}}, |
| {omnibox::kStarterPackIPH, {}}}, |
| {}); |
| PrefService* prefs = client_->GetPrefs(); |
| |
| // "Focus" omnibox with zero input to put us in Zero suggest mode. |
| AutocompleteInput input; |
| input.set_focus_type(metrics::INTERACTION_FOCUS); |
| |
| // Run the provider, there should be one match corresponding to IPH for |
| // Starter Pack. |
| EXPECT_FALSE(prefs->GetBoolean(omnibox::kDismissedGeminiIph)); |
| provider_->Start(input, false); |
| ACMatches matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 1u); |
| EXPECT_EQ(matches[0].type, AutocompleteMatchType::NULL_RESULT_MESSAGE); |
| EXPECT_EQ(matches[0].iph_type, IphType::kGemini); |
| |
| // Call `DeleteMatch()`, match should be deleted from `matches_` and the pref |
| // should be set to false. |
| provider_->DeleteMatch(matches[0]); |
| matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 0u); |
| EXPECT_TRUE(prefs->GetBoolean(omnibox::kDismissedGeminiIph)); |
| |
| // Run the provider again, IPH match should not be provided. |
| provider_->Start(input, false); |
| matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 0u); |
| } |
| |
| TEST_F(FeaturedSearchProviderTest, |
| ZeroSuggestFeaturedEnterpriseSiteSearchIPHSuggestion) { |
| base::test::ScopedFeatureList features; |
| features.InitWithFeatures({omnibox::kStarterPackExpansion}, |
| {omnibox::kStarterPackIPH}); |
| |
| AddStarterPackEntriesToTemplateUrlService(); |
| |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(2), FeaturedUrlN(2), |
| TemplateURLData::PolicyOrigin::kSiteSearch); |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(1), FeaturedUrlN(1), |
| TemplateURLData::PolicyOrigin::kSiteSearch); |
| // Inactive featured enterprise keywords should not be shown or included in |
| // IPH. |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(3), FeaturedUrlN(3), |
| TemplateURLData::PolicyOrigin::kSiteSearch, |
| TemplateURLData::ActiveStatus::kFalse); |
| |
| // "Focus" omnibox with zero input to put us in Zero suggest mode. |
| AutocompleteInput input; |
| input.set_focus_type(metrics::INTERACTION_FOCUS); |
| |
| // Run the provider, there should be one match corresponding to IPH for |
| // Enterprise search aggregator. |
| provider_->Start(input, false); |
| ACMatches matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 1u); |
| EXPECT_EQ(matches[0].type, AutocompleteMatchType::NULL_RESULT_MESSAGE); |
| EXPECT_EQ(matches[0].iph_type, IphType::kFeaturedEnterpriseSiteSearch); |
| EXPECT_EQ(matches[0].contents, |
| u"Type @ to search across featured1.com, featured2.com"); |
| |
| // Not in ZPS, the IPH should not be provided. |
| input.set_focus_type(metrics::INTERACTION_DEFAULT); |
| provider_->Start(input, false); |
| matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 0u); |
| |
| // "@" state - Confirm expected starter pack is still shown but no ZPS. |
| std::vector<TestData> typing_scheme_cases = { |
| // Typing '@' should give all the starter pack suggestions, and no IPH. |
| {u"@", |
| {kAiModeUrl, kBookmarksUrl, FeaturedUrlN(1), FeaturedUrlN(2), kGeminiUrl, |
| kHistoryUrl, kTabsUrl}}}; |
| RunTest(typing_scheme_cases); |
| } |
| |
| TEST_F(FeaturedSearchProviderTest, |
| ZeroSuggestFeaturedSiteSearchIPHSuggestion_DeleteMatch) { |
| history_embeddings::ScopedFeatureParametersForTesting feature_parameters( |
| base::BindOnce([](history_embeddings::FeatureParameters& parameters) { |
| parameters.omnibox_scoped = false; |
| })); |
| base::test::ScopedFeatureList features; |
| features.InitWithFeaturesAndParameters( |
| {{history_embeddings::kHistoryEmbeddings, {}}, |
| {omnibox::kStarterPackExpansion, {}}}, |
| {omnibox::kStarterPackIPH}); |
| |
| AddStarterPackEntriesToTemplateUrlService(); |
| |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(2), FeaturedUrlN(2), |
| TemplateURLData::PolicyOrigin::kSiteSearch); |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(1), FeaturedUrlN(1), |
| TemplateURLData::PolicyOrigin::kSiteSearch); |
| // Inactive featured enterprise keywords should not be shown or included in |
| // IPH. |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(3), FeaturedUrlN(3), |
| TemplateURLData::PolicyOrigin::kSiteSearch, |
| TemplateURLData::ActiveStatus::kFalse); |
| |
| // "Focus" omnibox with zero input to put us in Zero suggest mode. |
| AutocompleteInput input; |
| input.set_focus_type(metrics::INTERACTION_FOCUS); |
| |
| // Run the provider, there should be one match corresponding to IPH for |
| // featured Enterprise site search. |
| PrefService* prefs = client_->GetPrefs(); |
| EXPECT_FALSE(prefs->GetBoolean( |
| omnibox::kDismissedFeaturedEnterpriseSiteSearchIphPrefName)); |
| provider_->Start(input, false); |
| ACMatches matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 1u); |
| EXPECT_EQ(matches[0].type, AutocompleteMatchType::NULL_RESULT_MESSAGE); |
| EXPECT_EQ(matches[0].iph_type, IphType::kFeaturedEnterpriseSiteSearch); |
| EXPECT_EQ(matches[0].contents, |
| u"Type @ to search across featured1.com, featured2.com"); |
| |
| // Call `DeleteMatch()`, match should be deleted from `matches_` and the pref |
| // should be set to false. |
| provider_->DeleteMatch(matches[0]); |
| matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 0u); |
| EXPECT_TRUE(prefs->GetBoolean( |
| omnibox::kDismissedFeaturedEnterpriseSiteSearchIphPrefName)); |
| |
| // Run the provider again, IPH match should not be provided. |
| provider_->Start(input, false); |
| matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 0u); |
| } |
| |
| TEST_F(FeaturedSearchProviderTest, |
| ZeroSuggestStarterPackIPHAfterFeaturedSiteSearchIPHDeleted) { |
| history_embeddings::ScopedFeatureParametersForTesting feature_parameters( |
| base::BindOnce([](history_embeddings::FeatureParameters& parameters) { |
| parameters.omnibox_scoped = false; |
| })); |
| base::test::ScopedFeatureList features; |
| features.InitWithFeaturesAndParameters( |
| {{history_embeddings::kHistoryEmbeddings, {}}, |
| {omnibox::kStarterPackExpansion, {}}, |
| {omnibox::kStarterPackIPH, {}}}, |
| {}); |
| |
| AddStarterPackEntriesToTemplateUrlService(); |
| |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(2), FeaturedUrlN(2), |
| TemplateURLData::PolicyOrigin::kSiteSearch); |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(1), FeaturedUrlN(1), |
| TemplateURLData::PolicyOrigin::kSiteSearch); |
| // Inactive featured enterprise keywords should not be shown or included in |
| // IPH. |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(3), FeaturedUrlN(3), |
| TemplateURLData::PolicyOrigin::kSiteSearch, |
| TemplateURLData::ActiveStatus::kFalse); |
| |
| // "Focus" omnibox with zero input to put us in Zero suggest mode. |
| AutocompleteInput input; |
| input.set_focus_type(metrics::INTERACTION_FOCUS); |
| |
| // Run the provider, there should be one match corresponding to IPH for |
| // featured Enterprise site search. |
| PrefService* prefs = client_->GetPrefs(); |
| EXPECT_FALSE(prefs->GetBoolean( |
| omnibox::kDismissedFeaturedEnterpriseSiteSearchIphPrefName)); |
| EXPECT_FALSE(prefs->GetBoolean(omnibox::kDismissedGeminiIph)); |
| provider_->Start(input, false); |
| ACMatches matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 1u); |
| EXPECT_EQ(matches[0].type, AutocompleteMatchType::NULL_RESULT_MESSAGE); |
| EXPECT_EQ(matches[0].iph_type, IphType::kFeaturedEnterpriseSiteSearch); |
| |
| // Call `DeleteMatch()`, match should be deleted from `matches_` and the pref |
| // should be set to false. |
| provider_->DeleteMatch(matches[0]); |
| matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 0u); |
| EXPECT_TRUE(prefs->GetBoolean( |
| omnibox::kDismissedFeaturedEnterpriseSiteSearchIphPrefName)); |
| EXPECT_FALSE(prefs->GetBoolean(omnibox::kDismissedGeminiIph)); |
| |
| // Run the provider again, there should be one match corresponding to IPH for |
| // Starter Pack. |
| EXPECT_FALSE(prefs->GetBoolean(omnibox::kDismissedGeminiIph)); |
| provider_->Start(input, false); |
| matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 1u); |
| EXPECT_EQ(matches[0].type, AutocompleteMatchType::NULL_RESULT_MESSAGE); |
| EXPECT_EQ(matches[0].iph_type, IphType::kGemini); |
| |
| // Call `DeleteMatch()`, match should be deleted from `matches_` and the pref |
| // should be set to false. |
| provider_->DeleteMatch(matches[0]); |
| matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 0u); |
| EXPECT_TRUE(prefs->GetBoolean( |
| omnibox::kDismissedFeaturedEnterpriseSiteSearchIphPrefName)); |
| EXPECT_TRUE(prefs->GetBoolean(omnibox::kDismissedGeminiIph)); |
| |
| // Run the provider again, IPH match should not be provided. |
| provider_->Start(input, false); |
| matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 0u); |
| } |
| |
| TEST_F(FeaturedSearchProviderTest, |
| ZeroSuggestEnterpriseSearchAggregatorIPHSuggestion) { |
| base::test::ScopedFeatureList features; |
| features.InitWithFeatures({omnibox::kStarterPackExpansion}, |
| {omnibox::kStarterPackIPH}); |
| |
| AddStarterPackEntriesToTemplateUrlService(); |
| |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(1), FeaturedUrlN(1), |
| TemplateURLData::PolicyOrigin::kSiteSearch); |
| AddFeaturedEnterpriseSearchEngine( |
| FeaturedKeywordN(4), FeaturedUrlN(4), |
| TemplateURLData::PolicyOrigin::kSearchAggregator); |
| |
| // "Focus" omnibox with zero input to put us in Zero suggest mode. |
| AutocompleteInput input; |
| input.set_focus_type(metrics::INTERACTION_FOCUS); |
| |
| // Run the provider, there should be one match corresponding to IPH for |
| // Enterprise search aggregator. |
| provider_->Start(input, false); |
| ACMatches matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 1u); |
| EXPECT_EQ(matches[0].type, AutocompleteMatchType::NULL_RESULT_MESSAGE); |
| EXPECT_EQ(matches[0].iph_type, IphType::kEnterpriseSearchAggregator); |
| |
| // Not in ZPS, the IPH should not be provided. |
| input.set_focus_type(metrics::INTERACTION_DEFAULT); |
| provider_->Start(input, false); |
| matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 0u); |
| |
| // "@" state - Confirm expected starter pack is still shown but no ZPS. |
| std::vector<TestData> typing_scheme_cases = { |
| // Typing '@' should give all the starter pack suggestions, and no IPH. |
| {u"@", |
| {kAiModeUrl, kBookmarksUrl, FeaturedUrlN(1), FeaturedUrlN(4), kGeminiUrl, |
| kHistoryUrl, kTabsUrl}}}; |
| RunTest(typing_scheme_cases); |
| } |
| TEST_F(FeaturedSearchProviderTest, |
| ZeroSuggestEnterpriseSearchAggregatorIPHSuggestion_DeleteMatch) { |
| history_embeddings::ScopedFeatureParametersForTesting feature_parameters( |
| base::BindOnce([](history_embeddings::FeatureParameters& parameters) { |
| parameters.omnibox_scoped = false; |
| })); |
| base::test::ScopedFeatureList features; |
| features.InitWithFeaturesAndParameters( |
| {{history_embeddings::kHistoryEmbeddings, {}}, |
| {omnibox::kStarterPackExpansion, {}}}, |
| {omnibox::kStarterPackIPH}); |
| |
| AddStarterPackEntriesToTemplateUrlService(); |
| |
| AddFeaturedEnterpriseSearchEngine( |
| FeaturedKeywordN(4), FeaturedUrlN(4), |
| TemplateURLData::PolicyOrigin::kSearchAggregator); |
| |
| // "Focus" omnibox with zero input to put us in Zero suggest mode. |
| AutocompleteInput input; |
| input.set_focus_type(metrics::INTERACTION_FOCUS); |
| |
| // Run the provider, there should be one match corresponding to IPH for |
| // Enterprise search aggregator. |
| PrefService* prefs = client_->GetPrefs(); |
| EXPECT_FALSE(prefs->GetBoolean( |
| omnibox::kDismissedFeaturedEnterpriseSiteSearchIphPrefName)); |
| provider_->Start(input, false); |
| ACMatches matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 1u); |
| EXPECT_EQ(matches[0].type, AutocompleteMatchType::NULL_RESULT_MESSAGE); |
| EXPECT_EQ(matches[0].iph_type, IphType::kEnterpriseSearchAggregator); |
| |
| // Call `DeleteMatch()`, match should be deleted from `matches_` and the pref |
| // should be set to false. |
| provider_->DeleteMatch(matches[0]); |
| matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 0u); |
| EXPECT_TRUE(prefs->GetBoolean( |
| omnibox::kDismissedEnterpriseSearchAggregatorIphPrefName)); |
| |
| // Run the provider again, IPH match should not be provided. |
| provider_->Start(input, false); |
| matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 0u); |
| } |
| TEST_F( |
| FeaturedSearchProviderTest, |
| ZeroSuggestFeaturedSiteSearchIPHAfterEnterpriseSearchAggregatorIPHDeleted) { |
| history_embeddings::ScopedFeatureParametersForTesting feature_parameters( |
| base::BindOnce([](history_embeddings::FeatureParameters& parameters) { |
| parameters.omnibox_scoped = false; |
| })); |
| base::test::ScopedFeatureList features; |
| features.InitWithFeaturesAndParameters( |
| {{history_embeddings::kHistoryEmbeddings, {}}, |
| {omnibox::kStarterPackExpansion, {}}, |
| {omnibox::kStarterPackIPH, {}}}, |
| {}); |
| |
| AddStarterPackEntriesToTemplateUrlService(); |
| |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(1), FeaturedUrlN(1), |
| TemplateURLData::PolicyOrigin::kSiteSearch); |
| AddFeaturedEnterpriseSearchEngine( |
| FeaturedKeywordN(4), FeaturedUrlN(4), |
| TemplateURLData::PolicyOrigin::kSearchAggregator); |
| |
| // "Focus" omnibox with zero input to put us in Zero suggest mode. |
| AutocompleteInput input; |
| input.set_focus_type(metrics::INTERACTION_FOCUS); |
| |
| // Run the provider, there should be one match corresponding to IPH for |
| // Enterprise search aggregator. |
| PrefService* prefs = client_->GetPrefs(); |
| EXPECT_FALSE(prefs->GetBoolean( |
| omnibox::kDismissedEnterpriseSearchAggregatorIphPrefName)); |
| EXPECT_FALSE(prefs->GetBoolean( |
| omnibox::kDismissedFeaturedEnterpriseSiteSearchIphPrefName)); |
| provider_->Start(input, false); |
| ACMatches matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 1u); |
| EXPECT_EQ(matches[0].type, AutocompleteMatchType::NULL_RESULT_MESSAGE); |
| EXPECT_EQ(matches[0].iph_type, IphType::kEnterpriseSearchAggregator); |
| |
| // Call `DeleteMatch()`, match should be deleted from `matches_` and the pref |
| // should be set to false. |
| provider_->DeleteMatch(matches[0]); |
| matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 0u); |
| EXPECT_TRUE(prefs->GetBoolean( |
| omnibox::kDismissedEnterpriseSearchAggregatorIphPrefName)); |
| EXPECT_FALSE(prefs->GetBoolean( |
| omnibox::kDismissedFeaturedEnterpriseSiteSearchIphPrefName)); |
| |
| // Run the provider again, there should be one match corresponding to IPH for |
| // featured Enterprise site search. The match should not include the search |
| // aggregator keyword. |
| EXPECT_FALSE(prefs->GetBoolean( |
| omnibox::kDismissedFeaturedEnterpriseSiteSearchIphPrefName)); |
| provider_->Start(input, false); |
| matches = provider_->matches(); |
| EXPECT_EQ(matches.size(), 1u); |
| EXPECT_EQ(matches[0].type, AutocompleteMatchType::NULL_RESULT_MESSAGE); |
| EXPECT_EQ(matches[0].iph_type, IphType::kFeaturedEnterpriseSiteSearch); |
| EXPECT_EQ(matches[0].contents, u"Type @ to search across featured1.com"); |
| } |
| |
| TEST_F(FeaturedSearchProviderTest, HistoryEmbedding_Iphs) { |
| // Setup. |
| AddStarterPackEntriesToTemplateUrlService(); |
| |
| AutocompleteInput zero_input(u"", metrics::OmniboxEventProto::OTHER, |
| TestSchemeClassifier()); |
| zero_input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS); |
| AutocompleteInput non_zero_input(u"x", metrics::OmniboxEventProto::OTHER, |
| TestSchemeClassifier()); |
| AutocompleteInput scope_input(u"@history", metrics::OmniboxEventProto::OTHER, |
| TestSchemeClassifier()); |
| |
| auto mock_setting = [&](bool setting_visible, bool setting_opted_in) { |
| CHECK(!setting_opted_in || setting_visible); |
| EXPECT_CALL(*client_, IsHistoryEmbeddingsSettingVisible()) |
| .WillRepeatedly(testing::Return(setting_visible)); |
| EXPECT_CALL(*client_, IsHistoryEmbeddingsEnabled()) |
| .WillRepeatedly(testing::Return(setting_opted_in)); |
| }; |
| |
| // No IPH is shown when the feature is disabled. |
| { |
| history_embeddings::ScopedFeatureParametersForTesting feature_parameters( |
| base::BindOnce([](history_embeddings::FeatureParameters& parameters) { |
| parameters.omnibox_scoped = false; |
| })); |
| base::test::ScopedFeatureList disabled_features; |
| disabled_features.InitAndEnableFeatureWithParameters( |
| history_embeddings::kHistoryEmbeddings, {}); |
| { |
| SCOPED_TRACE(""); |
| RunAndVerifyIph(zero_input, {}); |
| } |
| { |
| SCOPED_TRACE(""); |
| RunAndVerifyIph(non_zero_input, {}); |
| } |
| { |
| SCOPED_TRACE(""); |
| RunAndVerifyIph(scope_input, {}); |
| } |
| } |
| |
| // '@history' promo is shown when embeddings is not opted-in (even if the |
| // feature is enabled). |
| { |
| base::test::ScopedFeatureList features; |
| features.InitWithFeatures({{history_embeddings::kHistoryEmbeddings}}, {}); |
| mock_setting(false, false); |
| { |
| SCOPED_TRACE(""); |
| RunAndVerifyIph(zero_input, |
| {{IphType::kHistoryScopePromo, |
| u"Type @history to search your browsing history"}}); |
| } |
| // Not shown for non-zero input. |
| { |
| SCOPED_TRACE(""); |
| RunAndVerifyIph(non_zero_input, {}); |
| } |
| |
| // '@history' AI promo is shown when embeddings is opted-in. |
| mock_setting(true, true); |
| { |
| SCOPED_TRACE(""); |
| RunAndVerifyIph( |
| zero_input, |
| {{IphType::kHistoryEmbeddingsScopePromo, |
| u"Type @history to search your browsing history, powered by AI"}}); |
| } |
| // Not shown for non-zero input. |
| { |
| SCOPED_TRACE(""); |
| RunAndVerifyIph(non_zero_input, {}); |
| } |
| |
| // chrome://settings/ai/historySearch promo shown when not opted-in and in |
| // @history scope. |
| mock_setting(true, false); |
| { |
| SCOPED_TRACE(""); |
| RunAndVerifyIph( |
| scope_input, |
| {{IphType::kHistoryEmbeddingsSettingsPromo, |
| // Should end with whitespace since there's a link following it. |
| u"For a more powerful way to search your browsing history, turn " |
| u"on ", |
| u"History search, powered by AI", |
| GURL("chrome://settings/ai/historySearch")}}); |
| } |
| // Not shown for unscoped inputs. Zero input will show the '@history' promo |
| // tested above, so just test `non_zero_input` here. |
| { |
| SCOPED_TRACE(""); |
| RunAndVerifyIph(non_zero_input, {}); |
| } |
| // Not shown if the setting isn't available. |
| mock_setting(false, false); |
| { |
| SCOPED_TRACE(""); |
| RunAndVerifyIph(scope_input, {}); |
| } |
| |
| // Disclaimer shown when opted-in and in @history scope. |
| mock_setting(true, true); |
| { |
| SCOPED_TRACE(""); |
| RunAndVerifyIph( |
| scope_input, |
| {{IphType::kHistoryEmbeddingsDisclaimer, |
| // Should end with whitespace since there's a link following it. |
| u"Your searches, best matches, and their page contents are sent to " |
| u"Google and may be seen by human reviewers to improve this " |
| u"feature. " |
| u"This is an experimental feature and won't always get it right. ", |
| u"Learn more", GURL("chrome://settings/ai/historySearch")}}); |
| } |
| // Not shown for unscoped inputs. Zero input will show the '@history' AI |
| // promo tested above, so just test `non_zero_input` here. |
| { |
| SCOPED_TRACE(""); |
| RunAndVerifyIph(non_zero_input, {}); |
| } |
| } |
| |
| // Not shown if omnibox entry is disabled, even if embeddings is overall |
| // enabled. |
| { |
| history_embeddings::ScopedFeatureParametersForTesting feature_parameters( |
| base::BindOnce([](history_embeddings::FeatureParameters& parameters) { |
| parameters.omnibox_scoped = false; |
| })); |
| base::test::ScopedFeatureList features_without_omnibox; |
| features_without_omnibox.InitAndEnableFeatureWithParameters( |
| history_embeddings::kHistoryEmbeddings, {}); |
| { |
| SCOPED_TRACE(""); |
| RunAndVerifyIph(zero_input, {}); |
| } |
| { |
| SCOPED_TRACE(""); |
| RunAndVerifyIph(non_zero_input, {}); |
| } |
| { |
| SCOPED_TRACE(""); |
| RunAndVerifyIph(scope_input, {}); |
| } |
| } |
| } |
| |
| TEST_F(FeaturedSearchProviderTest, IphShownLimit) { |
| base::test::ScopedFeatureList features; |
| features.InitWithFeatures( |
| {{omnibox::kStarterPackIPH}, {history_embeddings::kHistoryEmbeddings}}, |
| {}); |
| AddStarterPackEntriesToTemplateUrlService(); |
| AutocompleteInput input; |
| input.set_focus_type(metrics::INTERACTION_FOCUS); |
| |
| auto test = [&](const AutocompleteInput& input, |
| const std::vector<IphType> expected_iph_types) { |
| RunAndVerifyIphTypes(input, expected_iph_types); |
| // Notify the provider which matches were shown. |
| AutocompleteResult result; |
| result.AppendMatches(provider_->matches()); |
| provider_->RegisterDisplayedMatches(result); |
| }; |
| |
| // Show up to 3 IPHs per session. |
| { |
| SCOPED_TRACE(""); |
| test(input, {IphType::kGemini}); |
| } |
| { |
| SCOPED_TRACE(""); |
| test(input, {IphType::kGemini}); |
| } |
| { |
| SCOPED_TRACE(""); |
| test(input, {IphType::kGemini}); |
| } |
| { |
| SCOPED_TRACE(""); |
| test(input, {}); |
| } |
| |
| // Start a new session, should see an IPH 3 more times. But not the same IPH |
| // as before, since it already consumed its limit. |
| provider_ = new FeaturedSearchProvider(client_.get()); |
| { |
| SCOPED_TRACE(""); |
| test(input, {IphType::kHistoryScopePromo}); |
| } |
| { |
| SCOPED_TRACE(""); |
| test(input, {IphType::kHistoryScopePromo}); |
| } |
| { |
| SCOPED_TRACE(""); |
| test(input, {IphType::kHistoryScopePromo}); |
| } |
| { |
| SCOPED_TRACE(""); |
| test(input, {}); |
| } |
| } |
| |
| TEST_F(FeaturedSearchProviderTest, OffTheRecord_HistoryEmbeddings) { |
| base::test::ScopedFeatureList features; |
| features.InitWithFeatures({history_embeddings::kHistoryEmbeddings}, |
| {omnibox::kStarterPackIPH}); |
| AddStarterPackEntriesToTemplateUrlService(); |
| AutocompleteInput input; |
| input.set_focus_type(metrics::INTERACTION_FOCUS); |
| |
| // By default, the @history promo should be shown. |
| RunAndVerifyIphTypes(input, {IphType::kHistoryScopePromo}); |
| |
| // The @history scope doesn't work in Incognito or guest mode, though, so it |
| // doesn't make sense to promote it in these windows. |
| EXPECT_CALL(*client_, IsOffTheRecord()).WillRepeatedly(testing::Return(true)); |
| RunAndVerifyIphTypes(input, {}); |
| } |
| |
| TEST_F(FeaturedSearchProviderTest, OffTheRecord_FeaturedEnterpriseSearch) { |
| base::test::ScopedFeatureList features; |
| features.InitWithFeatures({omnibox::kStarterPackExpansion}, |
| {omnibox::kStarterPackIPH}); |
| AddStarterPackEntriesToTemplateUrlService(); |
| AddFeaturedEnterpriseSearchEngine(FeaturedKeywordN(1), FeaturedUrlN(1), |
| TemplateURLData::PolicyOrigin::kSiteSearch); |
| AddFeaturedEnterpriseSearchEngine( |
| FeaturedKeywordN(2), FeaturedUrlN(2), |
| TemplateURLData::PolicyOrigin::kSearchAggregator); |
| AutocompleteInput input; |
| input.set_focus_type(metrics::INTERACTION_FOCUS); |
| |
| // The enterprise search aggregator scope doesn't work in Incognito or guest |
| // mode. However, the match and IPH for enterprise site search engine should |
| // still show. |
| EXPECT_CALL(*client_, IsOffTheRecord()).WillRepeatedly(testing::Return(true)); |
| RunAndVerifyIph(input, {{IphType::kFeaturedEnterpriseSiteSearch, |
| u"Type @ to search across featured1.com"}}); |
| |
| // "@" state. |
| std::vector<TestData> typing_scheme_cases = { |
| // Typing '@' should give all the starter pack suggestions (excluding |
| // history), featured site search engine, and no IPH. |
| {u"@", |
| {kAiModeUrl, kBookmarksUrl, FeaturedUrlN(1), kGeminiUrl, kTabsUrl}}}; |
| RunTest(typing_scheme_cases); |
| } |