blob: 381a1a08ac678d62f2c0d269811f59e0216671aa [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// 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 <vector>
#include "base/gtest_prod_util.h"
#include "build/build_config.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/match_compare.h"
#include "components/omnibox/browser/search_suggestion_parser.h"
#include "components/omnibox/browser/suggestion_group.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 PrefService;
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 {
public:
typedef ACMatches::const_iterator const_iterator;
typedef ACMatches::iterator iterator;
using MatchDedupComparator = std::pair<GURL, bool>;
// 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);
// 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;
// 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;
// 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::JavaParamRef<jlongArray>& matches,
jint match_index,
jint 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);
// Removes duplicates, puts the list in sorted order and culls to leave only
// the best GetMaxMatches() matches. Sets the default match to the best match
// and updates the alternate nav URL.
//
// |preserve_default_match| 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.
void SortAndCull(const AutocompleteInput& input,
TemplateURLService* template_url_service,
const AutocompleteMatch* preserve_default_match = nullptr);
// 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 SuggestionGroup. SuggestionGroups 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 SuggestionGroup 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();
// Sets |action| in matches that have Pedal-triggering text.
void AttachPedalsToMatches(const AutocompleteInput& input,
const AutocompleteProviderClient& client);
// 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);
// If the top match is a Search Entity, and it was deduplicated with a
// non-entity match, split off the non-entity match from the list of
// duplicates, promote it to the top, and return true.
static bool DiscourageTopMatchFromBeingSearchEntity(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,
const ACMatches& matches,
const CompareWithDemoteByType<AutocompleteMatch>& comparing_object);
// Determines how many matches to keep depending on how many URLs would be
// shown. CalculateNumMatches defers to CalculateNumMatchesPerUrlCount if the
// kDynamicMaxAutocomplete feature is enabled.
static size_t CalculateNumMatchesPerUrlCount(
const ACMatches& matches,
const CompareWithDemoteByType<AutocompleteMatch>& comparing_object);
const SuggestionGroupsMap& suggestion_groups_map() const {
return suggestion_groups_map_;
}
// Clears the matches for this result set.
void Reset();
void Swap(AutocompleteResult* other);
// operator=() by another name.
void CopyFrom(const AutocompleteResult& other);
#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();
// Populates tail_suggest_common_prefix on the matches as well as prepends
// ellipses.
void SetTailSuggestContentPrefixes();
// Populates tail_suggest_common_prefix on the matches.
void SetTailSuggestCommonPrefixes();
// 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|.
// DCHECKs whether |suggestion_group_id| is found in |suggestion_groups_map_|.
std::u16string GetHeaderForSuggestionGroup(
SuggestionGroupId suggestion_group_id) const;
// Returns whether or not |suggestion_group_id| should be collapsed in the UI.
// This method takes into account both the user's stored prefs as well as
// the server-provided visibility hint for |suggestion_group_id|.
// DCHECKs whether |suggestion_group_id| is found in |suggestion_groups_map_|
// and whether the group info contains the original server provided group ID.
bool IsSuggestionGroupHidden(PrefService* prefs,
SuggestionGroupId suggestion_group_id) const;
// Sets the UI collapsed/expanded state of the |suggestion_group_id| in the
// user's stored prefs based on the value of |hidden|.
// DCHECKs whether |suggestion_group_id| is found in |suggestion_groups_map_|
// and whether the group info contains the original server provided group ID.
void SetSuggestionGroupHidden(PrefService* prefs,
SuggestionGroupId suggestion_group_id,
bool hidden) const;
// Returns the priority associated with |suggestion_group_id|.
// DCHECKs whether |suggestion_group_id| is found in |suggestion_groups_map_|.
SuggestionGroupPriority GetPriorityForSuggestionGroup(
SuggestionGroupId 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 SuggestionGroupsMap& suggeston_groups_map);
// 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.
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_TEST_ALL_PREFIXES(AutocompleteResultTest,
DemoteOnDeviceSearchSuggestions);
FRIEND_TEST_ALL_PREFIXES(
AutocompleteResultTest,
GroupSuggestionsBySearchVsURLHonorsProtectedSuggestions);
friend class HistoryURLProviderTest;
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
// 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);
// 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);
// 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);
// This pulls the relevant fields out of a match for comparison with other
// matches for the purpose of deduping. It uses the stripped URL, so that we
// collapse similar URLs if necessary, and whether the match is a calculator
// suggestion, because we don't want to dedupe them against URLs that simply
// happen to go to the same destination.
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,
size_t max_url_count,
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();
ACMatches matches_;
// The map of suggestion group IDs to suggestion groups information.
SuggestionGroupsMap suggestion_groups_map_;
#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
};
#endif // COMPONENTS_OMNIBOX_BROWSER_AUTOCOMPLETE_RESULT_H_