// 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.

#ifndef COMPONENTS_OMNIBOX_BROWSER_AUTOCOMPLETE_RESULT_H_
#define COMPONENTS_OMNIBOX_BROWSER_AUTOCOMPLETE_RESULT_H_

#include <stddef.h>

#include <map>
#include <optional>
#include <utility>
#include <vector>

#include "base/gtest_prod_util.h"
#include "base/memory/advanced_memory_safety_checks.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/omnibox/browser/actions/omnibox_action.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/match_compare.h"
#include "components/omnibox/browser/omnibox_metrics_provider.h"
#include "components/omnibox/browser/search_suggestion_parser.h"
#include "components/omnibox/browser/suggestion_group_util.h"
#include "third_party/omnibox_proto/groups.pb.h"
#include "url/gurl.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/jni_array.h"
#include "base/android/scoped_java_ref.h"
#endif

class AutocompleteInput;
class AutocompleteProvider;
class AutocompleteProviderClient;
class OmniboxTriggeredFeatureService;
class TemplateURLService;

// All matches from all providers for a particular query.  This also tracks
// what the default match should be if the user doesn't manually select another
// match.
class AutocompleteResult {
  // TODO(crbug.com/449894891): Remove this macro once it gets fixed.
  ADVANCED_MEMORY_SAFETY_CHECKS();

 public:
  typedef ACMatches::const_iterator const_iterator;
  typedef ACMatches::iterator iterator;
  using MatchDedupComparator = ACMatchKey<std::string,  // URL
                                          AutocompleteMatchDedupeType>;

  // Max number of matches we'll show from the various providers. This limit
  // may be different for zero suggest and non zero suggest. Does not take into
  // account the boost conditionally provided by the
  // omnibox::kDynamicMaxAutocomplete feature.
  static size_t GetMaxMatches(
      bool is_zero_suggest = false,
      AutocompleteInput::FeaturedKeywordMode featured_keyword_mode =
          AutocompleteInput::FeaturedKeywordMode::kFalse);
  // Defaults to GetMaxMatches if omnibox::kDynamicMaxAutocomplete is disabled;
  // otherwise returns the boosted dynamic limit.
  static size_t GetDynamicMaxMatches();

  AutocompleteResult();
  ~AutocompleteResult();
  AutocompleteResult(const AutocompleteResult&) = delete;
  AutocompleteResult& operator=(const AutocompleteResult&) = delete;

#if BUILDFLAG(IS_ANDROID)
  // Returns a corresponding Java object, creating it if necessary.
  // NOTE: Android specific methods are defined in autocomplete_match_android.cc
  base::android::ScopedJavaLocalRef<jobject> GetOrCreateJavaObject(
      JNIEnv* env,
      const TemplateURLService* template_url_service) const;

  // Notify the Java object that its native counterpart is about to be
  // destroyed.
  void DestroyJavaObject() const;

  // Construct an array of AutocompleteMatch objects arranged in the exact same
  // order as |matches_|.
  base::android::ScopedJavaLocalRef<jobjectArray> BuildJavaMatches(
      JNIEnv* env,
      const TemplateURLService* template_url_service) const;

  // Group suggestions in specified range by search vs url.
  // The range used is [first_index, last_index), which contains all the
  // elements between first_index and last_index, including the element pointed
  // by first_index, but not the element pointed by last_index.
  void GroupSuggestionsBySearchVsURL(JNIEnv* env,
                                     int first_index,
                                     int last_index);

  // Compares the set of AutocompleteMatch references held by Java with the
  // AutocompleteMatch objects held by this instance of the AutocompleteResult
  // and returns true if the two sets are same.
  // The |match_index|, when different than -1 (|kNoMatchIndex|), specifies the
  // index of a match of particular interest; this index helps identify cases
  // where an action is planned on suggestion at an index that falls outside of
  // bounds of valid AutocompleteResult indices, where every other aspect of the
  // AutocompleteResult is correct.
  bool VerifyCoherency(JNIEnv* env,
                       const base::android::JavaRef<jlongArray>& matches,
                       int32_t match_index,
                       int32_t verification_point);
#endif

  // Moves matches from |old_matches| to provide a consistent result set.
  // |old_matches| is mutated during this, and should not be used afterwards.
  void TransferOldMatches(const AutocompleteInput& input,
                          AutocompleteResult* old_matches);

  // Adds a new set of matches to the result set.  Does not re-sort.
  void AppendMatches(const ACMatches& matches);

  // Modifies |matches| such that any duplicate matches are coalesced into
  // representative "best" matches. The erased matches are moved into the
  // |duplicate_matches| members of their representative matches.
  void DeduplicateMatches(const AutocompleteInput& input,
                          TemplateURLService* template_url_service);

  // See `Sort()`. `SortAndCull()` also groups and culls the suggestions.
  // TODO(manukh): `Sort()` without grouping and culling is only needed
  //   temporarily for ML ranking without changing the search v URL balance.
  //   After we remove that restriction, they should be re-merged into 1
  //   function, because calling `Sort()` without calling `SortAndCull()`
  //   afterwards is probably invalid.
  void SortAndCull(const AutocompleteInput& input,
                   TemplateURLService* template_url_service,
                   OmniboxTriggeredFeatureService* triggered_feature_service,
                   bool is_lens_active,
                   bool can_show_contextual_suggestions,
                   bool mia_enabled,
                   bool is_incognito,
                   std::optional<AutocompleteMatch> default_match_to_preserve =
                       std::nullopt);

  // Removes duplicates, puts the list in sorted order. Sets the default match
  // to the best match and updates the alternate nav URL.
  //
  // `default_match_to_preserve` can be used to prevent the default match from
  // being surprisingly swapped out during the asynchronous pass. If it has a
  // value, this method searches the results for that match, and promotes it to
  // the top. But we don't add back that match if it doesn't already exist.
  //
  // On desktop, it filters the matches to be either all tail suggestions
  // (except for the first match) or no tail suggestions.
  //
  // TODO(manukh): `Sort()` is useful for determining the default suggestion
  //   accurately. It includes more code than absolutely necessary for
  //   determining the default suggestion to help avoid accidentally leaving the
  //   results in an invalid state. But it really shouldn't be called except if
  //   `SortAndCull()` is guaranteed to be called soon after. The 2 should be
  //   re-merged into 1 function once this is no longer needed.
  void Sort(const AutocompleteInput& input,
            TemplateURLService* template_url_service,
            std::optional<AutocompleteMatch> default_match_to_preserve);

  // Ensures that matches belonging to suggestion groups, i.e., those with a
  // suggestion_group_id value and a corresponding suggestion group info, are
  // grouped together at the bottom of result set based on the order in which
  // the groups should appear in the result set. This is done for two reasons:
  //
  // 1) Certain groups of remote zero-prefix matches need to appear under a
  // header as specified in omnibox::GroupConfig. GroupConfigs are
  // uniquely identified by the group IDs in |suggestion_groups_map_|. It is
  // also possible for zero-prefix matches to mix and match while belonging to
  // the same groups (e.g., bad server data or mixing of local and remote
  // suggestions from different providers). Hence, after mixing, deduping, and
  // sorting the matches, we group the ones with the same group ID and demote
  // them to the bottom of the result set based on a predetermined order. This
  // ensures matches without group IDs or omnibox::GroupConfig to appear at
  // the top of the result set, and two, there are no interleaving of groups or
  // headers;
  //
  // 2) Certain groups of non-zero-prefix matches, such as those produced by the
  // HistoryClusterProvider, must appear at the bottom of the result set.
  // Specifying a group ID (and a corresponding suggestion group info) for those
  // matches ensures that would happen.
  //
  // Called after matches are deduped and sorted and before they are culled.
  void GroupAndDemoteMatchesInGroups();

  // Filter and remove OmniboxActions according to Platform-specific rules.
  void TrimOmniboxActions(bool is_zero_suggest);

  // Split some `actions` on matches out to become their own matches.
  void SplitActionsToSuggestions();

  // Sets |action| in matches that have Pedal-triggering text.
  void AttachPedalsToMatches(const AutocompleteInput& input,
                             const AutocompleteProviderClient& client);

  // Sets a takeover action on all matches to issue a contextual search.
  void AttachContextualSearchFulfillmentActionToMatches();

  // Sets a takeover action on all matches to open Lens.
  void AttachContextualSearchOpenLensActionToMatches();

  // Sets |action| in matches that have associated keywords.
  void AttachSiteSearchActionToMatches(const TemplateURLService* service);

  // Sets a smart compose inline hint.
  void set_smart_compose_inline_hint(
      const std::string& smart_compose_inline_hint) {
    smart_compose_inline_hint_ = smart_compose_inline_hint;
  }

  // Sets if there are contextual chips available to show.
  void set_has_contextual_chips(bool has_contextual_chips) {
    has_contextual_chips_ = has_contextual_chips;
  }

  // Sets |has_tab_match| in matches whose URL matches an open tab's URL.
  // Also, fixes up the description if not using another UI element to
  // annotate (e.g. tab switch button). |input| can be null; if provided,
  // the match can be more precise (e.g. scheme presence).
  void ConvertOpenTabMatches(AutocompleteProviderClient* client,
                             const AutocompleteInput* input);

  // Returns true if at least one match was copied from the last result.
  bool HasCopiedMatches() const;

  // Vector-style accessors/operators.
  size_t size() const;
  bool empty() const;
  const_iterator begin() const;
  iterator begin();
  const_iterator end() const;
  iterator end();

  // Returns the match at the given index.
  const AutocompleteMatch& match_at(size_t index) const;
  AutocompleteMatch* match_at(size_t index);

  // Returns the default match if it exists, or nullptr otherwise.
  const AutocompleteMatch* default_match() const;

  // Returns the first match in |matches| which might be chosen as default.
  // If the page is not the fake box, the scores are not demoted by type.
  static ACMatches::const_iterator FindTopMatch(const AutocompleteInput& input,
                                                const ACMatches& matches);
  static ACMatches::iterator FindTopMatch(const AutocompleteInput& input,
                                          ACMatches* matches);

  // Just a helper function to encapsulate the logic of deciding how many
  // matches to keep, with respect to configured maximums, URL limits,
  // and relevancies.
  static size_t CalculateNumMatches(
      bool is_zero_suggest,
      AutocompleteInput::FeaturedKeywordMode featured_keyword_mode,
      const ACMatches& matches,
      const CompareWithDemoteByType<AutocompleteMatch>& comparing_object);
  // Determines how many matches to keep depending on how many URLs would be
  // shown. Increases the match limit if there are TYPE_UNSCOPED_EXTENSION
  // suggestions available so they don't replace other match types.
  // CalculateNumMatches defers to CalculateNumMatchesPerUrlCount if
  // all of the following are true:
  // 1) not in zero suggest.
  // 2) not in exact featured keyword mode.
  // 3) `kDynamicMaxAutocomplete` feature is enabled.
  static size_t CalculateNumMatchesPerUrlCount(
      const ACMatches& matches,
      const CompareWithDemoteByType<AutocompleteMatch>& comparing_object);

  const omnibox::GroupConfigMap& suggestion_groups_map() const {
    return suggestion_groups_map_;
  }

  const std::string smart_compose_inline_hint() const {
    return smart_compose_inline_hint_;
  }

  bool has_contextual_chips() const { return has_contextual_chips_; }

  const SessionData& session() const { return session_; }

  bool zero_prefix_enabled_in_session() const {
    return session_.zero_prefix_enabled;
  }

  void set_zero_prefix_enabled_in_session(bool enabled) {
    session_.zero_prefix_enabled = enabled;
  }

  size_t num_zero_prefix_suggestions_shown_in_session() const {
    return session_.num_zero_prefix_suggestions_shown;
  }

  void set_num_zero_prefix_suggestions_shown_in_session(size_t number) {
    session_.num_zero_prefix_suggestions_shown = number;
  }

  const std::vector<int64_t>& gws_event_id_hashes_in_session() const {
    return session_.gws_event_id_hashes;
  }

  void add_gws_event_id_hash_in_session(int64_t gws_event_id_hash) {
    session_.gws_event_id_hashes.push_back(gws_event_id_hash);
  }

  void clear_gws_event_id_hashes_in_session() {
    session_.gws_event_id_hashes.clear();
  }

  const std::vector<omnibox::metrics::ChromeSearchboxStats::ExperimentStatsV2>&
  experiment_stats_v2s_in_session() const;

  void add_experiment_stat_v2_in_session(
      const omnibox::metrics::ChromeSearchboxStats::ExperimentStatsV2& stat);

  std::pair<bool, bool> contextual_suggestions_shown_in_session() {
    return {session_.contextual_search_suggestions_shown_in_session,
            session_.lens_action_shown_in_session};
  }

  std::pair<bool, bool> suggestions_shown_in_session(bool is_zero_suggest) {
    if (is_zero_suggest) {
      return {session_.zero_prefix_search_suggestions_shown_in_session,
              session_.zero_prefix_url_suggestions_shown_in_session};
    } else {
      return {session_.typed_search_suggestions_shown_in_session,
              session_.typed_url_suggestions_shown_in_session};
    }
  }

  void set_suggestions_shown_in_session(bool is_zero_suggest,
                                        const AutocompleteMatch& match) {
    bool is_search = OmniboxMetricsProvider::GetClientSummarizedResultType(
                         match.GetOmniboxEventResultType()) ==
                     ClientSummarizedResultType::kSearch;

    if (is_zero_suggest) {
      session_.zero_prefix_suggestions_shown_in_session |= true;

      if (is_search) {
        session_.zero_prefix_search_suggestions_shown_in_session |= true;
      } else {
        session_.zero_prefix_url_suggestions_shown_in_session |= true;
      }
    } else {
      session_.typed_suggestions_shown_in_session |= true;

      if (is_search) {
        session_.typed_search_suggestions_shown_in_session |= true;
      } else {
        session_.typed_url_suggestions_shown_in_session |= true;
      }
    }

    if (match.takeover_action) {
      switch (match.takeover_action->ActionId()) {
        case OmniboxActionId::CONTEXTUAL_SEARCH_FULFILLMENT:
          session_.contextual_search_suggestions_shown_in_session |= true;
          break;
        case OmniboxActionId::CONTEXTUAL_SEARCH_OPEN_LENS:
          session_.lens_action_shown_in_session |= true;
          break;
        default:
          break;
      }
    }
  }

  // Clears this result set - i.e., `matches_` and `suggestion_groups_map_`.
  void ClearMatches();

  // Clears this result set and the session data - i.e., `matches_`,
  // `suggestion_groups_map_` and `session_`. Called when
  // AutocompleteController::Stop() with `clear_result=true` is called.
  void Reset();

#if DCHECK_IS_ON()
  // Does a data integrity check on this result.
  void Validate() const;
#endif  // DCHECK_IS_ON()

  // Returns a URL to offer the user as an alternative navigation when they
  // open |match| after typing in |input|.
  static GURL ComputeAlternateNavUrl(
      const AutocompleteInput& input,
      const AutocompleteMatch& match,
      AutocompleteProviderClient* provider_client);

  // Gets common prefix from SEARCH_SUGGEST_TAIL matches
  std::u16string GetCommonPrefix();

  // Estimates dynamic memory usage.
  // See base/trace_event/memory_usage_estimator.h for more info.
  size_t EstimateMemoryUsage() const;

  // Get a list of comparators used for deduping for the matches in this result.
  // This is only used for logging.
  std::vector<MatchDedupComparator> GetMatchDedupComparators() const;

  // Returns the header string associated with |suggestion_group_id|.
  // Returns an empty string if |suggestion_group_id| is not found in
  // |suggestion_groups_map_|.
  std::u16string GetHeaderForSuggestionGroup(
      omnibox::GroupId suggestion_group_id) const;

  // Returns the section associated with |suggestion_group_id|.
  // Returns omnibox::SECTION_DEFAULT if |suggestion_group_id| is not found in
  // |suggestion_groups_map_|.
  omnibox::GroupSection GetSectionForSuggestionGroup(
      omnibox::GroupId suggestion_group_id) const;

  // Returns the side type associated with `suggestion_group_id`.
  // Returns omnibox::DEFAULT_PRIMARY if `suggestion_group_id` is not found in
  // `suggestion_groups_map_`.
  omnibox::GroupConfig_SideType GetSideTypeForSuggestionGroup(
      omnibox::GroupId suggestion_group_id) const;

  // Returns the render type associated with `suggestion_group_id`.
  // Returns omnibox::DEFAULT_VERTICAL if `suggestion_group_id` is not found in
  // `suggestion_groups_map_`.
  omnibox::GroupConfig_RenderType GetRenderTypeForSuggestionGroup(
      omnibox::GroupId suggestion_group_id) const;

  // Updates |suggestion_groups_map_| with the suggestion groups information
  // from |suggeston_groups_map|. Followed by GroupAndDemoteMatchesInGroups()
  // which sorts the matches based on the order in which their groups should
  // appear while preserving the existing order of matches within the same
  // group.
  void MergeSuggestionGroupsMap(
      const omnibox::GroupConfigMap& suggeston_groups_map);

  // Erase matches where `predicate` returns true. In other words, this
  // preserves only those matches for which `predicate` returns false.
  template <typename UnaryPredicate>
  size_t EraseMatchesWhere(UnaryPredicate predicate) {
    return std::erase_if(matches_, predicate);
  }

  // Refreshes the ready state for this result.
  void RefreshReadyState();

  // Read-only access to `sequence_id_` for async use verification.
  uint32_t sequence_id() const { return sequence_id_; }

  // Read-only access to `result_ready_time_` for metric association.
  base::TimeTicks result_ready_time() const { return result_ready_time_; }

  // This method implements a stateful stable partition. Matches which are
  // search types, and their submatches regardless of type, are shifted
  // earlier in the range, while non-search types and their submatches
  // are shifted later. For grouping purposes, the starter pack suggestions
  // (while technically navigation suggestions) are grouped before search types.
  static void GroupSuggestionsBySearchVsURL(iterator begin, iterator end);

  // This value should be comfortably larger than any max-autocomplete-matches
  // under consideration.
  static constexpr size_t kMaxAutocompletePositionValue = 30;

 private:
  friend class AutocompleteController;
  friend class AutocompleteResultForTesting;
  friend class AutocompleteProviderTest;
  friend class AutocompleteResultTest;
  friend class HistoryURLProviderTest;
  FRIEND_TEST_ALL_PREFIXES(AutocompleteResultTest, Desktop_TwoColumnRealbox);
  FRIEND_TEST_ALL_PREFIXES(AutocompleteResultTest, Android_TrimOmniboxActions);
  FRIEND_TEST_ALL_PREFIXES(AutocompleteResultTest, SwapMatches);
  FRIEND_TEST_ALL_PREFIXES(AutocompleteResultTimeTest,
                           ResultReadyTimeSwapAndCopy);

  typedef std::map<AutocompleteProvider*, ACMatches> ProviderToMatches;

#if BUILDFLAG(IS_ANDROID)
  // iterator::difference_type is not defined in the STL that we compile with on
  // Android.
  typedef int matches_difference_type;
#else
  typedef ACMatches::iterator::difference_type matches_difference_type;
#endif

  // Swaps this result set - i.e., `matches_`, `suggestion_groups_map_`, and
  // `smart_compose_inline_hint_` - with `other`. Called in
  // `AutocompleteController` and tests only.
  void SwapMatchesWith(AutocompleteResult* other);

  // Copies the result set - i.e., `matches_` and `suggestion_groups_map_` and
  // `smart_compose_inline_hint_` - from `other`. Called in
  // `AutocompleteController` and tests only.
  void CopyMatchesFrom(const AutocompleteResult& other);

  // Modifies |matches| such that any duplicate matches are coalesced into
  // representative "best" matches. The erased matches are moved into the
  // |duplicate_matches| members of their representative matches.
  static void DeduplicateMatches(ACMatches* matches,
                                 const AutocompleteInput& input,
                                 TemplateURLService* template_url_service);

  // Returns true if |matches| contains a match with the same destination as
  // |match|.
  static bool HasMatchByDestination(const AutocompleteMatch& match,
                                    const ACMatches& matches);

  // If there are both tail and non-tail suggestions (ignoring one default
  // match), remove the tail suggestions.  If the only default matches are tail
  // suggestions, remove the non-tail suggestions.
  static void MaybeCullTailSuggestions(
      ACMatches* matches,
      const CompareWithDemoteByType<AutocompleteMatch>& comparing_object);

  // If the top match is a Search Entity, and it was deduplicated with a
  // non-entity match, splits off the non-entity match from the list of
  // duplicates and returns true. Otherwise returns false.
  // The non-entity duplicate is promoted to the top, unless the entity match
  // has Action in Suggest where it remains at the top.
  static bool UndedupeTopSearchEntityMatch(ACMatches* matches);

  // Positions inline location matches relative to their parents.
  void ArrangeInlineLocationSignalingMatch();

  // Populates |provider_to_matches| from |matches_|. This AutocompleteResult
  // should not be used after the 'move' version.
  void BuildProviderToMatchesCopy(ProviderToMatches* provider_to_matches) const;
  void BuildProviderToMatchesMove(ProviderToMatches* provider_to_matches);

  // Moves matches into this result. |old_matches| gives the matches from the
  // last result, and |new_matches| the results from this result. |old_matches|
  // should not be used afterwards.
  void MergeMatchesByProvider(ACMatches* old_matches,
                              const ACMatches& new_matches);

  // Returns a tuple encompassing all attributes relevant to determining whether
  // a match should be deduplicated with another match.
  static MatchDedupComparator GetMatchComparisonFields(
      const AutocompleteMatch& match);

  // This method reduces the number of navigation suggestions to that of
  // `max_url_matches_` but will allow more if there are no other types to
  // replace them.
  void LimitNumberOfUrlsShown(
      size_t max_matches,
      const CompareWithDemoteByType<AutocompleteMatch>& comparing_object);

  // If we have SearchProvider search suggestions, demote OnDeviceProvider
  // search suggestions, since, which in general have lower quality than
  // SearchProvider search suggestions. The demotion can happen in two ways,
  // controlled by Finch (1. decrease-relevances or 2. remove-suggestions):
  // 1. Decrease the on device search suggestion relevances that they will
  //    always be shown after SearchProvider search suggestions.
  // 2. Set the relevances of OnDeviceProvider search suggestions to 0, such
  //    that they will be removed from result list later.
  void DemoteOnDeviceSearchSuggestions();

  // The current result set. Cleared on `ClearMatches()` or `Reset()`.
  ACMatches matches_;

  // The smart compose completion, if any.
  std::string smart_compose_inline_hint_;

  // Whether or not the result can show the contextual chips (e.g. "Ask about
  // this page")
  bool has_contextual_chips_ = false;

  // The map of suggestion group IDs to suggestion group information for the
  // current result set. Cleared along with `matches_` on `ClearMatches()` or
  // `Reset()`.
  omnibox::GroupConfigMap suggestion_groups_map_;

  // The maximum number of URL matches that should be allowed within the Omnibox
  // if there are search-type matches available to replace them.
  size_t max_url_matches_ = 0;

  // The session data irrespective of the current result set. Cleared on
  // `Reset()`.
  // TODO(crbug.com/40218651): This is a bandaid solution for storing the
  // session data. It relies on `ClearMatches()`, `SwapMatchesWith()`, and
  // `CopyMatchesFrom()` to only modify the current result set and not the
  // session data; and for `Reset()` to be called once during the autocomplete
  // session. Ideally, this should be replaced with a more general solution such
  // as changing the OmniboxTriggeredFeatureService so that it defines an
  // autocomplete session to start when the omnibox is focused and to end when
  // the popup closes.
  SessionData session_;

  // A simple mechanism to measure and prevent use of stale data while
  // interoperating asynchronously with the webui omnibox popup.
  uint32_t sequence_id_;

  // The time at which this result was refreshed. This must be manually
  // refreshed by calling `RefreshReadyState()` whenever the content of this
  // result is updated.
  base::TimeTicks result_ready_time_;

#if BUILDFLAG(IS_ANDROID)
  // Corresponding Java object.
  // This object should be ignored when AutocompleteResult is copied or moved.
  // This object should never be accessed directly. To acquire a reference to
  // java object, call the GetOrCreateJavaObject().
  // Note that this object is lazily constructed to avoid creating Java matches
  // for throw away AutocompleteMatch objects, eg. during Classify() or
  // QualifyPartialUrlQuery() calls.
  // See AutocompleteControllerAndroid for more details.
  mutable base::android::ScopedJavaGlobalRef<jobject> java_result_;
#endif

  // For LOG debugging.
  friend std::ostream& operator<<(std::ostream& os,
                                  const AutocompleteResult& result);
};

#endif  // COMPONENTS_OMNIBOX_BROWSER_AUTOCOMPLETE_RESULT_H_
