| // Copyright 2014 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_result.h" | 
 |  | 
 | #include <stddef.h> | 
 |  | 
 | #include <algorithm> | 
 | #include <array> | 
 | #include <iterator> | 
 | #include <memory> | 
 | #include <string> | 
 | #include <vector> | 
 |  | 
 | #include "base/compiler_specific.h" | 
 | #include "base/containers/span.h" | 
 | #include "base/memory/raw_ptr.h" | 
 | #include "base/metrics/field_trial.h" | 
 | #include "base/metrics/field_trial_params.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/strings/utf_string_conversions.h" | 
 | #include "base/test/gtest_util.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_feature_configs.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/groups.pb.h" | 
 | #include "third_party/omnibox_proto/suggest_template_info.pb.h" | 
 | #include "third_party/omnibox_proto/types.pb.h" | 
 | #include "ui/base/device_form_factor.h" | 
 |  | 
 | using metrics::OmniboxEventProto; | 
 |  | 
 | namespace { | 
 | class FakeOmniboxAction : public OmniboxAction { | 
 |  public: | 
 |   explicit FakeOmniboxAction(OmniboxActionId id) | 
 |       : OmniboxAction(LabelStrings(u"", u"", u"", u""), GURL{}), id_(id) {} | 
 |   OmniboxActionId ActionId() const override { return id_; } | 
 |  | 
 |  private: | 
 |   ~FakeOmniboxAction() override = default; | 
 |   OmniboxActionId id_{}; | 
 | }; | 
 |  | 
 | struct AutocompleteMatchTestData { | 
 |   std::string destination_url; | 
 |   AutocompleteMatch::Type type; | 
 | }; | 
 |  | 
 | // Adds |count| AutocompleteMatches to |matches|. | 
 | template <typename T> | 
 | void PopulateAutocompleteMatchesFromTestData(const T* data, | 
 |                                              size_t count, | 
 |                                              ACMatches* matches) { | 
 |   static_assert(std::is_base_of<AutocompleteMatchTestData, T>::value, | 
 |                 "T must derive from AutocompleteMatchTestData"); | 
 |   ASSERT_TRUE(matches != nullptr); | 
 |   for (size_t i = 0; i < count; ++i) { | 
 |     AutocompleteMatch match; | 
 |     match.destination_url = GURL(UNSAFE_TODO(data[i]).destination_url); | 
 |     match.relevance = | 
 |         matches->empty() ? 1300 : (matches->back().relevance - 100); | 
 |     match.allowed_to_be_default_match = true; | 
 |     match.type = UNSAFE_TODO(data[i]).type; | 
 |     matches->push_back(match); | 
 |   } | 
 | } | 
 |  | 
 | // Basic match representation for testing `MaybeCullTailSuggestions()`. | 
 | // Defined externally to allow for `PrintTo()`. | 
 | struct CullTailTestMatch { | 
 |   std::u16string id; | 
 |   AutocompleteMatchType::Type type; | 
 |   bool allowed_default; | 
 |  | 
 |   bool operator==(const CullTailTestMatch& other) const { | 
 |     return id == other.id && type == other.type && | 
 |            allowed_default == other.allowed_default; | 
 |   } | 
 |  | 
 |   // To help `EXPECT_THAT` pretty print `CullTailTestMatch`s. | 
 |   friend void PrintTo(const CullTailTestMatch& match, std::ostream* os) { | 
 |     *os << match.id << " " << match.type << " " << match.allowed_default; | 
 |   } | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | class AutocompleteResultForTesting : public AutocompleteResult { | 
 |  public: | 
 |   using AutocompleteResult::DemoteOnDeviceSearchSuggestions; | 
 |   using AutocompleteResult::matches_; | 
 |   using AutocompleteResult::MaybeCullTailSuggestions; | 
 | }; | 
 |  | 
 | class AutocompleteResultTest : public testing::Test { | 
 |  public: | 
 |   struct TestData { | 
 |     // Used to build a URL for the AutocompleteMatch. The URL becomes | 
 |     // "http://" + ('a' + |url_id|) (e.g. an ID of 2 yields "http://c"). | 
 |     int url_id; | 
 |  | 
 |     // ID of the provider. | 
 |     int provider_id; | 
 |  | 
 |     // Relevance score. | 
 |     int relevance; | 
 |  | 
 |     // Allowed to be default match status. | 
 |     bool allowed_to_be_default_match; | 
 |  | 
 |     // Duplicate matches. | 
 |     std::vector<AutocompleteMatch> duplicate_matches; | 
 |  | 
 |     // Type of the match | 
 |     AutocompleteMatchType::Type type{AutocompleteMatchType::SEARCH_SUGGEST}; | 
 |  | 
 |     // Suggestion Group ID for this suggestion | 
 |     std::optional<omnibox::GroupId> suggestion_group_id; | 
 |  | 
 |     // Inline autocompletion. | 
 |     std::string inline_autocompletion; | 
 |  | 
 |     IphType iph_type = IphType::kNone; | 
 |   }; | 
 |  | 
 |   AutocompleteResultTest() { | 
 |     variations::testing::ClearAllVariationParams(); | 
 |  | 
 |     // Create the list of mock providers. 6 is enough. | 
 |     mock_provider_list_.push_back(new FakeAutocompleteProvider( | 
 |         AutocompleteProvider::Type::TYPE_HISTORY_QUICK)); | 
 |     mock_provider_list_.push_back(new FakeAutocompleteProvider( | 
 |         AutocompleteProvider::Type::TYPE_HISTORY_URL)); | 
 |     mock_provider_list_.push_back( | 
 |         new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_SEARCH)); | 
 |     mock_provider_list_.push_back(new FakeAutocompleteProvider( | 
 |         AutocompleteProvider::Type::TYPE_ON_DEVICE_HEAD)); | 
 |     mock_provider_list_.push_back(new FakeAutocompleteProvider( | 
 |         AutocompleteProvider::Type::TYPE_FEATURED_SEARCH)); | 
 |     mock_provider_list_.push_back(new FakeAutocompleteProvider( | 
 |         AutocompleteProvider::Type::TYPE_UNSCOPED_EXTENSION)); | 
 |  | 
 |     for (const auto& provider : mock_provider_list_) | 
 |       provider->done_ = false; | 
 |  | 
 |     template_url_service().Load(); | 
 |   } | 
 |   AutocompleteResultTest(const AutocompleteResultTest&) = delete; | 
 |   AutocompleteResultTest& operator=(const AutocompleteResultTest&) = delete; | 
 |  | 
 |   void TearDown() override { task_environment_.RunUntilIdle(); } | 
 |  | 
 |   // Converts `TestData` to `AutocompleteMatch`. | 
 |   AutocompleteMatch PopulateAutocompleteMatch(const TestData& data); | 
 |  | 
 |   // Adds |count| AutocompleteMatches to |matches|. | 
 |   void PopulateAutocompleteMatches(base::span<const TestData> data, | 
 |                                    ACMatches* matches); | 
 |   ACMatches PopulateAutocompleteMatches(const std::vector<TestData>& data); | 
 |  | 
 |   // Asserts that |result| has |expected_count| matches matching |expected|. | 
 |   void AssertResultMatches(const AutocompleteResult& result, | 
 |                            base::span<const TestData> expected); | 
 |  | 
 |   void AssertMatch(AutocompleteMatch match, | 
 |                    const TestData& expected_match_data, | 
 |                    int i); | 
 |  | 
 |   // Creates an AutocompleteResult from |last| and |current|. The two are | 
 |   // merged by |TransferOldMatches| and compared by |AssertResultMatches|. | 
 |   void RunTransferOldMatchesTest(base::span<const TestData> last, | 
 |                                  base::span<const TestData> current, | 
 |                                  const TestData* expected, | 
 |                                  size_t expected_size); | 
 |   void RunTransferOldMatchesTest(base::span<const TestData> last, | 
 |                                  base::span<const TestData> current, | 
 |                                  const TestData* expected, | 
 |                                  size_t expected_size, | 
 |                                  AutocompleteInput input); | 
 |  | 
 |   void SortMatchesAndVerifyOrder( | 
 |       const std::string& input_text, | 
 |       OmniboxEventProto::PageClassification page_classification, | 
 |       const ACMatches& matches, | 
 |       const std::vector<size_t>& expected_order, | 
 |       const AutocompleteMatchTestData data[]); | 
 |  | 
 |   // Returns a (mock) AutocompleteProvider of given |provider_id|. | 
 |   FakeAutocompleteProvider* GetProvider(int provider_id) { | 
 |     EXPECT_LT(provider_id, static_cast<int>(mock_provider_list_.size())); | 
 |     return mock_provider_list_[provider_id].get(); | 
 |   } | 
 |  | 
 |   OmniboxTriggeredFeatureService* triggered_feature_service() { | 
 |     return &triggered_feature_service_; | 
 |   } | 
 |  | 
 |   TemplateURLService& template_url_service() { | 
 |     return *search_engines_test_environment_.template_url_service(); | 
 |   } | 
 |  | 
 |  protected: | 
 |   search_engines::SearchEnginesTestEnvironment search_engines_test_environment_; | 
 |  | 
 |  private: | 
 |   base::test::TaskEnvironment task_environment_; | 
 |   OmniboxTriggeredFeatureService triggered_feature_service_; | 
 |  | 
 |   // For every provider mentioned in TestData, we need a mock provider. | 
 |   std::vector<scoped_refptr<FakeAutocompleteProvider>> mock_provider_list_; | 
 | }; | 
 |  | 
 | AutocompleteMatch AutocompleteResultTest::PopulateAutocompleteMatch( | 
 |     const TestData& data) { | 
 |   AutocompleteMatch match; | 
 |   match.provider = GetProvider(data.provider_id); | 
 |   match.type = data.type; | 
 |   match.fill_into_edit = base::NumberToString16(data.url_id); | 
 |   std::string url_id(1, data.url_id + 'a'); | 
 |   match.destination_url = GURL("http://" + url_id); | 
 |   match.relevance = data.relevance; | 
 |   match.allowed_to_be_default_match = data.allowed_to_be_default_match; | 
 |   match.duplicate_matches = data.duplicate_matches; | 
 |   if (data.suggestion_group_id.has_value()) { | 
 |     match.suggestion_group_id = data.suggestion_group_id.value(); | 
 |   } | 
 |   match.inline_autocompletion = base::UTF8ToUTF16(data.inline_autocompletion); | 
 |   match.iph_type = data.iph_type; | 
 |   return match; | 
 | } | 
 |  | 
 | void AutocompleteResultTest::PopulateAutocompleteMatches( | 
 |     base::span<const TestData> data, | 
 |     ACMatches* matches) { | 
 |   for (size_t i = 0; i < data.size(); ++i) { | 
 |     matches->push_back(PopulateAutocompleteMatch(data[i])); | 
 |   } | 
 | } | 
 |  | 
 | ACMatches AutocompleteResultTest::PopulateAutocompleteMatches( | 
 |     const std::vector<TestData>& data) { | 
 |   ACMatches matches; | 
 |   for (const auto& d : data) | 
 |     matches.push_back(PopulateAutocompleteMatch(d)); | 
 |   return matches; | 
 | } | 
 |  | 
 | void AutocompleteResultTest::AssertResultMatches( | 
 |     const AutocompleteResult& result, | 
 |     base::span<const TestData> expected) { | 
 |   std::vector<int> relevances = {}; | 
 |   std::ranges::transform( | 
 |       expected, std::back_inserter(relevances), | 
 |       [](const auto& test_data) { return test_data.relevance; }); | 
 |   std::vector<int> expected_relevances = {}; | 
 |   std::ranges::transform( | 
 |       result, std::back_inserter(expected_relevances), | 
 |       [&](const AutocompleteMatch& match) { return match.relevance; }); | 
 |   EXPECT_THAT(relevances, testing::ElementsAreArray(expected_relevances)); | 
 | } | 
 |  | 
 | void AutocompleteResultTest::AssertMatch(AutocompleteMatch match, | 
 |                                          const TestData& expected_match_data, | 
 |                                          int i) { | 
 |   AutocompleteMatch expected_match = | 
 |       PopulateAutocompleteMatch(expected_match_data); | 
 |   EXPECT_EQ(expected_match.provider, match.provider) << i; | 
 |   EXPECT_EQ(expected_match.type, match.type) << i; | 
 |   EXPECT_EQ(expected_match.relevance, match.relevance) << i; | 
 |   EXPECT_EQ(expected_match.allowed_to_be_default_match, | 
 |             match.allowed_to_be_default_match) | 
 |       << i; | 
 |   EXPECT_EQ(expected_match.destination_url.spec(), match.destination_url.spec()) | 
 |       << i; | 
 |   EXPECT_EQ(expected_match.inline_autocompletion, match.inline_autocompletion) | 
 |       << i; | 
 | } | 
 |  | 
 | void AutocompleteResultTest::RunTransferOldMatchesTest( | 
 |     base::span<const TestData> last, | 
 |     base::span<const TestData> current, | 
 |     const TestData* expected, | 
 |     size_t expected_size) { | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   RunTransferOldMatchesTest(last, current, expected, expected_size, input); | 
 | } | 
 |  | 
 | void AutocompleteResultTest::RunTransferOldMatchesTest( | 
 |     base::span<const TestData> last, | 
 |     base::span<const TestData> current, | 
 |     const TestData* expected, | 
 |     size_t expected_size, | 
 |     AutocompleteInput input) { | 
 |   ACMatches last_matches; | 
 |   PopulateAutocompleteMatches(last, &last_matches); | 
 |   AutocompleteResult last_result; | 
 |   last_result.AppendMatches(last_matches); | 
 |   last_result.SortAndCull( | 
 |       input, &template_url_service(), triggered_feature_service(), | 
 |       /*is_lens_active=*/false, /*can_show_contextual_suggestions=*/false, | 
 |       /*mia_enabled*/ false); | 
 |  | 
 |   ACMatches current_matches; | 
 |   PopulateAutocompleteMatches(current, ¤t_matches); | 
 |   AutocompleteResult current_result; | 
 |   current_result.AppendMatches(current_matches); | 
 |   current_result.SortAndCull( | 
 |       input, &template_url_service(), triggered_feature_service(), | 
 |       /*is_lens_active=*/false, /*can_show_contextual_suggestions=*/false, | 
 |       /*mia_enabled*/ false); | 
 |   current_result.TransferOldMatches(input, &last_result); | 
 |   current_result.SortAndCull( | 
 |       input, &template_url_service(), triggered_feature_service(), | 
 |       /*is_lens_active=*/false, /*can_show_contextual_suggestions=*/false, | 
 |       /*mia_enabled*/ false); | 
 |  | 
 |   AssertResultMatches(current_result, UNSAFE_TODO({expected, expected_size})); | 
 | } | 
 |  | 
 | 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[]) { | 
 |   AutocompleteInput input(base::ASCIIToUTF16(input_text), page_classification, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   std::vector<std::string> expected; | 
 |   std::ranges::transform( | 
 |       expected_order, std::back_inserter(expected), | 
 |       [&](size_t i) { return UNSAFE_TODO(data[i]).destination_url; }); | 
 |   std::vector<std::string> actual; | 
 |   std::ranges::transform( | 
 |       result, std::back_inserter(actual), | 
 |       [](const auto& match) { return match.destination_url.spec(); }); | 
 |   EXPECT_THAT(actual, testing::ElementsAreArray(expected)); | 
 | } | 
 |  | 
 | // Assertion testing for AutocompleteResult::SwapMatchesWith. | 
 | TEST_F(AutocompleteResultTest, SwapMatches) { | 
 |   AutocompleteResult r1; | 
 |   AutocompleteResult r2; | 
 |  | 
 |   // Swap with empty shouldn't do anything interesting. | 
 |   r1.SwapMatchesWith(&r2); | 
 |   EXPECT_FALSE(r1.default_match()); | 
 |   EXPECT_FALSE(r2.default_match()); | 
 |  | 
 |   // Swap with a single match. | 
 |   ACMatches matches; | 
 |   AutocompleteMatch match; | 
 |   match.relevance = 1; | 
 |   match.allowed_to_be_default_match = true; | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   matches.push_back(match); | 
 |   r1.AppendMatches(matches); | 
 |   r1.set_smart_compose_inline_hint("smart compose!"); | 
 |   r1.SortAndCull(input, &template_url_service(), triggered_feature_service(), | 
 |                  /*is_lens_active=*/false, | 
 |                  /*can_show_contextual_suggestions=*/false, | 
 |                  /*mia_enabled*/ false); | 
 |   EXPECT_TRUE(r1.default_match()); | 
 |   EXPECT_EQ(&*r1.begin(), r1.default_match()); | 
 |  | 
 |   r1.SwapMatchesWith(&r2); | 
 |   EXPECT_TRUE(r1.empty()); | 
 |   EXPECT_FALSE(r1.default_match()); | 
 |   EXPECT_TRUE(r1.smart_compose_inline_hint().empty()); | 
 |   ASSERT_FALSE(r2.empty()); | 
 |   EXPECT_TRUE(r2.default_match()); | 
 |   EXPECT_EQ(&*r2.begin(), r2.default_match()); | 
 |   EXPECT_EQ("smart compose!", r2.smart_compose_inline_hint()); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, AlternateNavUrl) { | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |  | 
 |   FakeAutocompleteProviderClient client; | 
 |   reinterpret_cast<TestingPrefServiceSimple*>(client.GetLocalState()) | 
 |       ->registry() | 
 |       ->RegisterIntegerPref(omnibox::kIntranetRedirectBehavior, 0); | 
 |  | 
 |   // Against search matches, we should not generate an alternate nav URL, unless | 
 |   // overriden by policy, tested in AlternateNavUrl_IntranetRedirectPolicy | 
 |   // below. | 
 |   { | 
 |     AutocompleteMatch match; | 
 |     match.type = AutocompleteMatchType::SEARCH_SUGGEST; | 
 |     match.destination_url = GURL("http://www.foo.com/s?q=foo"); | 
 |     GURL alternate_nav_url = | 
 |         AutocompleteResult::ComputeAlternateNavUrl(input, match, &client); | 
 |     EXPECT_FALSE(alternate_nav_url.is_valid()); | 
 |   } | 
 |  | 
 |   // Against matching URL matches, we should never generate an alternate nav | 
 |   // URL. | 
 |   { | 
 |     AutocompleteMatch match; | 
 |     match.type = AutocompleteMatchType::SEARCH_SUGGEST; | 
 |     match.destination_url = GURL("http://a/"); | 
 |     GURL alternate_nav_url = | 
 |         AutocompleteResult::ComputeAlternateNavUrl(input, match, &client); | 
 |     EXPECT_FALSE(alternate_nav_url.is_valid()); | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, AlternateNavUrl_IntranetRedirectPolicy) { | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |  | 
 |   FakeAutocompleteProviderClient client; | 
 |   reinterpret_cast<TestingPrefServiceSimple*>(client.GetLocalState()) | 
 |       ->registry() | 
 |       ->RegisterIntegerPref(omnibox::kIntranetRedirectBehavior, 0); | 
 |  | 
 |   // Allow alternate nav URLs when policy allows. | 
 |   { | 
 |     client.GetLocalState()->SetInteger( | 
 |         omnibox::kIntranetRedirectBehavior, | 
 |         static_cast<int>(omnibox::IntranetRedirectorBehavior:: | 
 |                              ENABLE_INTERCEPTION_CHECKS_AND_INFOBARS)); | 
 |  | 
 |     AutocompleteMatch match; | 
 |     match.type = AutocompleteMatchType::SEARCH_SUGGEST; | 
 |     match.destination_url = GURL("http://www.foo.com/s?q=foo"); | 
 |     GURL alternate_nav_url = | 
 |         AutocompleteResult::ComputeAlternateNavUrl(input, match, &client); | 
 |     EXPECT_EQ("http://a/", alternate_nav_url.spec()); | 
 |   } | 
 |  | 
 |   // Disallow alternate nav URLs when policy disallows. | 
 |   { | 
 |     client.GetLocalState()->SetInteger( | 
 |         omnibox::kIntranetRedirectBehavior, | 
 |         static_cast<int>(omnibox::IntranetRedirectorBehavior::DISABLE_FEATURE)); | 
 |  | 
 |     AutocompleteMatch match; | 
 |     match.type = AutocompleteMatchType::SEARCH_SUGGEST; | 
 |     match.destination_url = GURL("http://www.foo.com/s?q=foo"); | 
 |     GURL alternate_nav_url = | 
 |         AutocompleteResult::ComputeAlternateNavUrl(input, match, &client); | 
 |     EXPECT_FALSE(alternate_nav_url.is_valid()); | 
 |   } | 
 | } | 
 |  | 
 | // Tests that if the new results have a lower max relevance score than last, | 
 | // any copied results have their relevance shifted down. | 
 | TEST_F(AutocompleteResultTest, TransferOldMatches) { | 
 |   TestData last[] = { | 
 |       {0, 1, 1000, true}, | 
 |       {1, 1, 500, true}, | 
 |   }; | 
 |   TestData current[] = { | 
 |       {2, 1, 400, true}, | 
 |   }; | 
 |   TestData result[] = { | 
 |       {2, 1, 400, true}, | 
 |       {1, 1, 399, false},  // transferred matches aren't allowed to be default. | 
 |   }; | 
 |  | 
 |   ASSERT_NO_FATAL_FAILURE( | 
 |       RunTransferOldMatchesTest(last, current, result, std::size(result))); | 
 | } | 
 |  | 
 | // Tests that if the new results have a lower max relevance score than last, | 
 | // any copied results have their relevance shifted down when the allowed to | 
 | // be default constraint comes into play. | 
 | TEST_F(AutocompleteResultTest, TransferOldMatchesAllowedToBeDefault) { | 
 |   TestData last[] = { | 
 |       {0, 1, 1300, true}, | 
 |       {1, 1, 1200, true}, | 
 |       {2, 1, 1100, true}, | 
 |   }; | 
 |   TestData current[] = { | 
 |       {3, 1, 1000, false}, | 
 |       {4, 1, 900, true}, | 
 |   }; | 
 |   // The expected results are out of relevance order because the top-scoring | 
 |   // allowed to be default match is always pulled to the top. | 
 |   TestData result[] = { | 
 |       {4, 1, 900, true}, | 
 |       {3, 1, 1000, false}, | 
 |       {2, 1, 899, false},  // transferred matches aren't allowed to be default. | 
 |   }; | 
 |  | 
 |   ASSERT_NO_FATAL_FAILURE( | 
 |       RunTransferOldMatchesTest(last, current, result, std::size(result))); | 
 | } | 
 |  | 
 | // Tests |TransferOldMatches()| with an |AutocompleteInput| with | 
 | // |prevent_inline_autocomplete| set to true. Noteworthy, expect that resulting | 
 | // matches must have effectively empty autocompletions; i.e. either empty | 
 | // |inline_autocompletion|, or false |allowed_to_be_default|. Tests all 12 | 
 | // combinations of 1) last match has a lower or higher relevance than current | 
 | // match, 2) last match was allowed to be default, 3) last match had | 
 | // autocompletion, and 4) current match is allowed to be default. | 
 | TEST_F(AutocompleteResultTest, | 
 |        TransferOldMatchesAllowedToBeDefaultWithPreventInlineAutocompletion) { | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   input.set_prevent_inline_autocomplete(true); | 
 |  | 
 |   { | 
 |     SCOPED_TRACE( | 
 |         "Current matches not allowed to be default and scored higher."); | 
 |     // 1) `allowed_to_be_default` should be true only for `last` matches without | 
 |     //    autocompletion. | 
 |     // 2) `allowed_to_be_default` should not be set true for previously not | 
 |     //     allowed matches, even if they don't have inline autocompletion. | 
 |     // 3) When `allowed_to_be_default` is false, `current` matches should be | 
 |     //    preferred as they're scored higher. | 
 |     // clang-format off | 
 |     TestData last[] = { | 
 |         {0, 1, 1020, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, {}, "autocompletion"}, | 
 |         {1, 1, 1010, true}, | 
 |         {2, 1, 1000, false}, | 
 |     }; | 
 |     TestData current[] = { | 
 |         {0, 2, 1520, false}, | 
 |         {1, 2, 1510, false}, | 
 |         {2, 2, 1500, false}, | 
 |     }; | 
 |     TestData result[] = { | 
 |         {1, 1, 1510, true}, | 
 |         {0, 2, 1520, false}, | 
 |         {2, 2, 1500, false}, | 
 |     }; | 
 |     // clang-format on | 
 |  | 
 |     ASSERT_NO_FATAL_FAILURE(RunTransferOldMatchesTest( | 
 |         last, current, result, std::size(result), input)); | 
 |   } | 
 |  | 
 |   { | 
 |     SCOPED_TRACE("Current matches not allowed to be default and scored lower."); | 
 |     // Similar to above, except `last` matches should be preferred in deduping | 
 |     // as they're scored higher. | 
 |     // clang-format off | 
 |     TestData last[] = { | 
 |         {0, 1, 1020, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, {}, "autocompletion"}, | 
 |         {1, 1, 1010, true}, | 
 |         {2, 1, 1000, false}, | 
 |     }; | 
 |     TestData current[] = { | 
 |         // Need a high-scoring current match to avoid demoting last matches. | 
 |         {3, 2, 1500, false}, | 
 |         {0, 2, 520, false}, | 
 |         {1, 2, 510, false}, | 
 |         {2, 2, 500, false}, | 
 |     }; | 
 |     TestData result[] = { | 
 |         {1, 1, 1010, true}, | 
 |         {3, 2, 1500, false}, | 
 |         {0, 1, 1020, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, {}, "autocompletion"}, | 
 |         {2, 1, 1000, false}, | 
 |     }; | 
 |     // clang-format on | 
 |  | 
 |     ASSERT_NO_FATAL_FAILURE(RunTransferOldMatchesTest( | 
 |         last, current, result, std::size(result), input)); | 
 |   } | 
 |  | 
 |   { | 
 |     SCOPED_TRACE("Current matches allowed to be default and scored higher."); | 
 |     // Deduping should prefer the `current` matches as they're both allowed to | 
 |     // be default and scored higher. | 
 |     // clang-format off | 
 |     TestData last[] = { | 
 |         {0, 1, 1020, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, {}, "autocompletion"}, | 
 |         {1, 1, 1010, true}, | 
 |         {2, 1, 1000, false}, | 
 |     }; | 
 |     TestData current[] = { | 
 |         {0, 2, 1520, true}, | 
 |         {1, 2, 1510, true}, | 
 |         {2, 2, 1500, true}, | 
 |     }; | 
 |     TestData result[] = { | 
 |         {0, 2, 1520, true}, | 
 |         {1, 2, 1510, true}, | 
 |         {2, 2, 1500, true}, | 
 |     }; | 
 |     // clang-format on | 
 |  | 
 |     ASSERT_NO_FATAL_FAILURE(RunTransferOldMatchesTest( | 
 |         last, current, result, std::size(result), input)); | 
 |   } | 
 |  | 
 |   { | 
 |     SCOPED_TRACE("Current matches allowed to be default and scored lower."); | 
 |     // `last` matches still allowed to be default after transferring should be | 
 |     // preferred in deduping as they're scored higher. Otherwise, `current` | 
 |     // matches should be preferred in deduping as they're allowed to be default. | 
 |     // clang-format off | 
 |     TestData last[] = { | 
 |         {0, 1, 1020, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, {}, "autocompletion"}, | 
 |         {1, 1, 1010, true}, | 
 |         {2, 1, 1000, false}, | 
 |     }; | 
 |     TestData current[] = { | 
 |         // Need a high-scoring current match to avoid demoting last matches. | 
 |         {3, 2, 1500, true}, | 
 |         {0, 2, 520, true}, | 
 |         {1, 2, 510, true}, | 
 |         {2, 2, 500, true}, | 
 |     }; | 
 |     TestData result[] = { | 
 |         {3, 2, 1500, true}, | 
 |         {0, 2, 1020, true}, | 
 |         {1, 1, 1010, true}, | 
 |         {2, 2, 1000, true}, | 
 |     }; | 
 |     // clang-format on | 
 |  | 
 |     ASSERT_NO_FATAL_FAILURE(RunTransferOldMatchesTest( | 
 |         last, current, result, std::size(result), input)); | 
 |   } | 
 | } | 
 |  | 
 | // Tests that matches are copied correctly from two distinct providers. | 
 | TEST_F(AutocompleteResultTest, TransferOldMatchesMultipleProviders) { | 
 |   TestData last[] = { | 
 |       {0, 1, 1300, false}, {1, 2, 1250, true},  {2, 1, 1200, false}, | 
 |       {3, 2, 1150, true},  {4, 1, 1100, false}, | 
 |   }; | 
 |   TestData current[] = { | 
 |       {5, 1, 1000, false}, | 
 |       {6, 2, 800, true}, | 
 |       {7, 1, 500, true}, | 
 |   }; | 
 |   // The expected results are out of relevance order because the top-scoring | 
 |   // allowed to be default match is always pulled to the top. | 
 |   TestData result[] = { | 
 |       {6, 2, 800, true}, {5, 1, 1000, false}, {3, 2, 799, false}, | 
 |       {7, 1, 500, true}, {4, 1, 499, false}, | 
 |   }; | 
 |  | 
 |   ASSERT_NO_FATAL_FAILURE( | 
 |       RunTransferOldMatchesTest(last, current, result, std::size(result))); | 
 | } | 
 |  | 
 | // Tests that matches are copied correctly from two distinct providers when | 
 | // one provider doesn't have a current legal default match. | 
 | TEST_F(AutocompleteResultTest, | 
 |        TransferOldMatchesWithOneProviderWithoutDefault) { | 
 |   TestData last[] = { | 
 |       {0, 2, 1250, true}, {1, 2, 1150, true}, {2, 1, 900, false}, | 
 |       {3, 1, 800, false}, {4, 1, 700, false}, | 
 |   }; | 
 |   TestData current[] = { | 
 |       {5, 1, 1000, true}, | 
 |       {6, 2, 800, false}, | 
 |       {7, 1, 500, true}, | 
 |   }; | 
 |   TestData result[] = { | 
 |       {5, 1, 1000, true}, {1, 2, 999, false}, {6, 2, 800, false}, | 
 |       {4, 1, 700, false}, {7, 1, 500, true}, | 
 |   }; | 
 |  | 
 |   ASSERT_NO_FATAL_FAILURE( | 
 |       RunTransferOldMatchesTest(last, current, result, std::size(result))); | 
 | } | 
 |  | 
 | // Tests that transferred matches do not include the specialized match types. | 
 | TEST_F(AutocompleteResultTest, TransferOldMatchesSkipsSpecializedSuggestions) { | 
 |   TestData last[] = { | 
 |       {0, 1, 1000, true, {}, AutocompleteMatchType::TILE_NAVSUGGEST}, | 
 |       {1, 4, 999, true, {}, AutocompleteMatchType::TILE_SUGGESTION}, | 
 |       {2, 2, 500, true}, | 
 |   }; | 
 |   TestData current[] = { | 
 |       {3, 1, 600, true}, | 
 |   }; | 
 |   TestData result[] = { | 
 |       {3, 1, 600, true}, | 
 |       {2, 2, 500, false}, | 
 |   }; | 
 |  | 
 |   ASSERT_NO_FATAL_FAILURE( | 
 |       RunTransferOldMatchesTest(last, current, result, std::size(result))); | 
 | } | 
 |  | 
 | // Tests that transferred matches do not include the specialized match types. | 
 | TEST_F(AutocompleteResultTest, TransferOldMatchesSkipDoneProviders) { | 
 |   TestData last[] = { | 
 |       {0, 1, 500},  // Suggestion from done provider | 
 |       {1, 2, 400},  // Suggestion for not-done provider | 
 |   }; | 
 |   TestData current[] = { | 
 |       {2, 3, 700},  // Suggestion from done provider | 
 |       {3, 4, 600},  // Suggestion for not-done provider | 
 |   }; | 
 |   TestData result[] = { | 
 |       {2, 3, 700},  // New suggestion from done provider | 
 |       {3, 4, 600},  // New suggestion from not-done provider | 
 |       // Skip suggestion `{0, 1, 500}`. | 
 |       {1, 2, 400},  // Transferred suggestion from not-done provider | 
 |   }; | 
 |  | 
 |   GetProvider(1)->done_ = true; | 
 |   GetProvider(3)->done_ = true; | 
 |  | 
 |   ASSERT_NO_FATAL_FAILURE( | 
 |       RunTransferOldMatchesTest(last, current, result, std::size(result))); | 
 | } | 
 |  | 
 | // Tests that matches with empty destination URLs aren't treated as duplicates | 
 | // and culled. | 
 | TEST_F(AutocompleteResultTest, SortAndCullEmptyDestinationURLs) { | 
 |   TestData data[] = { | 
 |       {1, 1, 500, true},  {0, 1, 1100, true}, {1, 1, 1000, true}, | 
 |       {0, 1, 1300, true}, {0, 1, 1200, true}, | 
 |   }; | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   matches[1].destination_url = GURL(); | 
 |   matches[3].destination_url = GURL(); | 
 |   matches[4].destination_url = GURL(); | 
 |  | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   // Of the two results with the same non-empty destination URL, the | 
 |   // lower-relevance one should be dropped.  All of the results with empty URLs | 
 |   // should be kept. | 
 |   ASSERT_EQ(4U, result.size()); | 
 |   EXPECT_TRUE(result.match_at(0)->destination_url.is_empty()); | 
 |   EXPECT_EQ(1300, result.match_at(0)->relevance); | 
 |   EXPECT_TRUE(result.match_at(1)->destination_url.is_empty()); | 
 |   EXPECT_EQ(1200, result.match_at(1)->relevance); | 
 |   EXPECT_TRUE(result.match_at(2)->destination_url.is_empty()); | 
 |   EXPECT_EQ(1100, result.match_at(2)->relevance); | 
 |   EXPECT_EQ("http://b/", result.match_at(3)->destination_url.spec()); | 
 |   EXPECT_EQ(1000, result.match_at(3)->relevance); | 
 | } | 
 |  | 
 | #if !(BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)) | 
 | // Tests which remove results only work on desktop. | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullTailSuggestions) { | 
 |   // clang-format off | 
 |   TestData data[] = { | 
 |       {1, 1, 500,  true}, | 
 |       {2, 1, 1100, false}, | 
 |       {3, 1, 1000, false}, | 
 |       {4, 1, 1300, false}, | 
 |       {5, 1, 1200, false}, | 
 |   }; | 
 |   // clang-format on | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   // These will get sorted up, but still removed. | 
 |   matches[3].type = AutocompleteMatchType::SEARCH_SUGGEST_TAIL; | 
 |   matches[4].type = AutocompleteMatchType::SEARCH_SUGGEST_TAIL; | 
 |  | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   EXPECT_EQ(3UL, result.size()); | 
 |   EXPECT_NE(AutocompleteMatchType::SEARCH_SUGGEST_TAIL, | 
 |             result.match_at(0)->type); | 
 |   EXPECT_NE(AutocompleteMatchType::SEARCH_SUGGEST_TAIL, | 
 |             result.match_at(1)->type); | 
 |   EXPECT_NE(AutocompleteMatchType::SEARCH_SUGGEST_TAIL, | 
 |             result.match_at(2)->type); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullKeepDefaultTailSuggestions) { | 
 |   // clang-format off | 
 |   TestData data[] = { | 
 |       {1, 1, 500,  true}, | 
 |       {2, 1, 1100, false}, | 
 |       {3, 1, 1000, false}, | 
 |       {4, 1, 1300, false}, | 
 |       {5, 1, 1200, false}, | 
 |   }; | 
 |   // clang-format on | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   // Make sure that even bad tail suggestions, if the only default match, | 
 |   // are kept. | 
 |   matches[0].type = AutocompleteMatchType::SEARCH_SUGGEST_TAIL; | 
 |   matches[1].type = AutocompleteMatchType::SEARCH_SUGGEST_TAIL; | 
 |   matches[2].type = AutocompleteMatchType::SEARCH_SUGGEST_TAIL; | 
 |  | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   EXPECT_EQ(3UL, result.size()); | 
 |   EXPECT_EQ(AutocompleteMatchType::SEARCH_SUGGEST_TAIL, | 
 |             result.match_at(0)->type); | 
 |   EXPECT_EQ(AutocompleteMatchType::SEARCH_SUGGEST_TAIL, | 
 |             result.match_at(1)->type); | 
 |   EXPECT_EQ(AutocompleteMatchType::SEARCH_SUGGEST_TAIL, | 
 |             result.match_at(2)->type); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullKeepMoreDefaultTailSuggestions) { | 
 |   // clang-format off | 
 |   TestData data[] = { | 
 |       {1, 1, 500,  true},   // Low score non-tail default | 
 |       {2, 1, 1100, false},  // Tail | 
 |       {3, 1, 1000, true},   // Allow a tail suggestion to be the default. | 
 |       {4, 1, 1300, false},  // Tail | 
 |       {5, 1, 1200, false},  // Tail | 
 |   }; | 
 |   // clang-format on | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   // Make sure that even a bad non-tail default suggestion is kept. | 
 |   for (size_t i = 1; i < 5; ++i) | 
 |     matches[i].type = AutocompleteMatchType::SEARCH_SUGGEST_TAIL; | 
 |  | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   EXPECT_EQ(5UL, result.size()); | 
 |   // Non-tail default must be first, regardless of score | 
 |   EXPECT_NE(AutocompleteMatchType::SEARCH_SUGGEST_TAIL, | 
 |             result.match_at(0)->type); | 
 |   for (size_t i = 1; i < 5; ++i) { | 
 |     EXPECT_EQ(AutocompleteMatchType::SEARCH_SUGGEST_TAIL, | 
 |               result.match_at(i)->type); | 
 |     EXPECT_FALSE(result.match_at(i)->allowed_to_be_default_match); | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullZeroRelevanceSuggestions) { | 
 |   // clang-format off | 
 |   TestData data[] = { | 
 |       {1, 1, 1000, true},   // A default non-tail suggestion. | 
 |       {2, 1, 0,    true},   // A no-relevance default non-tail suggestion. | 
 |       {3, 1, 1100, true},   // Default tail | 
 |       {4, 1, 1000, false},  // Tail | 
 |       {5, 1, 1300, false},  // Tail | 
 |       {6, 1, 0,    false},  // No-relevance tail suggestion. | 
 |   }; | 
 |   // clang-format on | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   for (size_t i = 2; i < std::size(data); ++i) | 
 |     matches[i].type = AutocompleteMatchType::SEARCH_SUGGEST_TAIL; | 
 |  | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   EXPECT_EQ(4UL, result.size()); | 
 |   EXPECT_NE(AutocompleteMatchType::SEARCH_SUGGEST_TAIL, | 
 |             result.match_at(0)->type); | 
 |   EXPECT_TRUE(result.match_at(0)->allowed_to_be_default_match); | 
 |   for (size_t i = 1; i < 4; ++i) { | 
 |     EXPECT_EQ(AutocompleteMatchType::SEARCH_SUGGEST_TAIL, | 
 |               result.match_at(i)->type); | 
 |     EXPECT_FALSE(result.match_at(i)->allowed_to_be_default_match); | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullZeroRelevanceDefaultMatches) { | 
 |   // clang-format off | 
 |   TestData data[] = { | 
 |       {1, 1, 0,    true},   // A zero-relevance default non-tail suggestion. | 
 |       {2, 1, 1100, true},   // Default tail | 
 |       {3, 1, 1000, false},  // Tail | 
 |       {4, 1, 1300, false},  // Tail | 
 |       {5, 1, 0,    false},  // No-relevance tail suggestion. | 
 |   }; | 
 |   // clang-format on | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   for (size_t i = 1; i < std::size(data); ++i) | 
 |     matches[i].type = AutocompleteMatchType::SEARCH_SUGGEST_TAIL; | 
 |  | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   // It should ignore the first suggestion, despite it being marked as | 
 |   // allowed to be default. | 
 |   EXPECT_EQ(3UL, result.size()); | 
 |   EXPECT_TRUE(result.match_at(0)->allowed_to_be_default_match); | 
 |   for (size_t i = 0; i < 3; ++i) { | 
 |     EXPECT_EQ(AutocompleteMatchType::SEARCH_SUGGEST_TAIL, | 
 |               result.match_at(i)->type); | 
 |     if (i > 0) | 
 |       EXPECT_FALSE(result.match_at(i)->allowed_to_be_default_match); | 
 |   } | 
 | } | 
 |  | 
 | #endif | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullOnlyTailSuggestions) { | 
 |   // clang-format off | 
 |   TestData data[] = { | 
 |       {1, 1, 500,  true},   // Allow a bad non-tail default. | 
 |       {2, 1, 1100, false},  // Tail | 
 |       {3, 1, 1000, false},  // Tail | 
 |       {4, 1, 1300, false},  // Tail | 
 |       {5, 1, 1200, false},  // Tail | 
 |   }; | 
 |   // clang-format on | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   // These will not be removed. | 
 |   for (size_t i = 1; i < 5; ++i) | 
 |     matches[i].type = AutocompleteMatchType::SEARCH_SUGGEST_TAIL; | 
 |  | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   EXPECT_EQ(5UL, result.size()); | 
 |   EXPECT_NE(AutocompleteMatchType::SEARCH_SUGGEST_TAIL, | 
 |             result.match_at(0)->type); | 
 |   for (size_t i = 1; i < 5; ++i) | 
 |     EXPECT_EQ(AutocompleteMatchType::SEARCH_SUGGEST_TAIL, | 
 |               result.match_at(i)->type); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullNoMatchesAllowedToBeDefault) { | 
 |   // clang-format off | 
 |   TestData data[] = { | 
 |       {1, 1, 500,  false},  // Not allowed_to_be_default_match | 
 |       {2, 1, 1100, false},  // Not allowed_to_be_default_match | 
 |       {3, 1, 1000, false},  // Not allowed_to_be_default_match | 
 |   }; | 
 |   // clang-format on | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |  | 
 |   AutocompleteInput input(std::u16string(), metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   EXPECT_EQ(3UL, result.size()); | 
 |   EXPECT_EQ(matches[1].destination_url, result.match_at(0)->destination_url); | 
 |   EXPECT_EQ(matches[2].destination_url, result.match_at(1)->destination_url); | 
 |   EXPECT_EQ(matches[0].destination_url, result.match_at(2)->destination_url); | 
 |   for (size_t i = 0; i < 3; ++i) | 
 |     EXPECT_FALSE(result.match_at(i)->allowed_to_be_default_match); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullDuplicateSearchURLs) { | 
 |   // Register a template URL that corresponds to 'foo' search engine. | 
 |   TemplateURLData url_data; | 
 |   url_data.SetShortName(u"unittest"); | 
 |   url_data.SetKeyword(u"foo"); | 
 |   url_data.SetURL("http://www.foo.com/s?q={searchTerms}"); | 
 |   template_url_service().Add(std::make_unique<TemplateURL>(url_data)); | 
 |  | 
 |   TestData data[] = { | 
 |       {0, 1, 1300, true}, {1, 1, 1200, true}, {2, 1, 1100, true}, | 
 |       {3, 1, 1000, true}, {4, 2, 900, true}, | 
 |   }; | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   matches[0].destination_url = GURL("http://www.foo.com/s?q=foo"); | 
 |   matches[1].destination_url = GURL("http://www.foo.com/s?q=foo2"); | 
 |   matches[2].destination_url = GURL("http://www.foo.com/s?q=foo&oq=f"); | 
 |   matches[3].destination_url = GURL("http://www.foo.com/s?q=foo&aqs=0"); | 
 |   matches[4].destination_url = GURL("http://www.foo.com/"); | 
 |  | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   // We expect the 3rd and 4th results to be removed. | 
 |   ASSERT_EQ(3U, result.size()); | 
 |   EXPECT_EQ("http://www.foo.com/s?q=foo", | 
 |             result.match_at(0)->destination_url.spec()); | 
 |   EXPECT_EQ(1300, result.match_at(0)->relevance); | 
 |   EXPECT_EQ("http://www.foo.com/s?q=foo2", | 
 |             result.match_at(1)->destination_url.spec()); | 
 |   EXPECT_EQ(1200, result.match_at(1)->relevance); | 
 |   EXPECT_EQ("http://www.foo.com/", result.match_at(2)->destination_url.spec()); | 
 |   EXPECT_EQ(900, result.match_at(2)->relevance); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullWithMatchDups) { | 
 |   // Register a template URL that corresponds to 'foo' search engine. | 
 |   TemplateURLData url_data; | 
 |   url_data.SetShortName(u"unittest"); | 
 |   url_data.SetKeyword(u"foo"); | 
 |   url_data.SetURL("http://www.foo.com/s?q={searchTerms}"); | 
 |   template_url_service().Add(std::make_unique<TemplateURL>(url_data)); | 
 |  | 
 |   AutocompleteMatch dup_match; | 
 |   dup_match.destination_url = GURL("http://www.foo.com/s?q=foo&oq=dup"); | 
 |   std::vector<AutocompleteMatch> dups; | 
 |   dups.push_back(dup_match); | 
 |  | 
 |   TestData data[] = { | 
 |       {0, 1, 1300, true, dups}, {1, 1, 1200, true}, {2, 1, 1100, true}, | 
 |       {3, 1, 1000, true, dups}, {4, 2, 900, true},  {5, 1, 800, true}, | 
 |   }; | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   matches[0].destination_url = GURL("http://www.foo.com/s?q=foo"); | 
 |   matches[1].destination_url = GURL("http://www.foo.com/s?q=foo2"); | 
 |   matches[2].destination_url = GURL("http://www.foo.com/s?q=foo&oq=f"); | 
 |   matches[3].destination_url = GURL("http://www.foo.com/s?q=foo&aqs=0"); | 
 |   matches[4].destination_url = GURL("http://www.foo.com/"); | 
 |   matches[5].destination_url = GURL("http://www.foo.com/s?q=foo2&oq=f"); | 
 |  | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   // Expect 3 unique results after SortAndCull(). | 
 |   ASSERT_EQ(3U, result.size()); | 
 |  | 
 |   // Check that 3rd and 4th result got added to the first result as duplicates | 
 |   // and also duplicates of the 4th match got copied. | 
 |   ASSERT_EQ(4U, result.match_at(0)->duplicate_matches.size()); | 
 |   const AutocompleteMatch* first_match = result.match_at(0); | 
 |   EXPECT_EQ(matches[2].destination_url, | 
 |             first_match->duplicate_matches.at(1).destination_url); | 
 |   EXPECT_EQ(matches[3].destination_url, | 
 |             first_match->duplicate_matches.at(2).destination_url); | 
 |   EXPECT_EQ(dup_match.destination_url, | 
 |             first_match->duplicate_matches.at(3).destination_url); | 
 |  | 
 |   // Check that 6th result started a new list of dups for the second result. | 
 |   ASSERT_EQ(1U, result.match_at(1)->duplicate_matches.size()); | 
 |   EXPECT_EQ(matches[5].destination_url, | 
 |             result.match_at(1)->duplicate_matches.at(0).destination_url); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullWithPreserveDefaultMatch) { | 
 |   auto test = [&](const std::vector<TestData>& last, | 
 |                   const std::vector<TestData>& current, | 
 |                   const std::vector<TestData>& expected) { | 
 |     AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                             TestSchemeClassifier()); | 
 |  | 
 |     ACMatches last_matches = PopulateAutocompleteMatches(last); | 
 |     AutocompleteResult last_result; | 
 |     last_result.AppendMatches(last_matches); | 
 |     last_result.SortAndCull( | 
 |         input, &template_url_service(), triggered_feature_service(), | 
 |         /*is_lens_active=*/false, /*can_show_contextual_suggestions=*/false, | 
 |         /*mia_enabled*/ false); | 
 |  | 
 |     ACMatches current_matches = PopulateAutocompleteMatches(current); | 
 |     AutocompleteResult current_result; | 
 |     current_result.AppendMatches(current_matches); | 
 |  | 
 |     // Run SortAndCull, but try to keep the first entry of last_matches on top. | 
 |     current_result.SortAndCull( | 
 |         input, &template_url_service(), triggered_feature_service(), | 
 |         /*is_lens_active=*/false, /*can_show_contextual_suggestions=*/false, | 
 |         /*mia_enabled*/ false, *last_result.match_at(0)); | 
 |  | 
 |     AssertResultMatches(current_result, expected); | 
 |   }; | 
 |  | 
 |   { | 
 |     SCOPED_TRACE("Lower scored default is preserved."); | 
 |     std::vector<TestData> last = { | 
 |         {0, 1, 500, true}, | 
 |         {1, 1, 400, true}, | 
 |     }; | 
 |     std::vector<TestData> current = { | 
 |         {1, 1, 500, true}, | 
 |         {0, 1, 400, true}, | 
 |     }; | 
 |     std::vector<TestData> expected = { | 
 |         {0, 1, 400, true}, | 
 |         {1, 1, 500, true}, | 
 |     }; | 
 |     test(last, current, expected); | 
 |   } | 
 |   { | 
 |     SCOPED_TRACE("Don't preserve a default that no longer matches."); | 
 |     std::vector<TestData> last = { | 
 |         {0, 1, 500, true}, | 
 |     }; | 
 |     std::vector<TestData> current = { | 
 |         {1, 1, 100, true}, | 
 |     }; | 
 |     std::vector<TestData> expected = { | 
 |         {1, 1, 100, true}, | 
 |     }; | 
 |     test(last, current, expected); | 
 |   } | 
 |   { | 
 |     SCOPED_TRACE( | 
 |         "Previous default does not replace a higher scored " | 
 |         "URL_WHAT_YOU_TYPED."); | 
 |     std::vector<TestData> last = { | 
 |         {0, 1, 500, true, {}, AutocompleteMatchType::HISTORY_URL}, | 
 |         {1, 1, 400, true, {}, AutocompleteMatchType::HISTORY_URL}, | 
 |     }; | 
 |     std::vector<TestData> current = { | 
 |         {0, 1, 500, true, {}, AutocompleteMatchType::HISTORY_URL}, | 
 |         {1, 1, 400, true, {}, AutocompleteMatchType::HISTORY_URL}, | 
 |         {2, 1, 600, true, {}, AutocompleteMatchType::URL_WHAT_YOU_TYPED}, | 
 |     }; | 
 |     std::vector<TestData> expected = { | 
 |         {2, 1, 600, true, {}, AutocompleteMatchType::URL_WHAT_YOU_TYPED}, | 
 |         {0, 1, 500, true, {}, AutocompleteMatchType::HISTORY_URL}, | 
 |         {1, 1, 400, true, {}, AutocompleteMatchType::HISTORY_URL}, | 
 |     }; | 
 |     test(last, current, expected); | 
 |   } | 
 |   { | 
 |     SCOPED_TRACE( | 
 |         "Previous default does replace a lower scored URL_WHAT_YOU_TYPED."); | 
 |     std::vector<TestData> last = { | 
 |         {0, 1, 500, true, {}, AutocompleteMatchType::HISTORY_URL}, | 
 |         {1, 1, 400, true, {}, AutocompleteMatchType::HISTORY_URL}, | 
 |     }; | 
 |     std::vector<TestData> current = { | 
 |         {0, 1, 500, true, {}, AutocompleteMatchType::HISTORY_URL}, | 
 |         {1, 1, 400, true, {}, AutocompleteMatchType::HISTORY_URL}, | 
 |         {2, 1, 300, true, {}, AutocompleteMatchType::URL_WHAT_YOU_TYPED}, | 
 |         {3, 1, 600, true, {}, AutocompleteMatchType::HISTORY_URL}, | 
 |     }; | 
 |     std::vector<TestData> expected = { | 
 |         {0, 1, 500, true, {}, AutocompleteMatchType::HISTORY_URL}, | 
 |         {3, 1, 600, true, {}, AutocompleteMatchType::HISTORY_URL}, | 
 |         {1, 1, 400, true, {}, AutocompleteMatchType::HISTORY_URL}, | 
 |         {2, 1, 300, true, {}, AutocompleteMatchType::URL_WHAT_YOU_TYPED}, | 
 |     }; | 
 |     test(last, current, expected); | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, DemoteOnDeviceSearchSuggestions) { | 
 |   // clang-format off | 
 |   TestData data[] = { | 
 |       {1, 1, 500,  true}, | 
 |       {2, 2, 1100, true}, | 
 |       {3, 2, 1000, true}, | 
 |       {4, 1, 1300, true}, | 
 |       {5, 1, 1200, true}, | 
 |   }; | 
 |   // clang-format on | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   matches[0].type = AutocompleteMatchType::SEARCH_SUGGEST; | 
 |   matches[1].type = AutocompleteMatchType::SEARCH_SUGGEST; | 
 |   matches[2].type = AutocompleteMatchType::SEARCH_SUGGEST; | 
 |   matches[3].type = AutocompleteMatchType::SEARCH_SUGGEST; | 
 |   matches[4].type = AutocompleteMatchType::SEARCH_SUGGEST; | 
 |  | 
 |   // match1, match2 are set as on device head suggestion. | 
 |   matches[1].subtypes = {omnibox::SUBTYPE_OMNIBOX_OTHER, | 
 |                          omnibox::SUBTYPE_SUGGEST_2G_LITE}; | 
 |   matches[2].subtypes = {omnibox::SUBTYPE_OMNIBOX_OTHER, | 
 |                          omnibox::SUBTYPE_SUGGEST_2G_LITE}; | 
 |   matches[0].provider->type_ = AutocompleteProvider::TYPE_SEARCH; | 
 |   matches[1].provider->type_ = AutocompleteProvider::TYPE_ON_DEVICE_HEAD; | 
 |  | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |  | 
 |   // Test setting on device suggestion relevances lower than search provider | 
 |   // suggestions. | 
 |   AutocompleteResultForTesting result; | 
 |   result.AppendMatches(matches); | 
 |   result.DemoteOnDeviceSearchSuggestions(); | 
 |   EXPECT_EQ(5UL, result.size()); | 
 |   EXPECT_NE(AutocompleteProvider::TYPE_ON_DEVICE_HEAD, | 
 |             result.match_at(0)->provider->type()); | 
 |   EXPECT_EQ(AutocompleteProvider::TYPE_ON_DEVICE_HEAD, | 
 |             result.match_at(1)->provider->type()); | 
 |   EXPECT_LT(result.match_at(1)->relevance, result.match_at(0)->relevance); | 
 |   EXPECT_EQ(AutocompleteProvider::TYPE_ON_DEVICE_HEAD, | 
 |             result.match_at(2)->provider->type()); | 
 |   EXPECT_LT(result.match_at(2)->relevance, result.match_at(0)->relevance); | 
 |   EXPECT_NE(AutocompleteProvider::TYPE_ON_DEVICE_HEAD, | 
 |             result.match_at(3)->provider->type()); | 
 |   EXPECT_NE(AutocompleteProvider::TYPE_ON_DEVICE_HEAD, | 
 |             result.match_at(4)->provider->type()); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, DemoteByType) { | 
 |   base::test::ScopedFeatureList feature_list; | 
 |   feature_list.InitAndDisableFeature(omnibox::kGroupingFrameworkForNonZPS); | 
 |  | 
 |   // Add some matches. | 
 |   ACMatches matches; | 
 |   const AutocompleteMatchTestData data[] = { | 
 |       {"http://history-url/1300/", AutocompleteMatchType::HISTORY_URL}, | 
 |       {"http://history-title/1200/def", AutocompleteMatchType::HISTORY_TITLE}, | 
 |       {"http://search-what-you-typed/1100/", | 
 |        AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED}, | 
 |       {"http://search-history/1000/def", AutocompleteMatchType::SEARCH_HISTORY}, | 
 |   }; | 
 |   PopulateAutocompleteMatchesFromTestData(data, std::size(data), &matches); | 
 |  | 
 |   // Make history-title and search-history the only default matches, so that | 
 |   // they compete. | 
 |   matches[0].allowed_to_be_default_match = false; | 
 |   matches[2].allowed_to_be_default_match = false; | 
 |  | 
 | #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) | 
 |   // Where Grouping suggestions by Search vs URL kicks in, search gets | 
 |   // promoted to the top of the list. | 
 |  | 
 |   // No demotion (e.g. omnibox input) | 
 |   const std::vector<size_t> expected_natural_order{1, 2, 3, 0}; | 
 |   // Demoting all matches (e.g. realbox, search input) | 
 |   const std::vector<size_t> expected_demoted_all_order{3, 2, 0, 1}; | 
 |   // TODO(manukh): Flush out test so that `expected_natural_order` and | 
 |   //   `expected_demoted_non_default_order` are different. | 
 |   // Demoting non-default matches (e.g. realbox, URL input) | 
 |   const std::vector<size_t> expected_demoted_non_default_order{1, 2, 3, 0}; | 
 | #else | 
 |   // Note: Android and iOS performs grouping by Search vs URL at a later stage, | 
 |   // when views are built. this means the vector below will be demoted by type, | 
 |   // but not rearranged by Search vs URL. | 
 |  | 
 |   // No demotion (e.g. omnibox input) | 
 |   const std::vector<size_t> expected_natural_order{1, 0, 2, 3}; | 
 |   // Demoting all matches (e.g. realbox, search input) | 
 |   const std::vector<size_t> expected_demoted_all_order{3, 2, 0, 1}; | 
 |   // Demoting non-default matches (e.g. realbox, URL input) | 
 |   const std::vector<size_t> expected_demoted_non_default_order{1, 2, 3, 0}; | 
 | #endif | 
 |  | 
 |   // Expect no demotion on `HOME_PAGE` inputs. Expect grouping to only non- | 
 |   // default matches. History-title is the highest scoring defaultable | 
 |   // suggestion, so expect it 1st. Then the rest of the matches by relevance. | 
 |   { | 
 |     SCOPED_TRACE("SortAndCull with HOME_PAGE classification"); | 
 |     SortMatchesAndVerifyOrder("a", OmniboxEventProto::HOME_PAGE, matches, | 
 |                               expected_natural_order, data); | 
 |   } | 
 |  | 
 |   // For `NTP_REALBOX` inputs, expect demotion to occur and affect both the | 
 |   // default and non-default matches. After demotion, search-history is the | 
 |   // highest defaultable match, so expect it 1st. Then the SWYT match as the | 
 |   // history matches should be demoted. | 
 |   { | 
 |     SCOPED_TRACE("SortAndCull with NTP_REALBOX classification"); | 
 |     SortMatchesAndVerifyOrder("a", OmniboxEventProto::NTP_REALBOX, matches, | 
 |                               expected_demoted_all_order, data); | 
 |   } | 
 |  | 
 |   // Usually, the default match is exempt of demotion. Only if the input is both | 
 |   // a) realbox, and b) not a URL, then demotion is considered. See | 
 |   // `AutocompleteResult::FindTopMatch()`. The lower matches will be demoted in | 
 |   // the realbox, regardless of whether the input is a URL. | 
 |   { | 
 |     SCOPED_TRACE("SortAndCull with NTP_REALBOX classification and URL input"); | 
 |     SortMatchesAndVerifyOrder("www.example.com", OmniboxEventProto::NTP_REALBOX, | 
 |                               matches, expected_demoted_non_default_order, | 
 |                               data); | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullReorderForDefaultMatch) { | 
 |   TestData data[] = {{0, 1, 1300, true}, | 
 |                      {1, 1, 1200, true}, | 
 |                      {2, 1, 1100, true}, | 
 |                      {3, 1, 1000, true}}; | 
 |   TestSchemeClassifier test_scheme_classifier; | 
 |  | 
 |   { | 
 |     // Check that reorder doesn't do anything if the top result | 
 |     // is already a legal default match (which is the default from | 
 |     // PopulateAutocompleteMatches()). | 
 |     ACMatches matches; | 
 |     PopulateAutocompleteMatches(data, &matches); | 
 |     AutocompleteInput input(u"a", metrics::OmniboxEventProto::HOME_PAGE, | 
 |                             test_scheme_classifier); | 
 |     AutocompleteResult result; | 
 |     result.AppendMatches(matches); | 
 |     result.SortAndCull(input, &template_url_service(), | 
 |                        triggered_feature_service(), /*is_lens_active=*/false, | 
 |                        /*can_show_contextual_suggestions=*/false, | 
 |                        /*mia_enabled*/ false); | 
 |     AssertResultMatches(result, data); | 
 |   } | 
 |  | 
 |   { | 
 |     // Check that reorder swaps up a result appropriately. | 
 |     ACMatches matches; | 
 |     PopulateAutocompleteMatches(data, &matches); | 
 |     matches[0].allowed_to_be_default_match = false; | 
 |     matches[1].allowed_to_be_default_match = false; | 
 |     AutocompleteInput input(u"a", metrics::OmniboxEventProto::HOME_PAGE, | 
 |                             test_scheme_classifier); | 
 |     AutocompleteResult result; | 
 |     result.AppendMatches(matches); | 
 |     result.SortAndCull(input, &template_url_service(), | 
 |                        triggered_feature_service(), /*is_lens_active=*/false, | 
 |                        /*can_show_contextual_suggestions=*/false, | 
 |                        /*mia_enabled*/ false); | 
 |     ASSERT_EQ(4U, result.size()); | 
 |     EXPECT_EQ("http://c/", result.match_at(0)->destination_url.spec()); | 
 |     EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec()); | 
 |     EXPECT_EQ("http://b/", result.match_at(2)->destination_url.spec()); | 
 |     EXPECT_EQ("http://d/", result.match_at(3)->destination_url.spec()); | 
 |   } | 
 | } | 
 |  | 
 | // Note: DCHECKs not firing on Cast. | 
 | #if DCHECK_IS_ON() | 
 | TEST_F(AutocompleteResultTest, SortAndCullFailsWithIncorrectDefaultScheme) { | 
 |   // Scenario: | 
 |   // - User navigates to a webpage whose URL looks like a scheme, | 
 |   //   e.g. "chrome:123" (note the colon). | 
 |   // - The navigation becomes an entry in the user's URL history. | 
 |   // - User types "chrome:" again. | 
 |   // - The top suggestion will match the previous user entry and may be the | 
 |   //   "chrome:123" again, except typing ":" invokes scheme checker. | 
 |   // Make sure the scheme checker is not causing trouble when the default | 
 |   // suggestion is Search. | 
 |  | 
 |   const AutocompleteMatchTestData data[] = { | 
 |       {"https://chrome:123", AutocompleteMatchType::HISTORY_URL}, | 
 |       {"chrome://history", AutocompleteMatchType::HISTORY_URL}}; | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatchesFromTestData(data, std::size(data), &matches); | 
 |   matches[0].allowed_to_be_default_match = true; | 
 |   matches[1].allowed_to_be_default_match = true; | 
 |   TestSchemeClassifier test_scheme_classifier; | 
 |  | 
 |   AutocompleteInput input(u"chrome:", metrics::OmniboxEventProto::HOME_PAGE, | 
 |                           test_scheme_classifier); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   EXPECT_DEATH_IF_SUPPORTED( | 
 |       result.SortAndCull(input, &template_url_service(), | 
 |                          triggered_feature_service(), /*is_lens_active=*/false, | 
 |                          /*can_show_contextual_suggestions=*/false, | 
 |                          /*mia_enabled*/ false), | 
 |       ""); | 
 | } | 
 | #endif | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullPermitSearchForSchemeMatching) { | 
 |   // Scenario: | 
 |   // - User searches for something that looks like a scheme, | 
 |   //   e.g. "chrome: how to do x" (note the colon). | 
 |   // - The search becomes an entry in the user's history (local or remote) | 
 |   //   outranking any HistoryURL matches. | 
 |   // - User types "chrome:" again. | 
 |   // - The top suggestion will match the previous user entry and may be the | 
 |   //   "chrome: how to do x" again, except typing ":" invokes scheme checker. | 
 |   // Make sure the scheme checker is not causing trouble when the default | 
 |   // suggestion is Search. | 
 |   const AutocompleteMatchTestData data[] = { | 
 |       {"https://google.com/search?q=chrome:123", | 
 |        AutocompleteMatchType::SEARCH_SUGGEST}, | 
 |       {"chrome://history", AutocompleteMatchType::HISTORY_URL}}; | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatchesFromTestData(data, std::size(data), &matches); | 
 |   matches[0].allowed_to_be_default_match = true; | 
 |   matches[1].allowed_to_be_default_match = true; | 
 |   TestSchemeClassifier test_scheme_classifier; | 
 |  | 
 |   AutocompleteInput input(u"chrome:", metrics::OmniboxEventProto::HOME_PAGE, | 
 |                           test_scheme_classifier); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   // Must not assert. | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullPromoteDefaultMatch) { | 
 |   TestData data[] = {{0, 1, 1300, false}, | 
 |                      {1, 1, 1200, false}, | 
 |                      {2, 2, 1100, false}, | 
 |                      {2, 3, 1000, false}, | 
 |                      {2, 4, 900, true}}; | 
 |   TestSchemeClassifier test_scheme_classifier; | 
 |  | 
 |   // Check that reorder swaps up a result, and promotes relevance, | 
 |   // appropriately. | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::HOME_PAGE, | 
 |                           test_scheme_classifier); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |   ASSERT_EQ(3U, result.size()); | 
 |   EXPECT_EQ("http://c/", result.match_at(0)->destination_url.spec()); | 
 |   EXPECT_EQ(1100, result.match_at(0)->relevance); | 
 |   EXPECT_TRUE(result.match_at(0)->allowed_to_be_default_match); | 
 |   EXPECT_EQ(GetProvider(4), result.match_at(0)->provider); | 
 |   EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec()); | 
 |   EXPECT_FALSE(result.match_at(1)->allowed_to_be_default_match); | 
 |   EXPECT_EQ("http://b/", result.match_at(2)->destination_url.spec()); | 
 |   EXPECT_FALSE(result.match_at(2)->allowed_to_be_default_match); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullPromoteUnconsecutiveMatches) { | 
 |   TestData data[] = { | 
 |       {0, 1, 1300, false}, {1, 1, 1200, true}, {3, 2, 1100, false}, | 
 |       {2, 1, 1000, false}, {3, 3, 900, true},  {4, 1, 800, false}, | 
 |       {3, 4, 700, false}, | 
 |   }; | 
 |   TestSchemeClassifier test_scheme_classifier; | 
 |  | 
 |   // Check that reorder swaps up a result, and promotes relevance, | 
 |   // even for a default match that isn't the best. | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::HOME_PAGE, | 
 |                           test_scheme_classifier); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |   ASSERT_EQ(5U, result.size()); | 
 |   EXPECT_EQ("http://b/", result.match_at(0)->destination_url.spec()); | 
 |   EXPECT_EQ(1200, result.match_at(0)->relevance); | 
 |   EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec()); | 
 |   EXPECT_EQ("http://d/", result.match_at(2)->destination_url.spec()); | 
 |   EXPECT_EQ(1100, result.match_at(2)->relevance); | 
 |   EXPECT_EQ(GetProvider(3), result.match_at(2)->provider); | 
 |   EXPECT_EQ("http://c/", result.match_at(3)->destination_url.spec()); | 
 |   EXPECT_EQ("http://e/", result.match_at(4)->destination_url.spec()); | 
 | } | 
 |  | 
 | struct EntityTestData { | 
 |   AutocompleteMatchType::Type type; | 
 |   raw_ptr<FakeAutocompleteProvider> provider; | 
 |   std::string destination_url; | 
 |   int relevance; | 
 |   bool allowed_to_be_default_match; | 
 |   std::string fill_into_edit; | 
 |   std::string inline_autocompletion; | 
 | }; | 
 |  | 
 | void PopulateEntityTestCases(std::vector<EntityTestData>& test_cases, | 
 |                              ACMatches* matches) { | 
 |   for (const auto& test_case : test_cases) { | 
 |     AutocompleteMatch match; | 
 |     match.provider = test_case.provider; | 
 |     match.type = test_case.type; | 
 |     match.destination_url = GURL(test_case.destination_url); | 
 |     match.relevance = test_case.relevance; | 
 |     match.allowed_to_be_default_match = test_case.allowed_to_be_default_match; | 
 |     match.fill_into_edit = base::UTF8ToUTF16(test_case.fill_into_edit); | 
 |     match.inline_autocompletion = | 
 |         base::UTF8ToUTF16(test_case.inline_autocompletion); | 
 |     matches->push_back(match); | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullPreferEntities) { | 
 |   // clang-format off | 
 |   std::vector<EntityTestData> test_cases = { | 
 |     { | 
 |       AutocompleteMatchType::SEARCH_SUGGEST, GetProvider(1), | 
 |       "http://search/?q=foo", 1100, false, "foo", "" | 
 |     }, | 
 |     { | 
 |       AutocompleteMatchType::SEARCH_SUGGEST_ENTITY, GetProvider(1), | 
 |       "http://search/?q=foo", 1000, false, "foo", "" | 
 |     }, | 
 |     { | 
 |       AutocompleteMatchType::SEARCH_SUGGEST, GetProvider(1), | 
 |       "http://search/?q=foo", 900, true, "foo", "oo" | 
 |     }, | 
 |     // This match will be the first result but it won't affect the entity | 
 |     // deduping because it has a different URL. | 
 |     // | 
 |     // Also keeping this as the default match allows us to test that Entities | 
 |     // and plain matches are deduplicated when they are not the default match. | 
 |     // See SortAndCullPreferEntitiesButKeepDefaultPlainMatches for details. | 
 |     { | 
 |       AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED, GetProvider(1), | 
 |       "http://search/?q=bar", 1200, true, "foo", "oo" | 
 |     }, | 
 |   }; | 
 |   // clang-format on | 
 |   ACMatches matches; | 
 |   PopulateEntityTestCases(test_cases, &matches); | 
 |  | 
 |   AutocompleteInput input(u"f", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   // The first result will be the personalized suggestion. | 
 |   EXPECT_EQ(2UL, result.size()); | 
 |   EXPECT_EQ(AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED, | 
 |             result.match_at(0)->type); | 
 |   EXPECT_EQ(1200, result.match_at(0)->relevance); | 
 |  | 
 |   // The second result will be the result of deduping the other three. | 
 |   // The chosen match should be the entity suggestion and it should have been | 
 |   // promoted to receive the first match's relevance and the last match's | 
 |   // allowed_to_be_default_match and inline_autocompletion values. | 
 |   EXPECT_EQ(AutocompleteMatchType::SEARCH_SUGGEST_ENTITY, | 
 |             result.match_at(1)->type); | 
 |   EXPECT_EQ(1100, result.match_at(1)->relevance); | 
 |   EXPECT_TRUE(result.match_at(1)->allowed_to_be_default_match); | 
 |   EXPECT_EQ(u"oo", result.match_at(1)->inline_autocompletion); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, | 
 |        SortAndCullPreferNonEntitiesForDefaultSuggestion) { | 
 |   // When the top scoring allowed_to_be_default suggestion is a search entity, | 
 |   // and there is a duplicate non-entity search suggest that is also | 
 |   // allowed_to_be_default, prefer the latter. | 
 |  | 
 |   std::vector<EntityTestData> test_cases = { | 
 |       {AutocompleteMatchType::SEARCH_SUGGEST_ENTITY, GetProvider(1), | 
 |        "http://search/?q=foo", 1000, true}, | 
 |       {AutocompleteMatchType::SEARCH_SUGGEST, GetProvider(1), | 
 |        "http://search/?q=foo2", 1200}, | 
 |       // A duplicate search suggestion should be preferred to a search entity | 
 |       // suggestion, even if the former is scored lower. | 
 |       {AutocompleteMatchType::SEARCH_SUGGEST, GetProvider(1), | 
 |        "http://search/?q=foo", 900, true}, | 
 |   }; | 
 |   ACMatches matches; | 
 |   PopulateEntityTestCases(test_cases, &matches); | 
 |  | 
 |   // Simulate the search provider pre-grouping duplicate suggestions. We want | 
 |   // to make sure `stripped_destination_url` is correctly appended to pre-duped | 
 |   // suggestions. | 
 |   matches[0].duplicate_matches.push_back(matches.back()); | 
 |   matches.pop_back(); | 
 |  | 
 |   // Simulate the top match having an `entity_id`. | 
 |   matches[0].entity_id = "/m/012abc"; | 
 |  | 
 |   AutocompleteInput input(u"f", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   ASSERT_EQ(result.size(), 3u); | 
 |  | 
 |   auto* match = result.match_at(0); | 
 |   EXPECT_EQ(match->type, AutocompleteMatchType::SEARCH_SUGGEST); | 
 |   // Should not inherit the search entity suggestion's relevance; only the | 
 |   // non-dup suggestion inherits from the dup suggestions; not vice versa. | 
 |   EXPECT_EQ(match->relevance, 900); | 
 |   EXPECT_TRUE(match->allowed_to_be_default_match); | 
 |   EXPECT_EQ(match->stripped_destination_url.spec(), "http://search/?q=foo"); | 
 |   // Expect that the Entity's ID was merged over to the plain match equivalent. | 
 |   EXPECT_EQ(match->entity_id, "/m/012abc"); | 
 |  | 
 |   match = result.match_at(1); | 
 |   // The search entity suggestion should be ranked higher than the higher | 
 |   // scoring 'foo2' search suggestion. When demoting default entity suggestions, | 
 |   // they are moved to position 2 rather than re-ranked according to their | 
 |   // relevance. | 
 |   EXPECT_EQ(match->type, AutocompleteMatchType::SEARCH_SUGGEST_ENTITY); | 
 |   EXPECT_EQ(match->relevance, 1000); | 
 |   EXPECT_TRUE(match->allowed_to_be_default_match); | 
 |   EXPECT_EQ(match->stripped_destination_url.spec(), "http://search/?q=foo"); | 
 |   EXPECT_EQ(match->entity_id, "/m/012abc"); | 
 |  | 
 |   match = result.match_at(2); | 
 |   EXPECT_EQ(match->type, AutocompleteMatchType::SEARCH_SUGGEST); | 
 |   EXPECT_EQ(match->relevance, 1200); | 
 |   EXPECT_FALSE(match->allowed_to_be_default_match); | 
 |   EXPECT_EQ(match->stripped_destination_url.spec(), "http://search/?q=foo2"); | 
 |   EXPECT_TRUE(match->entity_id.empty()); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, | 
 |        SortAndCullDontPreferNonEntityNonDefaultForDefaultSuggestion) { | 
 |   // When the top scoring allowed_to_be_default suggestion is a search entity, | 
 |   // and there are no duplicate allowed_to_be_default suggestions, keep the | 
 |   // search entity suggestion default. | 
 |  | 
 |   std::vector<EntityTestData> test_cases = { | 
 |       {AutocompleteMatchType::SEARCH_SUGGEST_ENTITY, GetProvider(1), | 
 |        "http://search/?q=foo", 1000, true}, | 
 |       // A duplicate non-allowed_to_be_default search suggestion should not be | 
 |       // preferred to a lower ranked search entity suggestion. | 
 |       {AutocompleteMatchType::SEARCH_SUGGEST, GetProvider(1), | 
 |        "http://search/?q=foo", 1300}, | 
 |       // A non-duplicate allowed_to_be_default search suggestion should not be | 
 |       // preferred to a higher ranked search entity suggestion. | 
 |       {AutocompleteMatchType::SEARCH_SUGGEST, GetProvider(1), | 
 |        "http://search/?q=foo2", 900, true}, | 
 |   }; | 
 |   ACMatches matches; | 
 |   PopulateEntityTestCases(test_cases, &matches); | 
 |  | 
 |   AutocompleteInput input(u"f", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   ASSERT_EQ(result.size(), 2u); | 
 |  | 
 |   auto* match = result.match_at(0); | 
 |   EXPECT_EQ(match->type, AutocompleteMatchType::SEARCH_SUGGEST_ENTITY); | 
 |   EXPECT_EQ(match->relevance, 1300); | 
 |   EXPECT_TRUE(match->allowed_to_be_default_match); | 
 |  | 
 |   match = result.match_at(1); | 
 |   EXPECT_EQ(match->type, AutocompleteMatchType::SEARCH_SUGGEST); | 
 |   EXPECT_EQ(match->relevance, 900); | 
 |   EXPECT_TRUE(match->allowed_to_be_default_match); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullPreferEntitiesFillIntoEditMustMatch) { | 
 |   // clang-format off | 
 |   std::vector<EntityTestData> test_cases = { | 
 |     { | 
 |       AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED, GetProvider(1), | 
 |       "http://search/?q=foo", 1100, false, "foo", "" | 
 |     }, | 
 |     { | 
 |       AutocompleteMatchType::SEARCH_SUGGEST_ENTITY, GetProvider(1), | 
 |       "http://search/?q=foo", 1000, false, "foobar", "" | 
 |     }, | 
 |     { | 
 |       AutocompleteMatchType::SEARCH_SUGGEST, GetProvider(1), | 
 |       "http://search/?q=foo", 900, true, "foo", "oo" | 
 |     }, | 
 |   }; | 
 |   // clang-format on | 
 |   ACMatches matches; | 
 |   PopulateEntityTestCases(test_cases, &matches); | 
 |  | 
 |   AutocompleteInput input(u"f", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   // The entity suggestion won't be chosen in this case because it has a non- | 
 |   // matching value for fill_into_edit. | 
 |   EXPECT_EQ(1UL, result.size()); | 
 |   // But the final type will have the specialized Search History type, since | 
 |   // that's consumed into the final match during the merge step. | 
 |   EXPECT_EQ(AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED, | 
 |             result.match_at(0)->type); | 
 |   EXPECT_EQ(1100, result.match_at(0)->relevance); | 
 |   EXPECT_TRUE(result.match_at(0)->allowed_to_be_default_match); | 
 |   EXPECT_EQ(u"oo", result.match_at(0)->inline_autocompletion); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, | 
 |        SortAndCullPreferEntitiesButKeepDefaultPlainMatches) { | 
 |   // clang-format off | 
 |   std::vector<EntityTestData> test_cases = { | 
 |     { | 
 |       AutocompleteMatchType::SEARCH_SUGGEST, GetProvider(1), | 
 |       "http://search/?q=foo", 1001, true, "foo", "" | 
 |     }, | 
 |     { | 
 |       AutocompleteMatchType::SEARCH_SUGGEST_ENTITY, GetProvider(1), | 
 |       "http://search/?q=foo", 1000, false, "foo", "" | 
 |     }, | 
 |     { | 
 |       AutocompleteMatchType::SEARCH_SUGGEST, GetProvider(1), | 
 |       "http://search/?q=foo", 900, true, "foo", "oo" | 
 |     }, | 
 |   }; | 
 |   // clang-format on | 
 |   ACMatches matches; | 
 |   PopulateEntityTestCases(test_cases, &matches); | 
 |  | 
 |   AutocompleteInput input(u"f", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   // The first result will be a plain match. | 
 |   EXPECT_EQ(2UL, result.size()); | 
 |   EXPECT_EQ(AutocompleteMatchType::SEARCH_SUGGEST, result.match_at(0)->type); | 
 |   EXPECT_EQ(1001, result.match_at(0)->relevance); | 
 |  | 
 |   // The second result will be the result of deduping the Suggest Entity with | 
 |   // the third result. It should have still consumed the inline autocomplete | 
 |   // and allowed_to_be_default qualities from the other two. | 
 |   EXPECT_EQ(AutocompleteMatchType::SEARCH_SUGGEST_ENTITY, | 
 |             result.match_at(1)->type); | 
 |   EXPECT_EQ(1001, result.match_at(1)->relevance); | 
 |   EXPECT_TRUE(result.match_at(1)->allowed_to_be_default_match); | 
 |   EXPECT_EQ(u"oo", result.match_at(1)->inline_autocompletion); | 
 | } | 
 |  | 
 | TEST_F( | 
 |     AutocompleteResultTest, | 
 |     SortAndCullPreferNonEntitySpecializedSearchSuggestionForDefaultSuggestion) { | 
 |   // When selecting among multiple duplicate non-entity suggestions, prefer | 
 |   // promoting the one that is a SEARCH_SUGGEST or other "specialized" search | 
 |   // suggestion. | 
 |  | 
 |   std::vector<EntityTestData> test_cases = { | 
 |       // Entity search suggestion. | 
 |       {AutocompleteMatchType::SEARCH_SUGGEST_ENTITY, GetProvider(1), | 
 |        "http://search/?q=foo", 1200, true}, | 
 |       // Duplicate non-entity SEARCH_SUGGEST suggestion should be preferred | 
 |       // above the other options (even when scoring lower). | 
 |       {AutocompleteMatchType::SEARCH_SUGGEST, GetProvider(1), | 
 |        "http://search/?q=foo", 1000, true}, | 
 |       // Duplicate non-entity match which is neither a SEARCH_SUGGEST nor a | 
 |       // "specialized" search suggestion. | 
 |       {AutocompleteMatchType::SEARCH_HISTORY, GetProvider(1), | 
 |        "http://search/?q=foo", 1100, true}, | 
 |   }; | 
 |   ACMatches matches; | 
 |   PopulateEntityTestCases(test_cases, &matches); | 
 |  | 
 |   // Simulate the search provider pre-grouping duplicate suggestions. | 
 |   matches[0].duplicate_matches.push_back(matches.back()); | 
 |   matches.pop_back(); | 
 |  | 
 |   matches[0].duplicate_matches.push_back(matches.back()); | 
 |   matches.pop_back(); | 
 |  | 
 |   AutocompleteInput input(u"f", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   ASSERT_EQ(result.size(), 2u); | 
 |  | 
 |   auto* match = result.match_at(0); | 
 |   // The non-entity SEARCH_SUGGEST suggestion should be promoted as the top | 
 |   // match. | 
 |   EXPECT_EQ(match->type, AutocompleteMatchType::SEARCH_SUGGEST); | 
 |   EXPECT_TRUE(match->allowed_to_be_default_match); | 
 |  | 
 |   match = result.match_at(1); | 
 |   // The entity search suggestion should have been demoted. | 
 |   EXPECT_EQ(match->type, AutocompleteMatchType::SEARCH_SUGGEST_ENTITY); | 
 |   EXPECT_TRUE(match->allowed_to_be_default_match); | 
 |  | 
 |   EXPECT_EQ(match->duplicate_matches.size(), 1u); | 
 |  | 
 |   // The non-entity match which is neither a SEARCH_SUGGEST nor a "specialized" | 
 |   // search suggestion should remain in |duplicate_matches| (i.e. it's not | 
 |   // promoted as the top match). | 
 |   EXPECT_EQ(match->duplicate_matches.at(0).type, | 
 |             AutocompleteMatchType::SEARCH_HISTORY); | 
 |   EXPECT_TRUE(match->duplicate_matches.at(0).allowed_to_be_default_match); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullPromoteDuplicateSearchURLs) { | 
 |   // Register a template URL that corresponds to 'foo' search engine. | 
 |   TemplateURLData url_data; | 
 |   url_data.SetShortName(u"unittest"); | 
 |   url_data.SetKeyword(u"foo"); | 
 |   url_data.SetURL("http://www.foo.com/s?q={searchTerms}"); | 
 |   template_url_service().Add(std::make_unique<TemplateURL>(url_data)); | 
 |  | 
 |   TestData data[] = { | 
 |       {0, 1, 1300, false}, {1, 1, 1200, true}, {2, 1, 1100, true}, | 
 |       {3, 1, 1000, true},  {4, 2, 900, true}, | 
 |   }; | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   // Note that 0, 2 and 3 will compare equal after stripping. | 
 |   matches[0].destination_url = GURL("http://www.foo.com/s?q=foo"); | 
 |   matches[1].destination_url = GURL("http://www.foo.com/s?q=foo2"); | 
 |   matches[2].destination_url = GURL("http://www.foo.com/s?q=foo&oq=f"); | 
 |   matches[3].destination_url = GURL("http://www.foo.com/s?q=foo&aqs=0"); | 
 |   matches[4].destination_url = GURL("http://www.foo.com/"); | 
 |  | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   // We expect the 3rd and 4th results to be removed. | 
 |   ASSERT_EQ(3U, result.size()); | 
 |   EXPECT_EQ("http://www.foo.com/s?q=foo&oq=f", | 
 |             result.match_at(0)->destination_url.spec()); | 
 |   EXPECT_EQ(1300, result.match_at(0)->relevance); | 
 |   EXPECT_EQ("http://www.foo.com/s?q=foo2", | 
 |             result.match_at(1)->destination_url.spec()); | 
 |   EXPECT_EQ(1200, result.match_at(1)->relevance); | 
 |   EXPECT_EQ("http://www.foo.com/", result.match_at(2)->destination_url.spec()); | 
 |   EXPECT_EQ(900, result.match_at(2)->relevance); | 
 | } | 
 |  | 
 | #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) | 
 | TEST_F(AutocompleteResultTest, SortAndCullFeaturedSearchBeforeStarterPack) { | 
 |   base::test::ScopedFeatureList feature_list; | 
 |   feature_list.InitAndEnableFeatureWithParameters( | 
 |       omnibox::kUIExperimentMaxAutocompleteMatches, | 
 |       {{OmniboxFieldTrial::kUIMaxAutocompleteMatchesParam, "5"}}); | 
 |   TestData data[] = { | 
 |       {1, 1, 500, false, {}, AutocompleteMatchType::STARTER_PACK}, | 
 |       {2, 1, 500, false, {}, AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH}, | 
 |       {3, 1, 500, false, {}, AutocompleteMatchType::STARTER_PACK}, | 
 |       {4, 1, 500, false, {}, AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH}, | 
 |       {5, 2, 900, true, {}, AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED}, | 
 |   }; | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |  | 
 |   AutocompleteInput input(u"@", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   ASSERT_EQ(5U, AutocompleteResult::GetMaxMatches(/*is_zero_suggest=*/false)); | 
 |   const std::array<TestData, 5> expected_data{{ | 
 |       // Default match ranks higher. | 
 |       {5, 2, 900, true, {}, AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED}, | 
 |       // Featured enterprise search. | 
 |       {2, 1, 500, false, {}, AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH}, | 
 |       {4, 1, 500, false, {}, AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH}, | 
 |       // Starter pack after featured search. | 
 |       {1, 1, 500, false, {}, AutocompleteMatchType::STARTER_PACK}, | 
 |       {3, 1, 500, false, {}, AutocompleteMatchType::STARTER_PACK}, | 
 |   }}; | 
 |   AssertResultMatches(result, expected_data); | 
 | } | 
 | #endif | 
 |  | 
 | TEST_F(AutocompleteResultTest, | 
 |        GroupSuggestionsBySearchVsURLHonorsProtectedSuggestions) { | 
 |   base::test::ScopedFeatureList feature_list; | 
 |   feature_list.InitAndEnableFeatureWithParameters( | 
 |       omnibox::kUIExperimentMaxAutocompleteMatches, | 
 |       {{OmniboxFieldTrial::kUIMaxAutocompleteMatchesParam, "7"}}); | 
 |   TestData data[] = { | 
 |       {0, 2, 400, true, {}, AutocompleteMatchType::HISTORY_TITLE}, | 
 |       {1, 1, 800, false, {}, AutocompleteMatchType::CLIPBOARD_URL}, | 
 |       {2, 1, 700, false, {}, AutocompleteMatchType::TILE_NAVSUGGEST}, | 
 |       {3, 1, 600, false, {}, AutocompleteMatchType::TILE_SUGGESTION}, | 
 |       {4, 1, 1000, false, {}, AutocompleteMatchType::HISTORY_URL}, | 
 |       {5, 1, 900, false, {}, AutocompleteMatchType::SEARCH_SUGGEST}, | 
 |       {6, 1, 800, false, {}, AutocompleteMatchType::SEARCH_SUGGEST}, | 
 |   }; | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |  | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResultForTesting result; | 
 |   result.AppendMatches(matches); | 
 |   result.GroupSuggestionsBySearchVsURL(std::next(result.matches_.begin()), | 
 |                                        result.matches_.end()); | 
 |  | 
 |   auto expected_data = std::to_array<TestData>({ | 
 |       {0, 2, 400, true, {}, AutocompleteMatchType::HISTORY_TITLE}, | 
 |       {1, 1, 800, false, {}, AutocompleteMatchType::CLIPBOARD_URL}, | 
 |       {2, 1, 700, false, {}, AutocompleteMatchType::TILE_NAVSUGGEST}, | 
 |       {3, 1, 600, false, {}, AutocompleteMatchType::TILE_SUGGESTION}, | 
 |       {5, 1, 900, false, {}, AutocompleteMatchType::SEARCH_SUGGEST}, | 
 |       {6, 1, 800, false, {}, AutocompleteMatchType::SEARCH_SUGGEST}, | 
 |       {4, 1, 1000, false, {}, AutocompleteMatchType::HISTORY_URL}, | 
 |   }); | 
 |  | 
 |   AssertResultMatches(result, base::span<TestData>(expected_data) | 
 |                                   .first(AutocompleteResult::GetMaxMatches())); | 
 | } | 
 |  | 
 | #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) | 
 | TEST_F(AutocompleteResultTest, GroupSuggestionsByExtension) { | 
 |   const auto group1 = omnibox::GROUP_UNSCOPED_EXTENSION_1; | 
 |   const auto group2 = omnibox::GROUP_UNSCOPED_EXTENSION_2; | 
 |  | 
 |   TestData data[] = { | 
 |       {0, 5, 400, false, {}, AutocompleteMatchType::HISTORY_TITLE, group1}, | 
 |       {1, 1, 800, false, {}, AutocompleteMatchType::CLIPBOARD_URL}, | 
 |       {2, 1, 700, false, {}, AutocompleteMatchType::TILE_NAVSUGGEST}, | 
 |       {3, 5, 600, false, {}, AutocompleteMatchType::HISTORY_TITLE, group2}, | 
 |       {4, 5, 1000, false, {}, AutocompleteMatchType::HISTORY_TITLE, group1}, | 
 |       {5, 2, 900, true, {}, AutocompleteMatchType::SEARCH_SUGGEST}, | 
 |       {6, 5, 800, false, {}, AutocompleteMatchType::HISTORY_TITLE, group2}, | 
 |   }; | 
 |  | 
 |   omnibox::GroupConfigMap suggestion_groups_map; | 
 |   suggestion_groups_map[group1].set_section( | 
 |       omnibox::SECTION_UNSCOPED_EXTENSION_1); | 
 |   suggestion_groups_map[group2].set_section( | 
 |       omnibox::SECTION_UNSCOPED_EXTENSION_2); | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResultForTesting result; | 
 |   result.MergeSuggestionGroupsMap(suggestion_groups_map); | 
 |   result.AppendMatches(matches); | 
 |  | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   TestData expected_data[] = { | 
 |       {5, 2, 900, true, {}, AutocompleteMatchType::SEARCH_SUGGEST}, | 
 |       {1, 1, 800, false, {}, AutocompleteMatchType::CLIPBOARD_URL}, | 
 |       {2, 1, 700, false, {}, AutocompleteMatchType::TILE_NAVSUGGEST}, | 
 |       {4, 5, 1000, false, {}, AutocompleteMatchType::HISTORY_TITLE, group1}, | 
 |       {0, 5, 400, false, {}, AutocompleteMatchType::HISTORY_TITLE, group1}, | 
 |       {6, 5, 800, false, {}, AutocompleteMatchType::HISTORY_TITLE, group2}, | 
 |       {3, 5, 600, false, {}, AutocompleteMatchType::HISTORY_TITLE, group2}, | 
 |   }; | 
 |  | 
 |   AssertResultMatches(result, expected_data); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullMaxHistoryClusterSuggestions) { | 
 |   base::test::ScopedFeatureList feature_list; | 
 |   feature_list.InitAndDisableFeature(omnibox::kGroupingFrameworkForNonZPS); | 
 |  | 
 |   // Should limit history cluster suggestions to 1, even if there are no | 
 |   // alternative suggestions to display. | 
 |  | 
 |   ACMatches matches; | 
 |   const AutocompleteMatchTestData data[] = { | 
 |       {"url_1", AutocompleteMatchType::HISTORY_CLUSTER}, | 
 |       {"url_2", AutocompleteMatchType::HISTORY_CLUSTER}, | 
 |       {"url_3", AutocompleteMatchType::HISTORY_CLUSTER}, | 
 |   }; | 
 |   PopulateAutocompleteMatchesFromTestData(data, std::size(data), &matches); | 
 |   for (auto& m : matches) | 
 |     m.allowed_to_be_default_match = false; | 
 |  | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   ASSERT_EQ(result.size(), 1u); | 
 |   EXPECT_EQ(result.match_at(0)->type, AutocompleteMatchType::HISTORY_CLUSTER); | 
 | } | 
 | #endif | 
 |  | 
 | TEST_F(AutocompleteResultTest, SortAndCullMaxURLMatches) { | 
 |   base::test::ScopedFeatureList feature_list; | 
 |   feature_list.InitWithFeaturesAndParameters( | 
 |       {{omnibox::kUIExperimentMaxAutocompleteMatches, | 
 |         {{OmniboxFieldTrial::kUIMaxAutocompleteMatchesParam, "6"}}}, | 
 |        {omnibox::kOmniboxMaxURLMatches, | 
 |         {{OmniboxFieldTrial::kOmniboxMaxURLMatchesParam, "3"}}}}, | 
 |       {omnibox::kGroupingFrameworkForNonZPS}); | 
 |  | 
 |   EXPECT_TRUE(OmniboxFieldTrial::IsMaxURLMatchesFeatureEnabled()); | 
 |   EXPECT_EQ(OmniboxFieldTrial::GetMaxURLMatches(), 3u); | 
 |  | 
 |   // Case 1: Eject URL match for a search. | 
 |   // Does not apply to Android and iOS which picks top N matches and performs | 
 |   // group by search vs URL separately (Adaptive Suggestions). | 
 | #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) | 
 |   { | 
 |     ACMatches matches; | 
 |     const AutocompleteMatchTestData data[] = { | 
 |         {"http://search-what-you-typed/", | 
 |          AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED}, | 
 |         {"http://search-history/", AutocompleteMatchType::SEARCH_HISTORY}, | 
 |         {"http://history-url/", AutocompleteMatchType::HISTORY_URL}, | 
 |         {"http://history-title/", AutocompleteMatchType::HISTORY_TITLE}, | 
 |         {"http://url-what-you-typed/", | 
 |          AutocompleteMatchType::URL_WHAT_YOU_TYPED}, | 
 |         {"http://clipboard-url/", AutocompleteMatchType::CLIPBOARD_URL}, | 
 |         {"http://search-suggest/", AutocompleteMatchType::SEARCH_SUGGEST}, | 
 |     }; | 
 |     PopulateAutocompleteMatchesFromTestData(data, std::size(data), &matches); | 
 |  | 
 |     AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                             TestSchemeClassifier()); | 
 |     AutocompleteResult result; | 
 |     result.AppendMatches(matches); | 
 |     result.SortAndCull(input, &template_url_service(), | 
 |                        triggered_feature_service(), /*is_lens_active=*/false, | 
 |                        /*can_show_contextual_suggestions=*/false, | 
 |                        /*mia_enabled*/ false); | 
 |  | 
 |     // Expect the search suggest to be moved about URL suggestions due to | 
 |     // the logic which groups searches and URLs together. | 
 |     auto expected_types = std::to_array<AutocompleteMatchType::Type>({ | 
 |         AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, | 
 |         AutocompleteMatchType::SEARCH_HISTORY, | 
 |         AutocompleteMatchType::SEARCH_SUGGEST, | 
 |         AutocompleteMatchType::HISTORY_URL, | 
 |         AutocompleteMatchType::HISTORY_TITLE, | 
 |         AutocompleteMatchType::URL_WHAT_YOU_TYPED, | 
 |     }); | 
 |     EXPECT_EQ(result.size(), AutocompleteResult::GetMaxMatches()); | 
 |     for (size_t i = 0; i < result.size(); ++i) | 
 |       EXPECT_EQ(result.match_at(i)->type, expected_types[i]); | 
 |   } | 
 | #endif | 
 |  | 
 |   // Case 2: Do not eject URL match because there's no replacement. | 
 |   { | 
 |     ACMatches matches; | 
 |     const AutocompleteMatchTestData data[] = { | 
 |         {"http://search-what-you-typed/", | 
 |          AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED}, | 
 |         {"http://search-history/", AutocompleteMatchType::SEARCH_HISTORY}, | 
 |         {"http://history-url/", AutocompleteMatchType::HISTORY_URL}, | 
 |         {"http://history-title/", AutocompleteMatchType::HISTORY_TITLE}, | 
 |         {"http://url-what-you-typed/", | 
 |          AutocompleteMatchType::URL_WHAT_YOU_TYPED}, | 
 |         {"http://clipboard-url/", AutocompleteMatchType::CLIPBOARD_URL}, | 
 |         {"http://bookmark-title/", AutocompleteMatchType::BOOKMARK_TITLE}, | 
 |     }; | 
 |     PopulateAutocompleteMatchesFromTestData(data, std::size(data), &matches); | 
 |  | 
 |     AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                             TestSchemeClassifier()); | 
 |     AutocompleteResult result; | 
 |     result.AppendMatches(matches); | 
 |     result.SortAndCull(input, &template_url_service(), | 
 |                        triggered_feature_service(), /*is_lens_active=*/false, | 
 |                        /*can_show_contextual_suggestions=*/false, | 
 |                        /*mia_enabled*/ false); | 
 |  | 
 |     EXPECT_EQ(result.size(), AutocompleteResult::GetMaxMatches()); | 
 |     auto expected_types = std::to_array<AutocompleteMatchType::Type>({ | 
 |         AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, | 
 |         AutocompleteMatchType::SEARCH_HISTORY, | 
 |         AutocompleteMatchType::HISTORY_URL, | 
 |         AutocompleteMatchType::HISTORY_TITLE, | 
 |         AutocompleteMatchType::URL_WHAT_YOU_TYPED, | 
 |         AutocompleteMatchType::CLIPBOARD_URL, | 
 |     }); | 
 |     for (size_t i = 0; i < result.size(); ++i) | 
 |       EXPECT_EQ(result.match_at(i)->type, expected_types[i]) << i; | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, ConvertsOpenTabsCorrectly) { | 
 |   AutocompleteResult result; | 
 |   ACMatches matches; | 
 |   AutocompleteMatch match; | 
 |   match.destination_url = GURL("http://this-site-matches.com"); | 
 |   matches.push_back(match); | 
 |   match.destination_url = GURL("http://other-site-matches.com"); | 
 |   match.description = u"Some Other Site"; | 
 |   matches.push_back(match); | 
 |   match.destination_url = GURL("http://doesnt-match.com"); | 
 |   match.description = std::u16string(); | 
 |   matches.push_back(match); | 
 |   result.AppendMatches(matches); | 
 |  | 
 |   // Have IsTabOpenWithURL() return true for some URLs. | 
 |   FakeAutocompleteProviderClient client; | 
 |   static_cast<FakeTabMatcher&>(const_cast<TabMatcher&>(client.GetTabMatcher())) | 
 |       .set_url_substring_match("matches"); | 
 |  | 
 |   result.ConvertOpenTabMatches(&client, nullptr); | 
 |  | 
 |   EXPECT_TRUE(result.match_at(0)->has_tab_match.value_or(false)); | 
 |   EXPECT_TRUE(result.match_at(1)->has_tab_match.value_or(false)); | 
 |   EXPECT_FALSE(result.match_at(2)->has_tab_match.value_or(false)); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, AttachesPedals) { | 
 |   FakeAutocompleteProviderClient client; | 
 |   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()); | 
 |   client.set_pedal_provider( | 
 |       std::make_unique<OmniboxPedalProvider>(client, std::move(pedals))); | 
 |   EXPECT_NE(nullptr, client.GetPedalProvider()); | 
 |  | 
 |   AutocompleteResult result; | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |  | 
 |   // Populate result with test matches. | 
 |   { | 
 |     ACMatches matches; | 
 |     struct TestData : AutocompleteMatchTestData { | 
 |       std::string contents; | 
 |       TestData(std::string url, | 
 |                AutocompleteMatch::Type type, | 
 |                std::string contents) | 
 |           : AutocompleteMatchTestData{url, type}, contents(contents) {} | 
 |     }; | 
 |     const auto data = std::to_array<TestData>({ | 
 |         {"http://clear-history/", AutocompleteMatchType::SEARCH_SUGGEST, | 
 |          "clear history"}, | 
 |         {"http://search-what-you-typed/", | 
 |          AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, "search what you typed"}, | 
 |         {"http://search-history/", AutocompleteMatchType::SEARCH_HISTORY, | 
 |          "search history"}, | 
 |         {"http://history-url/", AutocompleteMatchType::HISTORY_URL, | 
 |          "history url"}, | 
 |         {"http://history-title/", AutocompleteMatchType::HISTORY_TITLE, | 
 |          "history title"}, | 
 |         {"http://url-what-you-typed/", | 
 |          AutocompleteMatchType::URL_WHAT_YOU_TYPED, "url what you typed"}, | 
 |         {"http://clipboard-url/", AutocompleteMatchType::CLIPBOARD_URL, | 
 |          "clipboard url"}, | 
 |         {"http://bookmark-title/", AutocompleteMatchType::BOOKMARK_TITLE, | 
 |          "bookmark title"}, | 
 |         {"http://entity-clear-history/", | 
 |          AutocompleteMatchType::SEARCH_SUGGEST_ENTITY, "clear history"}, | 
 |     }); | 
 |     PopulateAutocompleteMatchesFromTestData(data.data(), std::size(data), | 
 |                                             &matches); | 
 |     for (size_t i = 0; i < std::size(data); i++) { | 
 |       matches[i].contents = base::UTF8ToUTF16(data[i].contents); | 
 |     } | 
 |     result.AppendMatches(matches); | 
 |   } | 
 |  | 
 |   // Attach |pedal| to result matches where appropriate. | 
 |   result.AttachPedalsToMatches(input, client); | 
 |  | 
 |   // Ensure the entity suggestion doesn't get a pedal even though its contents | 
 |   // form a concept match. | 
 |   ASSERT_TRUE(std::prev(result.end())->actions.empty()); | 
 |  | 
 |   // The same concept-matching contents on a non-entity suggestion gets a pedal. | 
 |   ASSERT_TRUE(!result.begin()->actions.empty()); | 
 |  | 
 |   // Also ensure pedal can be retrieved with generic predicate. | 
 |   ASSERT_EQ(nullptr, | 
 |             std::prev(result.end())->GetActionWhere([](const auto& action) { | 
 |               return true; | 
 |             })); | 
 |   ASSERT_NE(nullptr, result.begin()->GetActionWhere([](const auto& action) { | 
 |     const auto* pedal = OmniboxPedal::FromAction(action.get()); | 
 |     return pedal && pedal->PedalId() == OmniboxPedalId::CLEAR_BROWSING_DATA; | 
 |   })); | 
 |  | 
 | // Android & iOS avoid attaching tab-switch actions by design. | 
 | #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) | 
 |   // Include a tab-switch action, which is common and shouldn't prevent | 
 |   // pedals from attaching to the same match. The first match has a URL | 
 |   // that triggers tab-switch action attachment with this fake matcher. | 
 |   static_cast<FakeTabMatcher&>(const_cast<TabMatcher&>(client.GetTabMatcher())) | 
 |       .set_url_substring_match("clear-history"); | 
 |   result.match_at(0)->actions.clear(); | 
 |   result.match_at(0)->has_tab_match.reset(); | 
 |   result.ConvertOpenTabMatches(&client, &input); | 
 |   EXPECT_EQ(result.match_at(0)->actions.size(), 1u); | 
 |   EXPECT_EQ(result.match_at(0)->GetActionAt(0u)->ActionId(), | 
 |             OmniboxActionId::TAB_SWITCH); | 
 |   result.AttachPedalsToMatches(input, client); | 
 |   EXPECT_EQ(result.match_at(0)->actions.size(), 2u); | 
 |   ASSERT_NE(nullptr, result.match_at(0)->GetActionWhere([](const auto& action) { | 
 |     const auto* pedal = OmniboxPedal::FromAction(action.get()); | 
 |     return pedal && pedal->PedalId() == OmniboxPedalId::CLEAR_BROWSING_DATA; | 
 |   })); | 
 | #endif | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, DocumentSuggestionsCanMergeButNotToDefault) { | 
 |   // Types are populated below to avoid introducing a new test data creation | 
 |   // process. | 
 |   TestData data[] = { | 
 |       {1, 4, 500, false},   // DOCUMENT result for url [1]. | 
 |       {1, 1, 1100, false},  // HISTORY result for url [1], higher priority. | 
 |       {2, 4, 600, false},   // DOCUMENT result for [2]. | 
 |       {2, 1, 1200, true},   // HISTORY result for url [2], higher priority, | 
 |                             // Can be default. | 
 |       {3, 4, 1000, false},  // DOCUMENT result for [3], higher priority | 
 |       {3, 1, 400, false},   // HISTORY result for url [3]. | 
 |   }; | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   matches[0].type = AutocompleteMatchType::DOCUMENT_SUGGESTION; | 
 |   static_cast<FakeAutocompleteProvider*>(matches[0].provider)->type_ = | 
 |       AutocompleteProvider::Type::TYPE_DOCUMENT; | 
 |   matches[1].type = AutocompleteMatchType::HISTORY_URL; | 
 |   matches[2].type = AutocompleteMatchType::DOCUMENT_SUGGESTION; | 
 |   static_cast<FakeAutocompleteProvider*>(matches[2].provider)->type_ = | 
 |       AutocompleteProvider::Type::TYPE_DOCUMENT; | 
 |   matches[3].type = AutocompleteMatchType::HISTORY_URL; | 
 |   matches[4].type = AutocompleteMatchType::DOCUMENT_SUGGESTION; | 
 |   static_cast<FakeAutocompleteProvider*>(matches[4].provider)->type_ = | 
 |       AutocompleteProvider::Type::TYPE_DOCUMENT; | 
 |   matches[5].type = AutocompleteMatchType::HISTORY_URL; | 
 |  | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   // We expect three results: | 
 |   // The document result for [1] may override the history result. | 
 |   // The document result for [2] cannot override a potentially-default result. | 
 |   // The document result for [3] is already higher-priority. | 
 |   EXPECT_EQ(result.size(), 3u); | 
 |  | 
 |   // First result should be the default with its original top-ranking score. | 
 |   EXPECT_EQ(result.match_at(0)->relevance, 1200); | 
 |   EXPECT_EQ(AutocompleteMatchType::HISTORY_URL, result.match_at(0)->type); | 
 |   EXPECT_TRUE(result.match_at(0)->allowed_to_be_default_match); | 
 |  | 
 |   // Second result should be a document result with elevated score. | 
 |   // The second DOCUMENT result is deduped and effectively dropped. | 
 |   EXPECT_EQ(result.match_at(1)->relevance, 1100); | 
 |   EXPECT_EQ(AutocompleteMatchType::DOCUMENT_SUGGESTION, | 
 |             result.match_at(1)->type); | 
 |   EXPECT_FALSE(result.match_at(1)->allowed_to_be_default_match); | 
 |  | 
 |   // Third result should be a document with original score. The history result | 
 |   // it duped against is lower-priority. | 
 |   EXPECT_EQ(result.match_at(2)->relevance, 1000); | 
 |   EXPECT_EQ(AutocompleteMatchType::DOCUMENT_SUGGESTION, | 
 |             result.match_at(2)->type); | 
 |   EXPECT_FALSE(result.match_at(2)->allowed_to_be_default_match); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, CalculateNumMatchesPerUrlCountTest) { | 
 |   CompareWithDemoteByType<AutocompleteMatch> comparison_object( | 
 |       metrics::OmniboxEventProto::OTHER); | 
 |   enum SuggestionType { search, url }; | 
 |  | 
 |   auto test = [comparison_object](std::string base_limit, | 
 |                                   std::string url_cutoff, | 
 |                                   std::string increased_limit, | 
 |                                   std::vector<SuggestionType> types, | 
 |                                   size_t expected_num_matches) { | 
 |     base::test::ScopedFeatureList feature_list; | 
 |     feature_list.InitWithFeaturesAndParameters( | 
 |         {{omnibox::kDynamicMaxAutocomplete, | 
 |           {{OmniboxFieldTrial::kDynamicMaxAutocompleteUrlCutoffParam, | 
 |             url_cutoff}, | 
 |            {OmniboxFieldTrial::kDynamicMaxAutocompleteIncreasedLimitParam, | 
 |             increased_limit}}}, | 
 |          {omnibox::kUIExperimentMaxAutocompleteMatches, | 
 |           {{OmniboxFieldTrial::kUIMaxAutocompleteMatchesParam, base_limit}}}}, | 
 |         {}); | 
 |  | 
 |     ACMatches matches; | 
 |     for (auto type : types) { | 
 |       AutocompleteMatch m; | 
 |       m.relevance = 100; | 
 |       if (type) | 
 |         m.type = AutocompleteMatchType::URL_WHAT_YOU_TYPED; | 
 |       matches.push_back(m); | 
 |     } | 
 |     const size_t num_matches = AutocompleteResult::CalculateNumMatches( | 
 |         false, AutocompleteInput::FeaturedKeywordMode::kFalse, matches, | 
 |         comparison_object); | 
 |     EXPECT_EQ(num_matches, expected_num_matches); | 
 |   }; | 
 |  | 
 |   test("2", "0", "4", {search}, 1); | 
 |   test("2", "0", "4", {search, search, search, search, search}, 4); | 
 |   test("2", "0", "4", {search, search, search, search, url}, 4); | 
 |   test("2", "0", "4", {search, search, search, url, search}, 3); | 
 |   test("2", "0", "4", {search, search, url, search, search}, 2); | 
 |   test("2", "0", "4", {search, url, search, search, search}, 2); | 
 |   test("2", "1", "4", {search, url, search, search, search}, 4); | 
 |   test("2", "1", "4", {search, url, search, url, search}, 3); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, | 
 |        CalculateNumMatchesPerUrlCountWithUnscopedExtensions) { | 
 |   CompareWithDemoteByType<AutocompleteMatch> comparison_object( | 
 |       metrics::OmniboxEventProto::OTHER); | 
 |   enum SuggestionType { search, url }; | 
 |  | 
 |   auto test = [this, comparison_object]( | 
 |                   std::string base_limit, std::string url_cutoff, | 
 |                   std::string increased_limit, | 
 |                   std::vector<std::pair<SuggestionType, int>> | 
 |                       suggestion_and_provider_type, | 
 |                   size_t expected_num_matches) { | 
 |     base::test::ScopedFeatureList feature_list; | 
 |     feature_list.InitWithFeaturesAndParameters( | 
 |         {{omnibox::kDynamicMaxAutocomplete, | 
 |           {{OmniboxFieldTrial::kDynamicMaxAutocompleteUrlCutoffParam, | 
 |             url_cutoff}, | 
 |            {OmniboxFieldTrial::kDynamicMaxAutocompleteIncreasedLimitParam, | 
 |             increased_limit}}}, | 
 |          {omnibox::kUIExperimentMaxAutocompleteMatches, | 
 |           {{OmniboxFieldTrial::kUIMaxAutocompleteMatchesParam, base_limit}}}}, | 
 |         {}); | 
 |  | 
 |     ACMatches matches; | 
 |     for (auto pair : suggestion_and_provider_type) { | 
 |       AutocompleteMatch m; | 
 |       m.relevance = 100; | 
 |       if (pair.first) { | 
 |         m.type = AutocompleteMatchType::URL_WHAT_YOU_TYPED; | 
 |       } | 
 |       m.provider = GetProvider(pair.second); | 
 |       matches.push_back(m); | 
 |     } | 
 |     const size_t num_matches = AutocompleteResult::CalculateNumMatches( | 
 |         false, AutocompleteInput::FeaturedKeywordMode::kFalse, matches, | 
 |         comparison_object); | 
 |     EXPECT_EQ(num_matches, expected_num_matches); | 
 |   }; | 
 |  | 
 |   test("2", "0", "4", {{search, 2}, {search, 2}, {search, 5}}, 3); | 
 |   test("2", "0", "4", | 
 |        {{search, 2}, {search, 5}, {search, 2}, {search, 2}, {search, 2}}, 5); | 
 |   test("2", "1", "4", | 
 |        {{search, 2}, {url, 2}, {search, 5}, {search, 2}, {url, 2}}, 4); | 
 |   test("2", "1", "3", | 
 |        {{search, 2}, {search, 5}, {search, 5}, {search, 2}, {url, 2}}, 5); | 
 |   test("2", "2", "4", | 
 |        {{search, 5}, {url, 5}, {search, 5}, {url, 2}, {search, 2}, {search, 2}}, | 
 |        6); | 
 |   test("2", "1", "4", | 
 |        {{search, 5}, {search, 5}, {search, 2}, {url, 2}, {url, 2}, {url, 2}}, | 
 |        4); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, ClipboardSuggestionOnTopOfSearchSuggestionTest) { | 
 |   base::test::ScopedFeatureList feature_list; | 
 |   feature_list.InitAndDisableFeature(omnibox::kGroupingFrameworkForNonZPS); | 
 |  | 
 |   // clang-format off | 
 |   TestData data[] = { | 
 |       {1, 1, 500,  false}, | 
 |       {2, 2, 1100, false}, | 
 |       {3, 2, 1000, false}, | 
 |       {4, 1, 1300, false}, | 
 |       {5, 1, 1500, false}, | 
 |   }; | 
 |   // clang-format on | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   matches[0].type = AutocompleteMatchType::SEARCH_SUGGEST; | 
 |   static_cast<FakeAutocompleteProvider*>(matches[0].provider)->type_ = | 
 |       AutocompleteProvider::Type::TYPE_ZERO_SUGGEST_LOCAL_HISTORY; | 
 |   matches[1].type = AutocompleteMatchType::SEARCH_SUGGEST; | 
 |   static_cast<FakeAutocompleteProvider*>(matches[1].provider)->type_ = | 
 |       AutocompleteProvider::Type::TYPE_ZERO_SUGGEST_LOCAL_HISTORY; | 
 |   matches[2].type = AutocompleteMatchType::SEARCH_SUGGEST; | 
 |   static_cast<FakeAutocompleteProvider*>(matches[2].provider)->type_ = | 
 |       AutocompleteProvider::Type::TYPE_ZERO_SUGGEST_LOCAL_HISTORY; | 
 |   matches[3].type = AutocompleteMatchType::SEARCH_SUGGEST; | 
 |   static_cast<FakeAutocompleteProvider*>(matches[3].provider)->type_ = | 
 |       AutocompleteProvider::Type::TYPE_ZERO_SUGGEST_LOCAL_HISTORY; | 
 |   matches[4].type = AutocompleteMatchType::CLIPBOARD_URL; | 
 |   static_cast<FakeAutocompleteProvider*>(matches[4].provider)->type_ = | 
 |       AutocompleteProvider::Type::TYPE_CLIPBOARD; | 
 |  | 
 |   AutocompleteInput input(u"", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   EXPECT_EQ(result.size(), 5u); | 
 |   EXPECT_EQ(result.match_at(0)->relevance, 1500); | 
 |   EXPECT_EQ(AutocompleteMatchType::CLIPBOARD_URL, result.match_at(0)->type); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, MaybeCullTailSuggestions) { | 
 |   auto test = [&](std::vector<CullTailTestMatch> input_matches) { | 
 |     ACMatches matches; | 
 |     std::ranges::transform(input_matches, std::back_inserter(matches), | 
 |                            [&](const CullTailTestMatch& test_match) { | 
 |                              AutocompleteMatch match; | 
 |                              match.contents = test_match.id; | 
 |                              match.type = test_match.type; | 
 |                              match.allowed_to_be_default_match = | 
 |                                  test_match.allowed_default; | 
 |                              match.relevance = 1000; | 
 |                              return match; | 
 |                            }); | 
 |  | 
 |     auto page_classification = metrics::OmniboxEventProto::PageClassification:: | 
 |         OmniboxEventProto_PageClassification_OTHER; | 
 |     AutocompleteResultForTesting::MaybeCullTailSuggestions( | 
 |         &matches, {page_classification}); | 
 |  | 
 |     std::vector<CullTailTestMatch> output_matches; | 
 |     std::ranges::transform( | 
 |         matches, std::back_inserter(output_matches), [](const auto& match) { | 
 |           return CullTailTestMatch{match.contents, match.type, | 
 |                                    match.allowed_to_be_default_match}; | 
 |         }); | 
 |     return output_matches; | 
 |   }; | 
 |  | 
 |   // When there are no suggestions, should return no suggestions. | 
 |   EXPECT_THAT(test({}), testing::ElementsAre()); | 
 |  | 
 |   // T = tail, N = non-tail; D = default-able | 
 |   CullTailTestMatch t{u"T", AutocompleteMatchType::SEARCH_SUGGEST_TAIL, false}; | 
 |   CullTailTestMatch n{u"N", AutocompleteMatchType::SEARCH_SUGGEST, false}; | 
 |   CullTailTestMatch td{u"TD", AutocompleteMatchType::SEARCH_SUGGEST_TAIL, true}; | 
 |   CullTailTestMatch nd{u"ND", AutocompleteMatchType::SEARCH_SUGGEST, true}; | 
 |  | 
 |   // When there are only non-tail suggestions, no suggestions should be culled. | 
 |   EXPECT_THAT(test({n, n}), testing::ElementsAre(n, n)); | 
 |   EXPECT_THAT(test({nd, nd}), testing::ElementsAre(nd, nd)); | 
 |   EXPECT_THAT(test({nd, n}), testing::ElementsAre(nd, n)); | 
 |  | 
 |   // When there are only tail suggestions, no suggestions should be culled. | 
 |   EXPECT_THAT(test({t, t}), testing::ElementsAre(t, t)); | 
 |   EXPECT_THAT(test({td, td}), testing::ElementsAre(td, td)); | 
 |   EXPECT_THAT(test({td, t}), testing::ElementsAre(td, t)); | 
 |  | 
 |   // When there is exactly 1 non-tail suggestions and it is default-able, tail | 
 |   // suggestions should not be culled. But they should be prevented from being | 
 |   // default. | 
 |   // A tail suggest that was originally default-able (`td`), but was | 
 |   // prevented from being default. | 
 |   CullTailTestMatch tdp{u"TD", AutocompleteMatchType::SEARCH_SUGGEST_TAIL, | 
 |                         false}; | 
 |   EXPECT_THAT(test({nd, t, t}), testing::ElementsAre(nd, t, t)); | 
 |   EXPECT_THAT(test({nd, td, td}), testing::ElementsAre(nd, tdp, tdp)); | 
 |   EXPECT_THAT(test({nd, td, t}), testing::ElementsAre(nd, tdp, t)); | 
 |  | 
 |   // When there is exactly 1 non-tail suggestions and it is not default-able, | 
 |   // either it or the tail suggestions should be culled, depending on if there | 
 |   // is a default-able tail suggestion. | 
 |   EXPECT_THAT(test({n, t, t}), testing::ElementsAre(n)); | 
 |   EXPECT_THAT(test({n, td, td}), testing::ElementsAre(td, td)); | 
 |   EXPECT_THAT(test({n, td, t}), testing::ElementsAre(td, t)); | 
 |  | 
 |   // When there are multiple non-tail suggestions, and at least 1 is | 
 |   // default-able, tail suggestions should be culled. | 
 |   EXPECT_THAT(test({nd, n, t}), testing::ElementsAre(nd, n)); | 
 |   EXPECT_THAT(test({nd, n, td, td}), testing::ElementsAre(nd, n)); | 
 |   EXPECT_THAT(test({nd, nd, td, t}), testing::ElementsAre(nd, nd)); | 
 |  | 
 |   // When there are multiple non-tail suggestions, and none of them are | 
 |   // default-able, either they or the tail suggestions should be called, | 
 |   // depending on if there is a default-able tail suggestion. | 
 |   EXPECT_THAT(test({n, n, t, t}), testing::ElementsAre(n, n)); | 
 |   EXPECT_THAT(test({n, n, td, td}), testing::ElementsAre(td, td)); | 
 |   EXPECT_THAT(test({n, n, td, t}), testing::ElementsAre(td, t)); | 
 |  | 
 |   // When there are both history cluster and tail suggestions, history cluster | 
 |   // suggestions should be hidden. | 
 |   // A history cluster suggestion. | 
 |   CullTailTestMatch h{u"H", AutocompleteMatchType::HISTORY_CLUSTER, false}; | 
 |   EXPECT_THAT(test({nd, td, t, h}), testing::ElementsAre(nd, tdp, t)); | 
 | } | 
 |  | 
 | #if !(BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)) | 
 | // Tests zps grouping for most visited sites and backfills with different | 
 | // suggestion limits. | 
 | TEST_F(AutocompleteResultTest, Desktop_MostVisitedSitesGrouping) { | 
 |   // Validate grouping logic in isolation from Lens action suggestion (which | 
 |   // would occupy an additional slot in the list of Omnibox suggestions). | 
 |   omnibox_feature_configs::ScopedConfigForTesting< | 
 |       omnibox_feature_configs::ContextualSearch> | 
 |       contextual_search_scoped_config; | 
 |   contextual_search_scoped_config.Get().show_open_lens_action = false; | 
 |  | 
 |   omnibox_feature_configs::ScopedConfigForTesting< | 
 |       omnibox_feature_configs::OmniboxZpsSuggestionLimit> | 
 |       scoped_config; | 
 |   scoped_config.Get().enabled = true; | 
 |   scoped_config.Get().max_suggestions = 8U; | 
 |   scoped_config.Get().max_url_suggestions = 4U; | 
 |   scoped_config.Get().max_search_suggestions = 4U; | 
 |  | 
 |   // Suggestion groups have the omnibox::SECTION_DEFAULT by default. | 
 |   const auto group1 = omnibox::GROUP_PERSONALIZED_ZERO_SUGGEST; | 
 |   const auto group2 = omnibox::GROUP_MOST_VISITED; | 
 |   omnibox::GroupConfigMap suggestion_groups_map; | 
 |   suggestion_groups_map[group1]; | 
 |   suggestion_groups_map[group2]; | 
 |  | 
 |   // Set up input for zero-prefix suggestions from the srp omnibox. | 
 |   AutocompleteInput omnibox_srp_zps_input( | 
 |       u"", | 
 |       metrics::OmniboxEventProto::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT, | 
 |       TestSchemeClassifier()); | 
 |   omnibox_srp_zps_input.set_focus_type( | 
 |       metrics::OmniboxFocusType::INTERACTION_FOCUS); | 
 |  | 
 |   { | 
 |     SCOPED_TRACE("Query from omnibox in srp"); | 
 |  | 
 |     TestData data[] = { | 
 |         {0, 1, 500, false, {}, AutocompleteMatchType::SEARCH_HISTORY, group1}, | 
 |         {1, 1, 490, false, {}, AutocompleteMatchType::SEARCH_HISTORY, group1}, | 
 |         {2, 1, 480, false, {}, AutocompleteMatchType::SEARCH_HISTORY, group1}, | 
 |         {3, 1, 470, false, {}, AutocompleteMatchType::SEARCH_HISTORY, group1}, | 
 |         {4, 1, 460, false, {}, AutocompleteMatchType::SEARCH_HISTORY, group1}, | 
 |         {5, 1, 450, false, {}, AutocompleteMatchType::HISTORY_URL, group2}, | 
 |         {6, 1, 440, false, {}, AutocompleteMatchType::HISTORY_URL, group2}, | 
 |         {7, 1, 430, false, {}, AutocompleteMatchType::HISTORY_URL, group2}, | 
 |         {8, 1, 420, false, {}, AutocompleteMatchType::HISTORY_URL, group2}, | 
 |         {9, 1, 410, false, {}, AutocompleteMatchType::HISTORY_URL, group2}, | 
 |     }; | 
 |     ACMatches matches; | 
 |     PopulateAutocompleteMatches(data, &matches); | 
 |  | 
 |     AutocompleteResult result; | 
 |     result.MergeSuggestionGroupsMap(suggestion_groups_map); | 
 |     result.AppendMatches(matches); | 
 |     result.SortAndCull(omnibox_srp_zps_input, &template_url_service(), | 
 |                        triggered_feature_service(), /*is_lens_active=*/false, | 
 |                        /*can_show_contextual_suggestions=*/false, | 
 |                        /*mia_enabled*/ false); | 
 |  | 
 |     // There should be 8 total suggestions, 4 from the group 1 and 4 from group | 
 |     // 2. Group 2 should follow group 1 since this is a search results page. | 
 |     const std::array<TestData, 8> expected_data{{ | 
 |         {5, 1, 500, false, {}, AutocompleteMatchType::SEARCH_HISTORY, group1}, | 
 |         {6, 1, 490, false, {}, AutocompleteMatchType::SEARCH_HISTORY, group1}, | 
 |         {7, 1, 480, false, {}, AutocompleteMatchType::SEARCH_HISTORY, group1}, | 
 |         {8, 1, 470, false, {}, AutocompleteMatchType::SEARCH_HISTORY, group1}, | 
 |         {0, 1, 450, false, {}, AutocompleteMatchType::HISTORY_URL, group2}, | 
 |         {1, 1, 440, false, {}, AutocompleteMatchType::HISTORY_URL, group2}, | 
 |         {2, 1, 430, false, {}, AutocompleteMatchType::HISTORY_URL, group2}, | 
 |         {3, 1, 420, false, {}, AutocompleteMatchType::HISTORY_URL, group2}, | 
 |     }}; | 
 |     AssertResultMatches(result, expected_data); | 
 |   } | 
 |  | 
 |   // Lower max suggestion and max url suggestion amount. | 
 |   scoped_config.Get().max_suggestions = 6U; | 
 |   scoped_config.Get().max_url_suggestions = 2U; | 
 |   // This shouldn't be applied since search results backfill url results. | 
 |   scoped_config.Get().max_search_suggestions = 2U; | 
 |  | 
 |   // Set up input for zero-prefix suggestions from a web page. | 
 |   AutocompleteInput web_zps_input(u"", metrics::OmniboxEventProto::OTHER, | 
 |                                   TestSchemeClassifier()); | 
 |   web_zps_input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS); | 
 |  | 
 |   { | 
 |     SCOPED_TRACE("Query from web page"); | 
 |  | 
 |     TestData data[] = { | 
 |         {0, 1, 500, false, {}, AutocompleteMatchType::HISTORY_URL, group2}, | 
 |         {1, 1, 490, false, {}, AutocompleteMatchType::HISTORY_URL, group2}, | 
 |         {2, 1, 480, false, {}, AutocompleteMatchType::HISTORY_URL, group2}, | 
 |         {3, 1, 470, false, {}, AutocompleteMatchType::HISTORY_URL, group2}, | 
 |         {4, 1, 460, false, {}, AutocompleteMatchType::HISTORY_URL, group2}, | 
 |         {5, 1, 450, false, {}, AutocompleteMatchType::SEARCH_HISTORY, group1}, | 
 |         {6, 1, 440, false, {}, AutocompleteMatchType::SEARCH_HISTORY, group1}, | 
 |         {7, 1, 430, false, {}, AutocompleteMatchType::SEARCH_HISTORY, group1}, | 
 |         {8, 1, 420, false, {}, AutocompleteMatchType::SEARCH_HISTORY, group1}, | 
 |         {9, 1, 410, false, {}, AutocompleteMatchType::SEARCH_HISTORY, group1}, | 
 |     }; | 
 |     ACMatches matches; | 
 |     PopulateAutocompleteMatches(data, &matches); | 
 |  | 
 |     AutocompleteResult result; | 
 |     result.MergeSuggestionGroupsMap(suggestion_groups_map); | 
 |     result.AppendMatches(matches); | 
 |     result.SortAndCull(web_zps_input, &template_url_service(), | 
 |                        triggered_feature_service(), /*is_lens_active=*/false, | 
 |                        /*can_show_contextual_suggestions=*/false, | 
 |                        /*mia_enabled*/ false); | 
 |  | 
 |     // There should be 4 total suggestions, 2 from the group 1 and 2 from group | 
 |     // 2. Group 1 should follow group 2 since this is a web page. | 
 |     const std::array<TestData, 4> expected_data{{ | 
 |         {0, 1, 500, false, {}, AutocompleteMatchType::HISTORY_URL, group2}, | 
 |         {1, 1, 490, false, {}, AutocompleteMatchType::HISTORY_URL, group2}, | 
 |         {5, 1, 450, false, {}, AutocompleteMatchType::SEARCH_HISTORY, group1}, | 
 |         {6, 1, 440, false, {}, AutocompleteMatchType::SEARCH_HISTORY, group1}, | 
 |     }}; | 
 |     AssertResultMatches(result, expected_data); | 
 |   } | 
 | } | 
 |  | 
 | void VerifyTriggeredFeatures( | 
 |     OmniboxTriggeredFeatureService* triggered_feature_service, | 
 |     std::vector<OmniboxTriggeredFeatureService::Feature> | 
 |         expected_triggered_features) { | 
 |   OmniboxTriggeredFeatureService::Features features_triggered; | 
 |   OmniboxTriggeredFeatureService::Features features_triggered_in_session; | 
 |   triggered_feature_service->RecordToLogs(&features_triggered, | 
 |                                           &features_triggered_in_session); | 
 |   triggered_feature_service->ResetSession(); | 
 |   EXPECT_THAT(features_triggered, | 
 |               testing::UnorderedElementsAreArray(expected_triggered_features)); | 
 |   EXPECT_THAT(features_triggered_in_session, | 
 |               testing::UnorderedElementsAreArray(expected_triggered_features)); | 
 | } | 
 |  | 
 | // NOTE: The tests below verify the behavior with the Grouping Framework for ZPS | 
 | // enabled. Suggestion groups only make sense within the Grouping Framework. | 
 | TEST_F(AutocompleteResultTest, Desktop_TwoColumnRealbox) { | 
 |   auto remote_secondary_zps_feature = | 
 |       metrics::OmniboxEventProto_Feature_REMOTE_SECONDARY_ZERO_SUGGEST; | 
 |  | 
 |   const auto group1 = omnibox::GROUP_PERSONALIZED_ZERO_SUGGEST; | 
 |   const auto group2 = omnibox::GROUP_TRENDS; | 
 |   const auto group3 = omnibox::GROUP_PREVIOUS_SEARCH_RELATED_ENTITY_CHIPS; | 
 |   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}, | 
 |       {5, 1, 450, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |       {6, 1, 440, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |       {7, 1, 430, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |   }; | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |  | 
 |   // Suggestion groups have the omnibox::SECTION_DEFAULT and | 
 |   // omnibox::GroupConfig_SideType_DEFAULT_PRIMARY by default. | 
 |   omnibox::GroupConfigMap suggestion_groups_map; | 
 |   suggestion_groups_map[group1]; | 
 |   suggestion_groups_map[group2]; | 
 |   suggestion_groups_map[group3].set_side_type( | 
 |       omnibox::GroupConfig_SideType_SECONDARY); | 
 |  | 
 |   // Set up input for zero-prefix suggestions from the omnibox. | 
 |   AutocompleteInput omnibox_zps_input( | 
 |       u"", | 
 |       metrics::OmniboxEventProto::INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS, | 
 |       TestSchemeClassifier()); | 
 |   omnibox_zps_input.set_focus_type( | 
 |       metrics::OmniboxFocusType::INTERACTION_FOCUS); | 
 |  | 
 |   { | 
 |     SCOPED_TRACE("Query from omnibox"); | 
 |     base::test::ScopedFeatureList feature_list; | 
 |     feature_list.InitWithFeaturesAndParameters( | 
 |         {}, | 
 |         {omnibox::kGroupingFrameworkForNonZPS, omnibox::kWebUIOmniboxPopup}); | 
 |     AutocompleteResult result; | 
 |     result.MergeSuggestionGroupsMap(suggestion_groups_map); | 
 |     result.AppendMatches(matches); | 
 |     result.SortAndCull(omnibox_zps_input, &template_url_service(), | 
 |                        triggered_feature_service(), /*is_lens_active=*/false, | 
 |                        /*can_show_contextual_suggestions=*/false, | 
 |                        /*mia_enabled*/ false); | 
 |  | 
 |     const std::array<TestData, 5> expected_data{{ | 
 |         // Previous search related suggestion chips are not permitted in the | 
 |         // omnibox even when the feature is enabled. | 
 |         {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); | 
 |  | 
 |     // Verify that the secondary zero-prefix suggestions were not triggered. | 
 |     VerifyTriggeredFeatures(triggered_feature_service(), {}); | 
 |   } | 
 |   { | 
 |     SCOPED_TRACE("Query from WebUI omnibox"); | 
 |     base::test::ScopedFeatureList feature_list; | 
 |     feature_list.InitWithFeaturesAndParameters( | 
 |         {{omnibox::kWebUIOmniboxPopup, {}}}, | 
 |         {omnibox::kGroupingFrameworkForNonZPS}); | 
 |     AutocompleteResult result; | 
 |     result.MergeSuggestionGroupsMap(suggestion_groups_map); | 
 |     result.AppendMatches(matches); | 
 |     result.SortAndCull(omnibox_zps_input, &template_url_service(), | 
 |                        triggered_feature_service(), /*is_lens_active=*/false, | 
 |                        /*can_show_contextual_suggestions=*/false, | 
 |                        /*mia_enabled*/ false); | 
 |  | 
 |     const std::array<TestData, 8> expected_data{{ | 
 |         // Previous search related suggestion chips are permitted in the omnibox | 
 |         // when the WebUI omnibox popup feature is enabled. | 
 |         {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}, | 
 |         {5, 1, 450, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |         {6, 1, 440, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |         {7, 1, 430, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |     }}; | 
 |     AssertResultMatches(result, expected_data); | 
 |  | 
 |     // Verify that the secondary zero-prefix suggestions were triggered. | 
 |     VerifyTriggeredFeatures(triggered_feature_service(), | 
 |                             {remote_secondary_zps_feature}); | 
 |   } | 
 |  | 
 |   // Set up input for zero-prefix suggestions from the realbox. | 
 |   AutocompleteInput realbox_zps_input( | 
 |       u"", metrics::OmniboxEventProto::NTP_REALBOX, TestSchemeClassifier()); | 
 |   realbox_zps_input.set_focus_type( | 
 |       metrics::OmniboxFocusType::INTERACTION_FOCUS); | 
 |  | 
 |   { | 
 |     SCOPED_TRACE("Query from realbox"); | 
 |     base::test::ScopedFeatureList feature_list; | 
 |     feature_list.InitWithFeaturesAndParameters( | 
 |         {}, | 
 |         {omnibox::kGroupingFrameworkForNonZPS, omnibox::kWebUIOmniboxPopup}); | 
 |     AutocompleteResult result; | 
 |     result.MergeSuggestionGroupsMap(suggestion_groups_map); | 
 |     result.AppendMatches(matches); | 
 |     result.SortAndCull(realbox_zps_input, &template_url_service(), | 
 |                        triggered_feature_service(), /*is_lens_active=*/false, | 
 |                        /*can_show_contextual_suggestions=*/false, | 
 |                        /*mia_enabled*/ false); | 
 |  | 
 |     const std::array<TestData, 8> 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}, | 
 |         {5, 1, 450, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |         {6, 1, 440, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |         {7, 1, 430, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |     }}; | 
 |     AssertResultMatches(result, expected_data); | 
 |  | 
 |     // Verify that the secondary zero-prefix suggestions were triggered. | 
 |     VerifyTriggeredFeatures(triggered_feature_service(), | 
 |                             {remote_secondary_zps_feature}); | 
 |   } | 
 |   { | 
 |     SCOPED_TRACE("Query from realbox - no secondary matches"); | 
 |     base::test::ScopedFeatureList feature_list; | 
 |     feature_list.InitWithFeaturesAndParameters( | 
 |         {}, | 
 |         {omnibox::kGroupingFrameworkForNonZPS, omnibox::kWebUIOmniboxPopup}); | 
 |     AutocompleteResult result; | 
 |     result.MergeSuggestionGroupsMap(suggestion_groups_map); | 
 |     // Clear the SideType_SECONDARY from the 3rd group. | 
 |     result.suggestion_groups_map_[group3].clear_side_type(); | 
 |     result.AppendMatches(matches); | 
 |     result.SortAndCull(realbox_zps_input, &template_url_service(), | 
 |                        triggered_feature_service(), /*is_lens_active=*/false, | 
 |                        /*can_show_contextual_suggestions=*/false, | 
 |                        /*mia_enabled*/ false); | 
 |  | 
 |     const std::array<TestData, 5> expected_data{{ | 
 |         // Previous search related suggestion chips not permitted when their | 
 |         // `SideType` is not SideType_Secondary. | 
 |         {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); | 
 |  | 
 |     // Verify that the secondary zero-prefix suggestions were not triggered. | 
 |     VerifyTriggeredFeatures(triggered_feature_service(), {}); | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, Desktop_ZpsGroupingIPH) { | 
 |   base::test::ScopedFeatureList feature_list; | 
 |   feature_list.InitAndEnableFeature(omnibox::kStarterPackIPH); | 
 |  | 
 |   const auto group1 = omnibox::GROUP_PERSONALIZED_ZERO_SUGGEST; | 
 |   const auto group2 = omnibox::GROUP_ZERO_SUGGEST_IN_PRODUCT_HELP; | 
 |   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, group1}, | 
 |       {4, 1, 460, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1}, | 
 |       {5, 1, 450, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1}, | 
 |       {6, 1, 440, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1}, | 
 |       {7, 1, 430, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1}, | 
 |       {8, | 
 |        4, | 
 |        420, | 
 |        false, | 
 |        {}, | 
 |        AutocompleteMatchType::NULL_RESULT_MESSAGE, | 
 |        group2, | 
 |        "", | 
 |        IphType::kFeaturedEnterpriseSiteSearch}, | 
 |   }; | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |  | 
 |   // Suggestion groups have the omnibox::SECTION_DEFAULT by default. | 
 |   omnibox::GroupConfigMap suggestion_groups_map; | 
 |   suggestion_groups_map[group1]; | 
 |   suggestion_groups_map[group2]; | 
 |  | 
 |   // Set up input for zero-prefix suggestions from the omnibox. | 
 |   AutocompleteInput omnibox_zps_input( | 
 |       u"", | 
 |       metrics::OmniboxEventProto::INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS, | 
 |       TestSchemeClassifier()); | 
 |   omnibox_zps_input.set_focus_type( | 
 |       metrics::OmniboxFocusType::INTERACTION_FOCUS); | 
 |  | 
 |   { | 
 |     SCOPED_TRACE("Query from omnibox - with IPH"); | 
 |  | 
 |     AutocompleteResult result; | 
 |     result.MergeSuggestionGroupsMap(suggestion_groups_map); | 
 |     result.AppendMatches(matches); | 
 |     result.SortAndCull(omnibox_zps_input, &template_url_service(), | 
 |                        triggered_feature_service(), /*is_lens_active=*/false, | 
 |                        /*can_show_contextual_suggestions=*/false, | 
 |                        /*mia_enabled*/ false); | 
 |  | 
 |     // There should be 8 total suggestions, including the IPH suggestion. | 
 |     // With the IPH suggestion present, the 8th group1 suggestion should be | 
 |     // culled in favor of the IPH suggestion. | 
 |     const std::array<TestData, 8> 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, group1}, | 
 |         {4, 1, 460, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1}, | 
 |         {5, 1, 450, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1}, | 
 |         {6, 1, 440, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1}, | 
 |         {8, | 
 |          4, | 
 |          420, | 
 |          false, | 
 |          {}, | 
 |          AutocompleteMatchType::NULL_RESULT_MESSAGE, | 
 |          group2}, | 
 |     }}; | 
 |     AssertResultMatches(result, expected_data); | 
 |   } | 
 |  | 
 |   // Set up input for zero-prefix suggestions from the realbox. | 
 |   AutocompleteInput realbox_zps_input( | 
 |       u"", metrics::OmniboxEventProto::NTP_REALBOX, TestSchemeClassifier()); | 
 |   realbox_zps_input.set_focus_type( | 
 |       metrics::OmniboxFocusType::INTERACTION_FOCUS); | 
 |  | 
 |   { | 
 |     SCOPED_TRACE("Query from realbox"); | 
 |     AutocompleteResult result; | 
 |     result.MergeSuggestionGroupsMap(suggestion_groups_map); | 
 |     result.AppendMatches(matches); | 
 |     result.SortAndCull(realbox_zps_input, &template_url_service(), | 
 |                        triggered_feature_service(), /*is_lens_active=*/false, | 
 |                        /*can_show_contextual_suggestions=*/false, | 
 |                        /*mia_enabled*/ false); | 
 |  | 
 |     // The IPH suggestion should not be shown in the Realbox, even if it's | 
 |     // present in the list of matches. | 
 |     const std::array<TestData, 8> 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, group1}, | 
 |         {4, 1, 460, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1}, | 
 |         {5, 1, 450, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1}, | 
 |         {6, 1, 440, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1}, | 
 |         {7, 1, 430, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1}, | 
 |     }}; | 
 |     AssertResultMatches(result, expected_data); | 
 |   } | 
 |  | 
 |   { | 
 |     SCOPED_TRACE("Query from omnibox - without IPH"); | 
 |     // Remove the IPH suggestion from the list of matches. | 
 |     matches.clear(); | 
 |     PopulateAutocompleteMatches(UNSAFE_TODO({data, std::size(data) - 1}), | 
 |                                 &matches); | 
 |  | 
 |     AutocompleteResult result; | 
 |     result.MergeSuggestionGroupsMap(suggestion_groups_map); | 
 |     result.AppendMatches(matches); | 
 |     result.SortAndCull(omnibox_zps_input, &template_url_service(), | 
 |                        triggered_feature_service(), /*is_lens_active=*/false, | 
 |                        /*can_show_contextual_suggestions=*/false, | 
 |                        /*mia_enabled*/ false); | 
 |  | 
 |     // There should be 8 total suggestions, including the IPH suggestion. | 
 |     // With the IPH suggestion not present, all suggestion slots should be | 
 |     // filled with group1 suggestions. | 
 |     const std::array<TestData, 8> 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, group1}, | 
 |         {4, 1, 460, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1}, | 
 |         {5, 1, 450, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1}, | 
 |         {6, 1, 440, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1}, | 
 |         {7, 1, 430, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1}, | 
 |     }}; | 
 |     AssertResultMatches(result, expected_data); | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, SplitActionsToSuggestions) { | 
 |   FakeAutocompleteProviderClient client; | 
 |   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()); | 
 |   client.set_pedal_provider( | 
 |       std::make_unique<OmniboxPedalProvider>(client, std::move(pedals))); | 
 |   EXPECT_NE(nullptr, client.GetPedalProvider()); | 
 |  | 
 |   AutocompleteResult result; | 
 |   AutocompleteInput input(u"a", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |  | 
 |   { | 
 |     ACMatches matches; | 
 |     struct TestData : AutocompleteMatchTestData { | 
 |       std::string contents; | 
 |       TestData(std::string url, | 
 |                AutocompleteMatch::Type type, | 
 |                std::string contents) | 
 |           : AutocompleteMatchTestData{url, type}, contents(contents) {} | 
 |     }; | 
 |     const auto data = std::to_array<TestData>({ | 
 |         {"http://clear-history/", AutocompleteMatchType::SEARCH_SUGGEST, | 
 |          "clear history"}, | 
 |         {"http://search-what-you-typed/", | 
 |          AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, "search what you typed"}, | 
 |         {"http://search-history/", AutocompleteMatchType::SEARCH_HISTORY, | 
 |          "search history"}, | 
 |         {"http://history-url/", AutocompleteMatchType::HISTORY_URL, | 
 |          "history url"}, | 
 |     }); | 
 |     PopulateAutocompleteMatchesFromTestData(data.data(), std::size(data), | 
 |                                             &matches); | 
 |     for (size_t i = 0; i < std::size(data); i++) { | 
 |       matches[i].contents = base::UTF8ToUTF16(data[i].contents); | 
 |     } | 
 |     result.AppendMatches(matches); | 
 |   } | 
 |  | 
 |   // First, the pedal is attached as normal. | 
 |   result.AttachPedalsToMatches(input, client); | 
 |   EXPECT_TRUE(!result.begin()->actions.empty()); | 
 |   EXPECT_EQ(nullptr, result.match_at(1)->takeover_action); | 
 |   EXPECT_EQ(result.size(), 4u); | 
 |  | 
 |   // Then pedals are split out to dedicated suggestions with takeover action. | 
 |   // Note that by design, number of results is not changed. | 
 |   result.SplitActionsToSuggestions(); | 
 |   EXPECT_TRUE(result.begin()->actions.empty()); | 
 |   EXPECT_NE(nullptr, result.match_at(1)->takeover_action); | 
 |   EXPECT_EQ(result.size(), 4u); | 
 |  | 
 |   // Now for an artifically exaggerated case with two pedals on one match, | 
 |   // which doesn't happen naturally but is useful for testing the method. | 
 |   static_cast<FakeTabMatcher&>(const_cast<TabMatcher&>(client.GetTabMatcher())) | 
 |       .set_url_substring_match("clear-history"); | 
 |   result.AttachPedalsToMatches(input, client); | 
 |   EXPECT_EQ(result.match_at(0)->actions.size(), 1u); | 
 |   result.match_at(0)->has_tab_match.reset(); | 
 |   result.ConvertOpenTabMatches(&client, &input); | 
 |   EXPECT_EQ(result.match_at(0)->actions.size(), 2u); | 
 |   EXPECT_EQ(result.match_at(0)->GetActionAt(1u)->ActionId(), | 
 |             OmniboxActionId::TAB_SWITCH); | 
 |   result.match_at(0)->actions.push_back(result.match_at(0)->GetActionAt(0u)); | 
 |   EXPECT_EQ(result.match_at(0)->actions.size(), 3u); | 
 |   // We have three actions: pedal, tab-switch, pedal. Split and ensure | 
 |   // both pedals became dedicated suggestions. The first one from above | 
 |   // is still there and is not affected by splitting again. | 
 |   result.SplitActionsToSuggestions(); | 
 |   EXPECT_EQ(result.match_at(0)->actions.size(), 1u); | 
 |   EXPECT_EQ(result.match_at(0)->GetActionAt(0u)->ActionId(), | 
 |             OmniboxActionId::TAB_SWITCH); | 
 |   EXPECT_EQ(result.match_at(1)->takeover_action->ActionId(), | 
 |             OmniboxActionId::PEDAL); | 
 |   EXPECT_EQ(result.match_at(2)->takeover_action->ActionId(), | 
 |             OmniboxActionId::PEDAL); | 
 |   EXPECT_EQ(result.match_at(3)->takeover_action->ActionId(), | 
 |             OmniboxActionId::PEDAL); | 
 |   EXPECT_EQ(result.size(), 4u); | 
 | } | 
 |  | 
 | #endif  // !(BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)) | 
 |  | 
 | TEST_F(AutocompleteResultTest, BadDestinationUrls) { | 
 |   AutocompleteMatch empty_url_match; | 
 |   empty_url_match.destination_url = GURL(); | 
 |   AutocompleteMatch invalid_scheme_match; | 
 |   invalid_scheme_match.destination_url = GURL("bad_scheme"); | 
 |   AutocompleteMatch invalid_url_match; | 
 |   invalid_url_match.destination_url = GURL("https://?k=v"); | 
 |  | 
 |   ACMatches invalid_matches; | 
 |   invalid_matches.push_back(empty_url_match); | 
 |   invalid_matches.push_back(invalid_scheme_match); | 
 |   invalid_matches.push_back(invalid_url_match); | 
 |  | 
 |   AutocompleteResult result; | 
 |   EXPECT_DCHECK_DEATH(result.AppendMatches(invalid_matches)); | 
 |  | 
 |   // Result set of matches should remain empty. | 
 |   EXPECT_TRUE(result.empty()); | 
 | } | 
 |  | 
 | #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, &matches); | 
 |  | 
 |   // Suggestion groups have the omnibox::SECTION_DEFAULT and | 
 |   // omnibox::GroupConfig_SideType_DEFAULT_PRIMARY by default. | 
 |   omnibox::GroupConfigMap suggestion_groups_map; | 
 |   suggestion_groups_map[group1]; | 
 |   suggestion_groups_map[group2]; | 
 |   suggestion_groups_map[group3]; | 
 |  | 
 |   // Set up input for zero-prefix suggestions. | 
 |   AutocompleteInput zero_input(u"", metrics::OmniboxEventProto::NTP, | 
 |                                TestSchemeClassifier()); | 
 |   zero_input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS); | 
 |  | 
 |   // NOTE: | 
 |   // The tests below verify the behavior with the Grouping Framework for ZPS | 
 |   // enabled. This is intentional: Suggestion Groups make no sense outside of | 
 |   // the grouping framework. | 
 |  | 
 |   { | 
 |     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(), /*is_lens_active=*/false, | 
 |                        /*can_show_contextual_suggestions=*/false, | 
 |                        /*mia_enabled*/ false); | 
 |  | 
 |     const std::array<TestData, 5> expected_data{{ | 
 |         // Default suggestion comes 1st. | 
 |         {1, 1, 490, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group1}, | 
 |         // Other types include all of the Inspire Me queries. | 
 |         {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  // BUILDFLAG(IS_ANDROID) | 
 |  | 
 | TEST_F(AutocompleteResultTest, Android_UndedupTopSearch) { | 
 |   scoped_refptr<FakeAutocompleteProvider> provider = | 
 |       new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_SEARCH); | 
 |  | 
 |   // 4 different matches to cover variety of scenarios. | 
 |   // Matches are recognized by their type and actions presence. | 
 |   // `search` is marked as a duplicate of both `entity` matches. | 
 |   AutocompleteMatch what_you_typed( | 
 |       provider.get(), 1, false, AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED); | 
 |   AutocompleteMatch search(provider.get(), 1, false, | 
 |                            AutocompleteMatchType::SEARCH_SUGGEST); | 
 |   search.allowed_to_be_default_match = true; | 
 |  | 
 |   AutocompleteMatch entity_without_action( | 
 |       provider.get(), 1, false, AutocompleteMatchType::SEARCH_SUGGEST_ENTITY); | 
 |   AutocompleteMatch entity_with_action( | 
 |       provider.get(), 1, false, AutocompleteMatchType::SEARCH_SUGGEST_ENTITY); | 
 |   entity_with_action.actions.push_back(base::MakeRefCounted<FakeOmniboxAction>( | 
 |       OmniboxActionId::ACTION_IN_SUGGEST)); | 
 |   entity_with_action.duplicate_matches.push_back(search); | 
 |   entity_without_action.duplicate_matches.push_back(search); | 
 |  | 
 |   struct UndedupTestData { | 
 |     std::string test_name; | 
 |     std::vector<AutocompleteMatch> input; | 
 |     std::vector<AutocompleteMatch> expected_result; | 
 |   } test_cases[]{ | 
 |       {"no op with no matches", {}, {}}, | 
 |       {"no op with no entities / 1", {what_you_typed}, {what_you_typed}}, | 
 |       {"no op with no entities / 2", | 
 |        {what_you_typed, search}, | 
 |        {what_you_typed, search}}, | 
 |       {"no op with entities with no actions", | 
 |        {what_you_typed, entity_without_action}, | 
 |        {what_you_typed, entity_without_action}}, | 
 |       {"no op with entities with actions at low positions", | 
 |        {what_you_typed, entity_with_action}, | 
 |        {what_you_typed, entity_with_action}}, | 
 |       {"no rotation when promotion is enabled with no actions at top position", | 
 |        {entity_without_action}, | 
 |        {search, entity_without_action}}, | 
 |       {"rotation when promotion is enabled with actions at top position", | 
 |        {entity_with_action}, | 
 |        {entity_with_action, search}}, | 
 |   }; | 
 |  | 
 |   // Crete matches following the `input_matches_and_actions` input. | 
 |   // The input specifies what type of OMNIBOX_ACTION should be added to every | 
 |   // individual match. | 
 |   // Once done, run the trimming and verify that the output contains exactly the | 
 |   // matches we want to see. | 
 |   for (const auto& test_case : test_cases) { | 
 |     auto result = test_case.input; | 
 |     AutocompleteResult::UndedupTopSearchEntityMatch(&result); | 
 |  | 
 |     EXPECT_EQ(result.size(), test_case.expected_result.size()); | 
 |     for (size_t index = 0u; index < result.size(); ++index) { | 
 |       const auto& found_match = result[index]; | 
 |       const auto& expect_match = test_case.expected_result[index]; | 
 |       EXPECT_EQ(found_match.type, expect_match.type) | 
 |           << "at index " << index | 
 |           << " while testing variant: " << test_case.test_name; | 
 |       EXPECT_EQ(found_match.actions.size(), expect_match.actions.size()) | 
 |           << "at index " << index | 
 |           << " while testing variant: " << test_case.test_name; | 
 |       for (size_t action_index = 0u; action_index < found_match.actions.size(); | 
 |            ++action_index) { | 
 |         EXPECT_EQ(found_match.actions[action_index]->ActionId(), | 
 |                   expect_match.actions[action_index]->ActionId()) | 
 |             << "action " << action_index << " at index " << index | 
 |             << " while testing variant: " << test_case.test_name; | 
 |       } | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | #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, &matches); | 
 |  | 
 |   // Suggestion groups have the omnibox::SECTION_DEFAULT and | 
 |   // omnibox::GroupConfig_SideType_DEFAULT_PRIMARY by default. | 
 |   omnibox::GroupConfigMap suggestion_groups_map; | 
 |   suggestion_groups_map[group1]; | 
 |   suggestion_groups_map[group2]; | 
 |  | 
 |   // Set up input for zero-prefix suggestions. | 
 |   AutocompleteInput zero_input(u"", metrics::OmniboxEventProto::NTP, | 
 |                                TestSchemeClassifier()); | 
 |   zero_input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS); | 
 |  | 
 |   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(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   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}}, | 
 |        // ZPS | 
 |        {{PEDAL}, {PEDAL}, {PEDAL}, {}}, | 
 |        // Typed | 
 |        {{PEDAL}, {PEDAL}, {PEDAL}, {}}}, | 
 |       {"Actions are shown only in first position", | 
 |        {{ACTION_IN_SUGGEST}, | 
 |         {ACTION_IN_SUGGEST}, | 
 |         {ACTION_IN_SUGGEST}, | 
 |         {ACTION_IN_SUGGEST}}, | 
 |        // ZPS | 
 |        {{}, {}, {}, {}}, | 
 |        // Typed | 
 |        {{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}}, | 
 |        // ZPS | 
 |        {{PEDAL}, {PEDAL}, {PEDAL}, {}}, | 
 |        // Typed | 
 |        {{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}}, | 
 |        // ZPS | 
 |        {{PEDAL}, {PEDAL}, {PEDAL}, {}}, | 
 |        // Typed | 
 |        {{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}}, | 
 |        // ZPS | 
 |        {{ANSWER_ACTION}, {ANSWER_ACTION}, {ANSWER_ACTION}, {ANSWER_ACTION}}, | 
 |        // Typed | 
 |        {{ANSWER_ACTION}, {ANSWER_ACTION}, {ANSWER_ACTION}, {ANSWER_ACTION}}}, | 
 |       {"Answer actions suppressed when there are urls", | 
 |        {{PEDAL, ANSWER_ACTION}, | 
 |         {ANSWER_ACTION}, | 
 |         {ANSWER_ACTION}, | 
 |         {ANSWER_ACTION}}, | 
 |        // ZPS | 
 |        {{PEDAL}, {}, {}, {}}, | 
 |        // Typed | 
 |        {{PEDAL}, {}, {}, {}}, | 
 |        /* include_url= */ true}, | 
 |   }; | 
 |  | 
 |   // Crete matches following the `input_matches_and_actions` input. | 
 |   // The input specifies what type of OMNIBOX_ACTION should be added to every | 
 |   // individual match. | 
 |   // Once done, run the trimming and verify that the output contains exactly the | 
 |   // matches we want to see. | 
 |   auto run_test = [&](const FilterOmniboxActionsTestData& data) { | 
 |     // Create AutocompleteResult from the test 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::SuggestTemplateInfo::TemplateAction action; | 
 |           action.set_action_type( | 
 |               omnibox:: | 
 |                   SuggestTemplateInfo_TemplateAction_ActionType_DIRECTIONS); | 
 |           match.actions.push_back(base::MakeRefCounted<OmniboxActionInSuggest>( | 
 |               std::move(action), 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) { | 
 |           // Check results. | 
 |           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; | 
 |             } | 
 |           } | 
 |         }; | 
 |  | 
 |     // Run the trimmer. ZPS, then typed. | 
 |     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 | 
 |  | 
 | #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) | 
 | TEST_F(AutocompleteResultTest, ContextualSearchAblateOthers) { | 
 |   omnibox_feature_configs::ScopedConfigForTesting< | 
 |       omnibox_feature_configs::ContextualSearch> | 
 |       contextual_scoped_config; | 
 |   contextual_scoped_config.Get().contextual_zps_limit = 3U; | 
 |   contextual_scoped_config.Get().show_open_lens_action = true; | 
 |   contextual_scoped_config.Get() | 
 |       .contextual_suggestions_ablate_others_when_present = true; | 
 |  | 
 |   omnibox_feature_configs::ScopedConfigForTesting< | 
 |       omnibox_feature_configs::OmniboxZpsSuggestionLimit> | 
 |       zps_scoped_config; | 
 |   zps_scoped_config.Get().enabled = true; | 
 |   zps_scoped_config.Get().max_search_suggestions = 4U; | 
 |   zps_scoped_config.Get().max_url_suggestions = 4U; | 
 |   zps_scoped_config.Get().max_suggestions = 8U; | 
 |  | 
 |   const auto group1 = omnibox::GROUP_MOST_VISITED; | 
 |   const auto group2 = omnibox::GROUP_PERSONALIZED_ZERO_SUGGEST; | 
 |   const auto group3 = omnibox::GROUP_CONTEXTUAL_SEARCH; | 
 |  | 
 |   TestData data[] = { | 
 |       {0, 1, 1000, false, {}, AutocompleteMatchType::HISTORY_URL, group1}, | 
 |       {1, 1, 900, false, {}, AutocompleteMatchType::HISTORY_URL, group1}, | 
 |       {2, 2, 800, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group2}, | 
 |       {3, 2, 700, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group2}, | 
 |       {4, 2, 600, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |       {5, 2, 500, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |   }; | 
 |  | 
 |   // Suggestion groups have the omnibox::SECTION_DEFAULT by default. | 
 |   omnibox::GroupConfigMap suggestion_groups_map; | 
 |   suggestion_groups_map[group1]; | 
 |   suggestion_groups_map[group2]; | 
 |   suggestion_groups_map[group3]; | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   for (AutocompleteMatch& m : matches) { | 
 |     if (m.suggestion_group_id && m.suggestion_group_id.value() == group3) { | 
 |       m.subtypes.insert(omnibox::SuggestSubtype::SUBTYPE_CONTEXTUAL_SEARCH); | 
 |     } | 
 |   } | 
 |  | 
 |   AutocompleteInput input(u"", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS); | 
 |  | 
 |   AutocompleteResultForTesting result; | 
 |   result.MergeSuggestionGroupsMap(suggestion_groups_map); | 
 |   result.AppendMatches(matches); | 
 |  | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/true, | 
 |                      /*mia_enabled=*/false); | 
 |  | 
 |   // Non-contextual search & URL suggestions should have been ablated. | 
 |   const std::array<TestData, 2> expected_data{{ | 
 |       {4, 2, 600, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |       {5, 2, 500, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |   }}; | 
 |   AssertResultMatches(result, expected_data); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, ContextualSearchAblateOthers_AblateSearchOnly) { | 
 |   omnibox_feature_configs::ScopedConfigForTesting< | 
 |       omnibox_feature_configs::ContextualSearch> | 
 |       contextual_scoped_config; | 
 |   contextual_scoped_config.Get().contextual_zps_limit = 3U; | 
 |   contextual_scoped_config.Get().show_open_lens_action = true; | 
 |   contextual_scoped_config.Get() | 
 |       .contextual_suggestions_ablate_others_when_present = true; | 
 |   contextual_scoped_config.Get().contextual_suggestions_ablate_search_only = | 
 |       true; | 
 |  | 
 |   omnibox_feature_configs::ScopedConfigForTesting< | 
 |       omnibox_feature_configs::OmniboxZpsSuggestionLimit> | 
 |       zps_scoped_config; | 
 |   zps_scoped_config.Get().enabled = true; | 
 |   zps_scoped_config.Get().max_search_suggestions = 4U; | 
 |   zps_scoped_config.Get().max_url_suggestions = 4U; | 
 |   zps_scoped_config.Get().max_suggestions = 8U; | 
 |  | 
 |   const auto group1 = omnibox::GROUP_MOST_VISITED; | 
 |   const auto group2 = omnibox::GROUP_PERSONALIZED_ZERO_SUGGEST; | 
 |   const auto group3 = omnibox::GROUP_CONTEXTUAL_SEARCH; | 
 |  | 
 |   TestData data[] = { | 
 |       {0, 1, 1000, false, {}, AutocompleteMatchType::HISTORY_URL, group1}, | 
 |       {1, 1, 900, false, {}, AutocompleteMatchType::HISTORY_URL, group1}, | 
 |       {2, 2, 800, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group2}, | 
 |       {3, 2, 700, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group2}, | 
 |       {4, 2, 600, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |       {5, 2, 500, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |   }; | 
 |  | 
 |   // Suggestion groups have the omnibox::SECTION_DEFAULT by default. | 
 |   omnibox::GroupConfigMap suggestion_groups_map; | 
 |   suggestion_groups_map[group1]; | 
 |   suggestion_groups_map[group2]; | 
 |   suggestion_groups_map[group3]; | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   for (AutocompleteMatch& m : matches) { | 
 |     if (m.suggestion_group_id && m.suggestion_group_id.value() == group3) { | 
 |       m.subtypes.insert(omnibox::SuggestSubtype::SUBTYPE_CONTEXTUAL_SEARCH); | 
 |     } | 
 |   } | 
 |  | 
 |   AutocompleteInput input(u"", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS); | 
 |  | 
 |   AutocompleteResultForTesting result; | 
 |   result.MergeSuggestionGroupsMap(suggestion_groups_map); | 
 |   result.AppendMatches(matches); | 
 |  | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/true, | 
 |                      /*mia_enabled=*/false); | 
 |  | 
 |   // Only non-contextual search suggestions should have been ablated. | 
 |   const std::array<TestData, 4> expected_data{{ | 
 |       {0, 1, 1000, false, {}, AutocompleteMatchType::HISTORY_URL, group1}, | 
 |       {1, 1, 900, false, {}, AutocompleteMatchType::HISTORY_URL, group1}, | 
 |       {4, 2, 600, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |       {5, 2, 500, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |   }}; | 
 |   AssertResultMatches(result, expected_data); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, ContextualSearchAblateOthers_AblateUrlOnly) { | 
 |   omnibox_feature_configs::ScopedConfigForTesting< | 
 |       omnibox_feature_configs::ContextualSearch> | 
 |       contextual_scoped_config; | 
 |   contextual_scoped_config.Get().contextual_zps_limit = 3U; | 
 |   contextual_scoped_config.Get().show_open_lens_action = true; | 
 |   contextual_scoped_config.Get() | 
 |       .contextual_suggestions_ablate_others_when_present = true; | 
 |   contextual_scoped_config.Get().contextual_suggestions_ablate_url_only = true; | 
 |  | 
 |   omnibox_feature_configs::ScopedConfigForTesting< | 
 |       omnibox_feature_configs::OmniboxZpsSuggestionLimit> | 
 |       zps_scoped_config; | 
 |   zps_scoped_config.Get().enabled = true; | 
 |   zps_scoped_config.Get().max_search_suggestions = 4U; | 
 |   zps_scoped_config.Get().max_url_suggestions = 4U; | 
 |   zps_scoped_config.Get().max_suggestions = 8U; | 
 |  | 
 |   const auto group1 = omnibox::GROUP_MOST_VISITED; | 
 |   const auto group2 = omnibox::GROUP_PERSONALIZED_ZERO_SUGGEST; | 
 |   const auto group3 = omnibox::GROUP_CONTEXTUAL_SEARCH; | 
 |  | 
 |   TestData data[] = { | 
 |       {0, 1, 1000, false, {}, AutocompleteMatchType::HISTORY_URL, group1}, | 
 |       {1, 1, 900, false, {}, AutocompleteMatchType::HISTORY_URL, group1}, | 
 |       {2, 2, 800, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group2}, | 
 |       {3, 2, 700, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group2}, | 
 |       {4, 2, 600, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |       {5, 2, 500, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |   }; | 
 |  | 
 |   // Suggestion groups have the omnibox::SECTION_DEFAULT by default. | 
 |   omnibox::GroupConfigMap suggestion_groups_map; | 
 |   suggestion_groups_map[group1]; | 
 |   suggestion_groups_map[group2]; | 
 |   suggestion_groups_map[group3]; | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   for (AutocompleteMatch& m : matches) { | 
 |     if (m.suggestion_group_id && m.suggestion_group_id.value() == group3) { | 
 |       m.subtypes.insert(omnibox::SuggestSubtype::SUBTYPE_CONTEXTUAL_SEARCH); | 
 |     } | 
 |   } | 
 |  | 
 |   AutocompleteInput input(u"", metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS); | 
 |  | 
 |   AutocompleteResultForTesting result; | 
 |   result.MergeSuggestionGroupsMap(suggestion_groups_map); | 
 |   result.AppendMatches(matches); | 
 |  | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/true, | 
 |                      /*mia_enabled=*/false); | 
 |  | 
 |   // Only URL suggestions should have been ablated. | 
 |   const std::array<TestData, 4> expected_data{{ | 
 |       {2, 2, 800, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group2}, | 
 |       {3, 2, 700, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group2}, | 
 |       {4, 2, 600, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |       {5, 2, 500, false, {}, AutocompleteMatchType::SEARCH_SUGGEST, group3}, | 
 |   }}; | 
 |   AssertResultMatches(result, expected_data); | 
 | } | 
 | #endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) | 
 |  | 
 | #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) | 
 | TEST_F(AutocompleteResultTest, AttachAimAction) { | 
 |   base::test::ScopedFeatureList feature_list; | 
 |   feature_list.InitAndEnableFeature(omnibox::kOmniboxAimShortcutTypedState); | 
 |  | 
 |   TestData data[] = { | 
 |       {0, 1, 1300, true, {}, AutocompleteMatchType::SEARCH_SUGGEST}, | 
 |       {1, 1, 1200, false, {}, AutocompleteMatchType::SEARCH_SUGGEST}, | 
 |   }; | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   matches[0].contents = u"eligible for aim action"; | 
 |  | 
 |   AutocompleteInput input(u"eligible for aim action", | 
 |                           metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   ASSERT_EQ(2U, result.size()); | 
 |   EXPECT_TRUE(result.match_at(0)->actions.empty()); | 
 |   EXPECT_TRUE(result.match_at(1)->actions.empty()); | 
 |  | 
 |   FakeAutocompleteProviderClient client; | 
 |   MockAimEligibilityService* mock_aim_eligibility_service = | 
 |       static_cast<MockAimEligibilityService*>(client.GetAimEligibilityService()); | 
 |   EXPECT_CALL(*mock_aim_eligibility_service, IsServerEligibilityEnabled()) | 
 |       .WillRepeatedly(testing::Return(true)); | 
 |   EXPECT_CALL(*mock_aim_eligibility_service, IsAimLocallyEligible()) | 
 |       .WillRepeatedly(testing::Return(true)); | 
 |   EXPECT_CALL(*mock_aim_eligibility_service, IsAimEligible()) | 
 |       .WillRepeatedly(testing::Return(true)); | 
 |   result.AttachAimAction(&template_url_service(), &client); | 
 |  | 
 |   ui::DeviceFormFactor factor = ui::GetDeviceFormFactor(); | 
 |   if (factor == ui::DEVICE_FORM_FACTOR_PHONE || | 
 |       factor == ui::DEVICE_FORM_FACTOR_FOLDABLE) { | 
 |     ASSERT_EQ(1U, result.match_at(0)->actions.size()); | 
 |     const auto* action_in_suggest = OmniboxActionInSuggest::FromAction( | 
 |         result.match_at(0)->actions[0].get()); | 
 |     ASSERT_TRUE(action_in_suggest); | 
 |     EXPECT_EQ( | 
 |         action_in_suggest->template_action.action_type(), | 
 |         omnibox::SuggestTemplateInfo_TemplateAction_ActionType_CHROME_AIM); | 
 |     EXPECT_TRUE(result.match_at(1)->actions.empty()); | 
 |   } else { | 
 |     ASSERT_EQ(0U, result.match_at(0)->actions.size()); | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, AttachAimAction_AimNotEligible) { | 
 |   base::test::ScopedFeatureList feature_list; | 
 |   feature_list.InitAndEnableFeature(omnibox::kOmniboxAimShortcutTypedState); | 
 |  | 
 |   TestData data[] = { | 
 |       {0, 1, 1300, true, {}, AutocompleteMatchType::SEARCH_SUGGEST}, | 
 |       {1, 1, 1200, false, {}, AutocompleteMatchType::SEARCH_SUGGEST}, | 
 |   }; | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   matches[0].contents = u"eligible for aim action"; | 
 |  | 
 |   AutocompleteInput input(u"eligible for aim action", | 
 |                           metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   FakeAutocompleteProviderClient client; | 
 |   MockAimEligibilityService* mock_aim_eligibility_service = | 
 |       static_cast<MockAimEligibilityService*>(client.GetAimEligibilityService()); | 
 |   EXPECT_CALL(*mock_aim_eligibility_service, IsServerEligibilityEnabled()) | 
 |       .WillRepeatedly(testing::Return(true)); | 
 |   EXPECT_CALL(*mock_aim_eligibility_service, IsAimLocallyEligible()) | 
 |       .WillRepeatedly(testing::Return(true)); | 
 |   EXPECT_CALL(*mock_aim_eligibility_service, IsAimEligible()) | 
 |       .WillRepeatedly(testing::Return(false)); | 
 |   result.AttachAimAction(&template_url_service(), &client); | 
 |  | 
 |   ASSERT_EQ(0U, result.match_at(0)->actions.size()); | 
 | } | 
 |  | 
 | TEST_F(AutocompleteResultTest, AttachAimAction_AimNotLocallyEligible) { | 
 |   base::test::ScopedFeatureList feature_list; | 
 |   feature_list.InitAndEnableFeature(omnibox::kOmniboxAimShortcutTypedState); | 
 |  | 
 |   TestData data[] = { | 
 |       {0, 1, 1300, true, {}, AutocompleteMatchType::SEARCH_SUGGEST}, | 
 |       {1, 1, 1200, false, {}, AutocompleteMatchType::SEARCH_SUGGEST}, | 
 |   }; | 
 |  | 
 |   ACMatches matches; | 
 |   PopulateAutocompleteMatches(data, &matches); | 
 |   matches[0].contents = u"eligible for aim action"; | 
 |  | 
 |   AutocompleteInput input(u"eligible for aim action", | 
 |                           metrics::OmniboxEventProto::OTHER, | 
 |                           TestSchemeClassifier()); | 
 |   AutocompleteResult result; | 
 |   result.AppendMatches(matches); | 
 |   result.SortAndCull(input, &template_url_service(), | 
 |                      triggered_feature_service(), /*is_lens_active=*/false, | 
 |                      /*can_show_contextual_suggestions=*/false, | 
 |                      /*mia_enabled*/ false); | 
 |  | 
 |   FakeAutocompleteProviderClient client; | 
 |   MockAimEligibilityService* mock_aim_eligibility_service = | 
 |       static_cast<MockAimEligibilityService*>(client.GetAimEligibilityService()); | 
 |   EXPECT_CALL(*mock_aim_eligibility_service, IsServerEligibilityEnabled()) | 
 |       .WillRepeatedly(testing::Return(false)); | 
 |   EXPECT_CALL(*mock_aim_eligibility_service, IsAimLocallyEligible()) | 
 |       .WillRepeatedly(testing::Return(false)); | 
 |   EXPECT_CALL(*mock_aim_eligibility_service, IsAimEligible()) | 
 |       .WillRepeatedly(testing::Return(true)); | 
 |   result.AttachAimAction(&template_url_service(), &client); | 
 |  | 
 |   ASSERT_EQ(0U, result.match_at(0)->actions.size()); | 
 | } | 
 | #endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) | 
 |  | 
 | TEST_F(AutocompleteResultTest, AttachContextualSearchOpenLensActionToMatches) { | 
 |   AutocompleteResult result; | 
 |   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); | 
 |  | 
 |   result.AppendMatches(matches); | 
 |   result.AttachContextualSearchOpenLensActionToMatches(); | 
 |  | 
 |   ASSERT_EQ(4u, result.size()); | 
 |  | 
 |   // Match 1 should have the takeover action. | 
 |   EXPECT_TRUE(result.match_at(0)->takeover_action); | 
 |   EXPECT_EQ(result.match_at(0)->takeover_action->ActionId(), | 
 |             OmniboxActionId::CONTEXTUAL_SEARCH_OPEN_LENS); | 
 |  | 
 |   // Others should not. | 
 |   EXPECT_FALSE(result.match_at(1)->takeover_action); | 
 |   EXPECT_FALSE(result.match_at(2)->takeover_action); | 
 |   EXPECT_FALSE(result.match_at(3)->takeover_action); | 
 | } |