#ifdef UNSAFE_BUFFERS_BUILD
#pragma allow_unsafe_buffers
#endif
#include "components/omnibox/browser/autocomplete_result.h"
#include <stddef.h>
#include <iterator>
#include <memory>
#include <string>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_params.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.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 "build/build_config.h"
#include "components/omnibox/browser/actions/omnibox_action_in_suggest.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_provider.h"
#include "components/omnibox/browser/autocomplete_provider_client.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/intranet_redirector_state.h"
#include "components/omnibox/browser/omnibox_field_trial.h"
#include "components/omnibox/browser/omnibox_prefs.h"
#include "components/omnibox/browser/tab_matcher.h"
#include "components/omnibox/browser/test_scheme_classifier.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/search_engines/search_engines_test_environment.h"
#include "components/search_engines/template_url_service.h"
#include "components/variations/variations_associated_data.h"
#include "omnibox_focus_type.pb.h"
#include "omnibox_triggered_feature_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/metrics_proto/omnibox_event.pb.h"
#include "third_party/omnibox_proto/entity_info.pb.h"
#include "third_party/omnibox_proto/groups.pb.h"
#include "third_party/omnibox_proto/types.pb.h"
#include "ui/base/device_form_factor.h"
OmniboxEventProto;
namespace {
class FakeOmniboxAction : public OmniboxAction { … };
struct AutocompleteMatchTestData { … };
template <typename T>
void PopulateAutocompleteMatchesFromTestData(const T* data,
size_t count,
ACMatches* matches) { … }
struct CullTailTestMatch { … };
}
class AutocompleteResultForTesting : public AutocompleteResult { … };
class AutocompleteResultTest : public testing::Test { … };
AutocompleteMatch AutocompleteResultTest::PopulateAutocompleteMatch(
const TestData& data) { … }
void AutocompleteResultTest::PopulateAutocompleteMatches(const TestData* data,
size_t count,
ACMatches* matches) { … }
ACMatches AutocompleteResultTest::PopulateAutocompleteMatches(
const std::vector<TestData>& data) { … }
void AutocompleteResultTest::AssertResultMatches(
const AutocompleteResult& result,
base::span<const TestData> expected) { … }
void AutocompleteResultTest::AssertMatch(AutocompleteMatch match,
const TestData& expected_match_data,
int i) { … }
void AutocompleteResultTest::RunTransferOldMatchesTest(const TestData* last,
size_t last_size,
const TestData* current,
size_t current_size,
const TestData* expected,
size_t expected_size) { … }
void AutocompleteResultTest::RunTransferOldMatchesTest(
const TestData* last,
size_t last_size,
const TestData* current,
size_t current_size,
const TestData* expected,
size_t expected_size,
AutocompleteInput input) { … }
void AutocompleteResultTest::SortMatchesAndVerifyOrder(
const std::string& input_text,
OmniboxEventProto::PageClassification page_classification,
const ACMatches& matches,
const std::vector<size_t>& expected_order,
const AutocompleteMatchTestData data[]) { … }
TEST_F(AutocompleteResultTest, SwapMatches) { … }
TEST_F(AutocompleteResultTest, AlternateNavUrl) { … }
TEST_F(AutocompleteResultTest, AlternateNavUrl_IntranetRedirectPolicy) { … }
TEST_F(AutocompleteResultTest, TransferOldMatches) { … }
TEST_F(AutocompleteResultTest, TransferOldMatchesAllowedToBeDefault) { … }
TEST_F(AutocompleteResultTest,
TransferOldMatchesAllowedToBeDefaultWithPreventInlineAutocompletion) { … }
TEST_F(AutocompleteResultTest, TransferOldMatchesMultipleProviders) { … }
TEST_F(AutocompleteResultTest,
TransferOldMatchesWithOneProviderWithoutDefault) { … }
TEST_F(AutocompleteResultTest, TransferOldMatchesSkipsSpecializedSuggestions) { … }
TEST_F(AutocompleteResultTest, TransferOldMatchesSkipDoneProviders) { … }
TEST_F(AutocompleteResultTest, SortAndCullEmptyDestinationURLs) { … }
#if !(BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS))
TEST_F(AutocompleteResultTest, SortAndCullTailSuggestions) { … }
TEST_F(AutocompleteResultTest, SortAndCullKeepDefaultTailSuggestions) { … }
TEST_F(AutocompleteResultTest, SortAndCullKeepMoreDefaultTailSuggestions) { … }
TEST_F(AutocompleteResultTest, SortAndCullZeroRelevanceSuggestions) { … }
TEST_F(AutocompleteResultTest, SortAndCullZeroRelevanceDefaultMatches) { … }
#endif
TEST_F(AutocompleteResultTest, SortAndCullOnlyTailSuggestions) { … }
TEST_F(AutocompleteResultTest, SortAndCullNoMatchesAllowedToBeDefault) { … }
TEST_F(AutocompleteResultTest, SortAndCullDuplicateSearchURLs) { … }
TEST_F(AutocompleteResultTest, SortAndCullWithMatchDups) { … }
TEST_F(AutocompleteResultTest, SortAndCullWithDemotionsByType) { … }
TEST_F(AutocompleteResultTest, SortAndCullWithPreserveDefaultMatch) { … }
TEST_F(AutocompleteResultTest, DemoteOnDeviceSearchSuggestions) { … }
TEST_F(AutocompleteResultTest, DemoteByType) { … }
TEST_F(AutocompleteResultTest, SortAndCullReorderForDefaultMatch) { … }
#if DCHECK_IS_ON()
TEST_F(AutocompleteResultTest, SortAndCullFailsWithIncorrectDefaultScheme) { … }
#endif
TEST_F(AutocompleteResultTest, SortAndCullPermitSearchForSchemeMatching) { … }
TEST_F(AutocompleteResultTest, SortAndCullPromoteDefaultMatch) { … }
TEST_F(AutocompleteResultTest, SortAndCullPromoteUnconsecutiveMatches) { … }
struct EntityTestData { … };
void PopulateEntityTestCases(std::vector<EntityTestData>& test_cases,
ACMatches* matches) { … }
TEST_F(AutocompleteResultTest, SortAndCullPreferEntities) { … }
TEST_F(AutocompleteResultTest,
SortAndCullPreferNonEntitiesForDefaultSuggestion) { … }
TEST_F(AutocompleteResultTest,
SortAndCullDontPreferNonEntityNonDefaultForDefaultSuggestion) { … }
TEST_F(AutocompleteResultTest, SortAndCullPreferEntitiesFillIntoEditMustMatch) { … }
TEST_F(AutocompleteResultTest,
SortAndCullPreferEntitiesButKeepDefaultPlainMatches) { … }
TEST_F(
AutocompleteResultTest,
SortAndCullPreferNonEntitySpecializedSearchSuggestionForDefaultSuggestion) { … }
TEST_F(AutocompleteResultTest, SortAndCullPromoteDuplicateSearchURLs) { … }
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
TEST_F(AutocompleteResultTest, SortAndCullFeaturedSearchBeforeStarterPack) { … }
#endif
TEST_F(AutocompleteResultTest,
GroupSuggestionsBySearchVsURLHonorsProtectedSuggestions) { … }
TEST_F(AutocompleteResultTest, SortAndCullMaxHistoryClusterSuggestions) { … }
TEST_F(AutocompleteResultTest, SortAndCullMaxURLMatches) { … }
TEST_F(AutocompleteResultTest, ConvertsOpenTabsCorrectly) { … }
TEST_F(AutocompleteResultTest, AttachesPedals) { … }
TEST_F(AutocompleteResultTest, DocumentSuggestionsCanMergeButNotToDefault) { … }
TEST_F(AutocompleteResultTest, CalculateNumMatchesPerUrlCountTest) { … }
TEST_F(AutocompleteResultTest, ClipboardSuggestionOnTopOfSearchSuggestionTest) { … }
TEST_F(AutocompleteResultTest, MaybeCullTailSuggestions) { … }
#if !(BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS))
void VerifyTriggeredFeatures(
OmniboxTriggeredFeatureService* triggered_feature_service,
std::vector<OmniboxTriggeredFeatureService::Feature>
expected_triggered_features) { … }
TEST_F(AutocompleteResultTest, Desktop_TwoColumnRealbox) { … }
TEST_F(AutocompleteResultTest, Desktop_ZpsGroupingIPH) { … }
TEST_F(AutocompleteResultTest, SplitActionsToSuggestions) { … }
#endif
#if BUILDFLAG(IS_ANDROID)
TEST_F(AutocompleteResultTest, Android_InspireMe) {
const auto group1 = omnibox::GROUP_PERSONALIZED_ZERO_SUGGEST;
const auto group2 = omnibox::GROUP_TRENDS;
const auto group3 = omnibox::GROUP_PREVIOUS_SEARCH_RELATED;
TestData data[] = {
{0, 1, 500, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1},
{1, 1, 490, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1},
{2, 1, 480, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1},
{3, 1, 470, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group2},
{4, 1, 460, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group2},
{5, 1, 450, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3},
{6, 1, 440, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3},
};
ACMatches matches;
PopulateAutocompleteMatches(data, std::size(data), &matches);
omnibox::GroupConfigMap suggestion_groups_map;
suggestion_groups_map[group1];
suggestion_groups_map[group2];
suggestion_groups_map[group3];
AutocompleteInput zero_input(u"", metrics::OmniboxEventProto::NTP,
TestSchemeClassifier());
zero_input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS);
{
SCOPED_TRACE("Inspire Me Passes Only Trending Queries");
AutocompleteResult result;
result.MergeSuggestionGroupsMap(suggestion_groups_map);
result.AppendMatches(matches);
result.SortAndCull(zero_input, &template_url_service(),
triggered_feature_service());
const std::array<TestData, 5> expected_data{{
{1, 1, 490, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1},
{0, 1, 500, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1},
{2, 1, 480, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1},
{3, 1, 470, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group2},
{4, 1, 460, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group2},
}};
AssertResultMatches(result, expected_data);
}
}
#endif
TEST_F(AutocompleteResultTest, Android_UndedupTopSearch) { … }
#if BUILDFLAG(IS_IOS)
TEST_F(AutocompleteResultTest, IOS_InspireMe) {
const auto group1 = omnibox::GROUP_PERSONALIZED_ZERO_SUGGEST;
const auto group2 = omnibox::GROUP_TRENDS;
TestData data[] = {
{0, 1, 500, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1},
{1, 1, 490, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1},
{2, 1, 480, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1},
{3, 1, 470, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group2},
{4, 1, 460, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group2},
};
ACMatches matches;
PopulateAutocompleteMatches(data, std::size(data), &matches);
omnibox::GroupConfigMap suggestion_groups_map;
suggestion_groups_map[group1];
suggestion_groups_map[group2];
AutocompleteInput zero_input(u"", metrics::OmniboxEventProto::NTP,
TestSchemeClassifier());
zero_input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS);
{
SCOPED_TRACE("Trend suggestions are only available on iPhones");
base::test::ScopedFeatureList feature_list;
AutocompleteResult result;
result.MergeSuggestionGroupsMap(suggestion_groups_map);
result.AppendMatches(matches);
result.SortAndCull(zero_input, &template_url_service(),
triggered_feature_service());
if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
const std::array<TestData, 3> expected_data{{
{0, 1, 500, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1},
{1, 1, 490, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1},
{2, 1, 480, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1},
}};
AssertResultMatches(result, expected_data);
} else {
const std::array<TestData, 5> expected_data{{
{0, 1, 500, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1},
{1, 1, 490, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1},
{2, 1, 480, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1},
{3, 1, 470, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group2},
{4, 1, 460, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group2},
}};
AssertResultMatches(result, expected_data);
}
}
}
#endif
#if (BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS))
TEST_F(AutocompleteResultTest, Mobile_TrimOmniboxActions) {
scoped_refptr<FakeAutocompleteProvider> provider =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_SEARCH);
using OmniboxActionId::ACTION_IN_SUGGEST;
using OmniboxActionId::ANSWER_ACTION;
using OmniboxActionId::PEDAL;
using OmniboxActionId::UNKNOWN;
const std::set<OmniboxActionId> all_actions_to_test{ACTION_IN_SUGGEST, PEDAL};
struct FilterOmniboxActionsTestData {
std::string test_name;
std::vector<std::vector<OmniboxActionId>> input_matches_and_actions;
std::vector<std::vector<OmniboxActionId>> result_matches_and_actions_zps;
std::vector<std::vector<OmniboxActionId>> result_matches_and_actions_typed;
bool include_url = false;
} test_cases[]{
{"No actions attached to matches",
{{}, {}, {}, {}},
{{}, {}, {}, {}},
{{}, {}, {}, {}}},
{"Pedals shown only in top three slots",
{{PEDAL}, {PEDAL}, {PEDAL}, {PEDAL}},
{{PEDAL}, {PEDAL}, {PEDAL}, {}},
{{PEDAL}, {PEDAL}, {PEDAL}, {}}},
{"Actions are shown only in first position",
{{ACTION_IN_SUGGEST},
{ACTION_IN_SUGGEST},
{ACTION_IN_SUGGEST},
{ACTION_IN_SUGGEST}},
{{}, {}, {}, {}},
{{ACTION_IN_SUGGEST}, {}, {}, {}}},
{"Actions are promoted over Pedals; positions dictate preference",
{{ACTION_IN_SUGGEST, PEDAL},
{ACTION_IN_SUGGEST, PEDAL},
{ACTION_IN_SUGGEST, PEDAL},
{ACTION_IN_SUGGEST, PEDAL}},
{{PEDAL}, {PEDAL}, {PEDAL}, {}},
{{ACTION_IN_SUGGEST}, {PEDAL}, {PEDAL}, {}}},
{"Actions are promoted over History clusters; positions dictate "
"preference",
{{ACTION_IN_SUGGEST, PEDAL},
{ACTION_IN_SUGGEST, PEDAL},
{ACTION_IN_SUGGEST, PEDAL},
{ACTION_IN_SUGGEST, PEDAL}},
{{PEDAL}, {PEDAL}, {PEDAL}, {}},
{{ACTION_IN_SUGGEST}, {PEDAL}, {PEDAL}, {}}},
{"Answer actions promoted over pedals; can go in any position",
{{ANSWER_ACTION, PEDAL},
{ANSWER_ACTION, PEDAL},
{ANSWER_ACTION, PEDAL},
{ANSWER_ACTION, PEDAL}},
{{ANSWER_ACTION}, {ANSWER_ACTION}, {ANSWER_ACTION}, {ANSWER_ACTION}},
{{ANSWER_ACTION}, {ANSWER_ACTION}, {ANSWER_ACTION}, {ANSWER_ACTION}}},
{"Answer actions suppressed when there are urls",
{{PEDAL, ANSWER_ACTION},
{ANSWER_ACTION},
{ANSWER_ACTION},
{ANSWER_ACTION}},
{{PEDAL}, {}, {}, {}},
{{PEDAL}, {}, {}, {}},
true},
};
auto run_test = [&](const FilterOmniboxActionsTestData& data) {
AutocompleteResult zps_result;
AutocompleteResult typed_result;
for (const auto& actions : data.input_matches_and_actions) {
AutocompleteMatch match(
provider.get(), 1, false,
data.include_url ? AutocompleteMatchType::URL_WHAT_YOU_TYPED
: AutocompleteMatchType::SEARCH_SUGGEST_ENTITY);
for (auto& action_id : actions) {
if (action_id == OmniboxActionId::ACTION_IN_SUGGEST) {
omnibox::ActionInfo info;
info.set_action_type(omnibox::ActionInfo_ActionType_DIRECTIONS);
match.actions.push_back(base::MakeRefCounted<OmniboxActionInSuggest>(
std::move(info), std::nullopt));
} else {
match.actions.push_back(
base::MakeRefCounted<FakeOmniboxAction>(action_id));
}
}
zps_result.AppendMatches({match});
typed_result.AppendMatches({match});
}
auto check_results =
[&](AutocompleteResult& result,
std::vector<std::vector<OmniboxActionId>> expected_actions) {
EXPECT_EQ(result.size(), expected_actions.size())
<< "while testing variant: " << data.test_name;
for (size_t index = 0u; index < result.size(); ++index) {
const auto* match = result.match_at(index);
const auto& expected_actions_at_position = expected_actions[index];
EXPECT_EQ(match->actions.size(),
expected_actions_at_position.size());
for (size_t action_index = 0u;
action_index < expected_actions_at_position.size();
++action_index) {
EXPECT_EQ(expected_actions_at_position[action_index],
match->actions[action_index]->ActionId())
<< "match " << index << "action " << action_index
<< " while testing variant: " << data.test_name;
}
}
};
zps_result.TrimOmniboxActions(true);
check_results(zps_result, data.result_matches_and_actions_zps);
typed_result.TrimOmniboxActions(false);
check_results(typed_result, data.result_matches_and_actions_typed);
};
for (const auto& test_case : test_cases) {
run_test(test_case);
}
}
#endif