blob: 07fe91dfb7409ebba8aefddae04e54458eb106d3 [file] [log] [blame]
// 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_match.h"
#include "third_party/omnibox_proto/types.pb.h"
#include <algorithm>
#include <string>
#include <string_view>
#include <vector>
#include "base/check_op.h"
#include "base/containers/fixed_flat_map.h"
#include "base/feature_list.h"
#include "base/hash/hash.h"
#include "base/i18n/case_conversion.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/trace_event/memory_usage_estimator.h"
#include "base/trace_event/trace_event.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "components/history_embeddings/history_embeddings_features.h"
#include "components/omnibox/browser/actions/omnibox_action.h"
#include "components/omnibox/browser/actions/omnibox_action_concepts.h"
#include "components/omnibox/browser/actions/omnibox_action_in_suggest.h"
#include "components/omnibox/browser/actions/omnibox_answer_action.h"
#include "components/omnibox/browser/autocomplete_match_type.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/omnibox/browser/document_provider.h"
#include "components/omnibox/browser/inline_autocompletion_util.h"
#include "components/omnibox/browser/omnibox_field_trial.h"
#include "components/omnibox/common/omnibox_feature_configs.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/search_engines/default_search_manager.h"
#include "components/search_engines/search_engine_type.h"
#include "components/search_engines/search_engine_utils.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_service.h"
#include "components/search_engines/template_url_starter_pack_data.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "third_party/omnibox_proto/answer_type.pb.h"
#include "third_party/omnibox_proto/groups.pb.h"
#include "third_party/omnibox_proto/suggest_template_info.pb.h"
#include "ui/base/device_form_factor.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/vector_icon_types.h"
#include "url/third_party/mozilla/url_parse.h"
#include "url/url_util.h"
#if (!BUILDFLAG(IS_ANDROID) || BUILDFLAG(ENABLE_VR)) && !BUILDFLAG(IS_IOS)
#include "components/omnibox/browser/suggestion_answer.h"
#include "components/omnibox/browser/vector_icons.h" // nogncheck
#include "components/vector_icons/vector_icons.h" // nogncheck
#endif
constexpr bool kIsDesktop = !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS);
constexpr bool kIsAndroid = BUILDFLAG(IS_ANDROID);
namespace {
bool IsTrivialClassification(const ACMatchClassifications& classifications) {
return classifications.empty() ||
((classifications.size() == 1) &&
(classifications.back().style == ACMatchClassification::NONE));
}
// Returns true if one of the |terms_prefixed_by_http_or_https| matches the
// beginning of the URL (sans scheme). (Recall that
// |terms_prefixed_by_http_or_https|, for the input "http://a b" will be
// ["a"].) This suggests that the user wants a particular URL with a scheme
// in mind, hence the caller should not consider another URL like this one
// but with a different scheme to be a duplicate.
bool WordMatchesURLContent(
const std::vector<std::u16string>& terms_prefixed_by_http_or_https,
const GURL& url) {
size_t prefix_length =
url.scheme().length() + strlen(url::kStandardSchemeSeparator);
DCHECK_GE(url.spec().length(), prefix_length);
const std::u16string& formatted_url = url_formatter::FormatUrl(
url, url_formatter::kFormatUrlOmitNothing, base::UnescapeRule::NORMAL,
nullptr, nullptr, &prefix_length);
if (prefix_length == std::u16string::npos)
return false;
const std::u16string& formatted_url_without_scheme =
formatted_url.substr(prefix_length);
for (const auto& term : terms_prefixed_by_http_or_https) {
if (base::StartsWith(formatted_url_without_scheme, term,
base::CompareCase::SENSITIVE))
return true;
}
return false;
}
// Gives a basis for match comparison that prefers some providers over others
// while remaining neutral with a default score of zero for most providers.
int GetDeduplicationProviderPreferenceScore(
const AutocompleteProvider* provider) {
if (!provider) {
return 0;
}
const AutocompleteProvider::Type type = provider->type();
constexpr auto kProviderPrefMap =
base::MakeFixedFlatMap<AutocompleteProvider::Type, int>({
// Prefer live remote suggestions. We check provider type instead
// of match type in order to distinguish live suggestions from the
// from their stale counterparts from the shortcut, history, and
// bookmark providers. The latter often have stale metadata such as
// last access date.
{AutocompleteProvider::TYPE_DOCUMENT, 2},
{AutocompleteProvider::TYPE_ENTERPRISE_SEARCH_AGGREGATOR, 2},
// Prefer bookmark suggestions, as:
// 1) Their titles may be explicitly set.
// 2) They may display enhanced information such as the bookmark
// folders path.
{AutocompleteProvider::TYPE_BOOKMARK, 1},
// Don't let bookmarks override builtins, as that interferes with
// starter pack matches when user has bookmarked their destination.
{AutocompleteProvider::TYPE_BUILTIN, kIsDesktop ? 1 : 0},
// Prefer non-shorcut matches over shortcuts, the latter of which may
// have stale or missing URL titles (the latter from what-you-typed
// matches).
{AutocompleteProvider::TYPE_SHORTCUTS, -1},
// Prefer non-fuzzy matches over fuzzy matches.
{AutocompleteProvider::TYPE_HISTORY_FUZZY, -2},
});
const auto it = kProviderPrefMap.find(type);
return it != kProviderPrefMap.end() ? it->second : 0;
}
} // namespace
template <typename... Args>
size_t ACMatchKeyHash<Args...>::operator()(
const ACMatchKey<Args...>& key) const {
size_t seed = 0;
// Compute a hash by applying `HashCombine` to each element of the "key"
// tuple.
std::apply(
[&seed](auto&&... args) { ((base::HashCombine(seed, args)), ...); }, key);
return seed;
}
// This trick allows implementing `ACMatchKeyHash` in the implementation file.
// Every unique specialization of `ACMatchKey` should have a corresponding
// declaration here.
// Deduplication key hash for `AutocompleteResult`.
template struct ACMatchKeyHash<std::string, // URL
AutocompleteMatchDedupeType>;
// RichAutocompletionParams ---------------------------------------------------
RichAutocompletionParams::RichAutocompletionParams()
: enabled(OmniboxFieldTrial::IsRichAutocompletionEnabled()),
autocomplete_titles_min_char(
OmniboxFieldTrial::kRichAutocompletionAutocompleteTitlesMinChar
.Get()),
autocomplete_shortcut_text_min_char(
OmniboxFieldTrial::kRichAutocompletionAutocompleteShortcutTextMinChar
.Get()) {}
RichAutocompletionParams& RichAutocompletionParams::GetParams() {
static RichAutocompletionParams params;
return params;
}
void RichAutocompletionParams::ClearParamsForTesting() {
GetParams() = {};
}
// SessionData ----------------------------------------------------------------
SessionData::SessionData() = default;
SessionData::SessionData(const SessionData& session_data) = default;
SessionData::~SessionData() = default;
SessionData& SessionData::operator=(const SessionData& match) = default;
// AutocompleteMatch ----------------------------------------------------------
// static
const char* AutocompleteMatch::DocumentTypeString(DocumentType type) {
return kDocumentTypeStrings[static_cast<int>(type)];
}
// static
bool AutocompleteMatch::DocumentTypeFromInteger(int value,
DocumentType* result) {
DCHECK(result);
// The resulting value may still be invalid after the static_cast.
DocumentType document_type = static_cast<DocumentType>(value);
if (document_type >= DocumentType::NONE &&
document_type < DocumentType::DOCUMENT_TYPE_SIZE) {
*result = document_type;
return true;
}
return false;
}
// static
const char16_t AutocompleteMatch::kInvalidChars[] = {
'\n', '\r', '\t',
0x2028, // Line separator
0x2029, // Paragraph separator
0};
// static
const char16_t AutocompleteMatch::kEllipsis[] = u"... ";
AutocompleteMatch::AutocompleteMatch()
: transition(ui::PAGE_TRANSITION_GENERATED) {}
AutocompleteMatch::AutocompleteMatch(AutocompleteProvider* provider,
int relevance,
bool deletable,
Type type)
: provider(provider),
relevance(relevance),
deletable(deletable),
type(type) {}
AutocompleteMatch::AutocompleteMatch(const AutocompleteMatch& match)
: provider(match.provider),
relevance(match.relevance),
typed_count(match.typed_count),
deletable(match.deletable),
fill_into_edit(match.fill_into_edit),
additional_text(match.additional_text),
inline_autocompletion(match.inline_autocompletion),
rich_autocompletion_triggered(match.rich_autocompletion_triggered),
allowed_to_be_default_match(match.allowed_to_be_default_match),
destination_url(match.destination_url),
stripped_destination_url(match.stripped_destination_url),
extra_headers(match.extra_headers),
image_dominant_color(match.image_dominant_color),
image_url(match.image_url),
icon_url(match.icon_url),
entity_id(match.entity_id),
website_uri(match.website_uri),
document_type(match.document_type),
enterprise_search_aggregator_type(
match.enterprise_search_aggregator_type),
tail_suggest_common_prefix(match.tail_suggest_common_prefix),
contents(match.contents),
contents_class(match.contents_class),
description(match.description),
description_class(match.description_class),
description_for_shortcuts(match.description_for_shortcuts),
description_class_for_shortcuts(match.description_class_for_shortcuts),
suggestion_group_id(match.suggestion_group_id),
swap_contents_and_description(match.swap_contents_and_description),
answer_template(match.answer_template),
suggest_template(match.suggest_template),
answer_type(match.answer_type),
transition(match.transition),
type(match.type),
suggest_type(match.suggest_type),
subtypes(match.subtypes),
has_tab_match(match.has_tab_match),
associated_keyword(match.associated_keyword
? new AutocompleteMatch(*match.associated_keyword)
: nullptr),
keyword(match.keyword),
from_keyword(match.from_keyword),
actions(match.actions),
takeover_action(match.takeover_action),
from_previous(match.from_previous),
session(match.session),
search_terms_args(
match.search_terms_args
? new TemplateURLRef::SearchTermsArgs(*match.search_terms_args)
: nullptr),
post_content(match.post_content
? new TemplateURLRef::PostContent(*match.post_content)
: nullptr),
additional_info(match.additional_info),
duplicate_matches(match.duplicate_matches),
suggest_tiles(match.suggest_tiles),
scoring_signals(match.scoring_signals),
culled_by_provider(match.culled_by_provider),
shortcut_boosted(match.shortcut_boosted),
iph_type(match.iph_type),
iph_link_text(match.iph_link_text),
iph_link_url(match.iph_link_url),
history_embeddings_answer_header_text(
match.history_embeddings_answer_header_text),
history_embeddings_answer_header_loading(
match.history_embeddings_answer_header_loading),
feedback_type(match.feedback_type),
matching_tab_group_uuid(match.matching_tab_group_uuid) {}
AutocompleteMatch::AutocompleteMatch(AutocompleteMatch&& match) noexcept {
*this = std::move(match);
}
AutocompleteMatch& AutocompleteMatch::operator=(
AutocompleteMatch&& match) noexcept {
provider = std::move(match.provider);
relevance = std::move(match.relevance);
typed_count = std::move(match.typed_count);
deletable = std::move(match.deletable);
fill_into_edit = std::move(match.fill_into_edit);
additional_text = std::move(match.additional_text);
inline_autocompletion = std::move(match.inline_autocompletion);
rich_autocompletion_triggered =
std::move(match.rich_autocompletion_triggered);
allowed_to_be_default_match = std::move(match.allowed_to_be_default_match);
destination_url = std::move(match.destination_url);
stripped_destination_url = std::move(match.stripped_destination_url);
extra_headers = std::move(match.extra_headers);
image_dominant_color = std::move(match.image_dominant_color);
image_url = std::move(match.image_url);
icon_url = std::move(match.icon_url);
entity_id = std::move(match.entity_id);
website_uri = std::move(match.website_uri);
document_type = std::move(match.document_type);
enterprise_search_aggregator_type =
std::move(match.enterprise_search_aggregator_type);
tail_suggest_common_prefix = std::move(match.tail_suggest_common_prefix);
contents = std::move(match.contents);
contents_class = std::move(match.contents_class);
description = std::move(match.description);
description_class = std::move(match.description_class);
description_for_shortcuts = std::move(match.description_for_shortcuts);
description_class_for_shortcuts =
std::move(match.description_class_for_shortcuts);
suggestion_group_id = std::move(match.suggestion_group_id);
swap_contents_and_description =
std::move(match.swap_contents_and_description);
answer_template = std::move(match.answer_template);
suggest_template = std::move(match.suggest_template);
answer_type = std::move(match.answer_type);
transition = std::move(match.transition);
type = std::move(match.type);
suggest_type = std::move(match.suggest_type);
subtypes = std::move(match.subtypes);
has_tab_match = std::move(match.has_tab_match);
associated_keyword = std::move(match.associated_keyword);
keyword = std::move(match.keyword);
from_keyword = std::move(match.from_keyword);
actions = std::move(match.actions);
takeover_action = std::move(match.takeover_action);
from_previous = std::move(match.from_previous);
session = std::move(match.session);
search_terms_args = std::move(match.search_terms_args);
post_content = std::move(match.post_content);
additional_info = std::move(match.additional_info);
duplicate_matches = std::move(match.duplicate_matches);
suggest_tiles = std::move(match.suggest_tiles);
scoring_signals = std::move(match.scoring_signals);
culled_by_provider = std::move(match.culled_by_provider);
shortcut_boosted = std::move(match.shortcut_boosted);
iph_type = std::move(match.iph_type);
iph_link_text = std::move(match.iph_link_text);
iph_link_url = std::move(match.iph_link_url);
history_embeddings_answer_header_text =
std::move(match.history_embeddings_answer_header_text);
history_embeddings_answer_header_loading =
std::move(match.history_embeddings_answer_header_loading);
feedback_type = std::move(match.feedback_type);
matching_tab_group_uuid = std::move(match.matching_tab_group_uuid);
#if BUILDFLAG(IS_ANDROID)
DestroyJavaObject();
std::swap(java_match_, match.java_match_);
std::swap(matching_java_tab_, match.matching_java_tab_);
UpdateJavaObjectNativeRef();
#endif
return *this;
}
AutocompleteMatch::~AutocompleteMatch() {
#if BUILDFLAG(IS_ANDROID)
DestroyJavaObject();
#endif
}
AutocompleteMatch& AutocompleteMatch::operator=(
const AutocompleteMatch& match) {
if (this == &match)
return *this;
provider = match.provider;
relevance = match.relevance;
typed_count = match.typed_count;
deletable = match.deletable;
fill_into_edit = match.fill_into_edit;
additional_text = match.additional_text;
inline_autocompletion = match.inline_autocompletion;
rich_autocompletion_triggered = match.rich_autocompletion_triggered;
allowed_to_be_default_match = match.allowed_to_be_default_match;
destination_url = match.destination_url;
stripped_destination_url = match.stripped_destination_url;
extra_headers = match.extra_headers;
image_dominant_color = match.image_dominant_color;
image_url = match.image_url;
icon_url = match.icon_url;
entity_id = match.entity_id;
website_uri = match.website_uri;
document_type = match.document_type;
enterprise_search_aggregator_type = match.enterprise_search_aggregator_type;
tail_suggest_common_prefix = match.tail_suggest_common_prefix;
contents = match.contents;
contents_class = match.contents_class;
description = match.description;
description_class = match.description_class;
description_for_shortcuts = match.description_for_shortcuts;
description_class_for_shortcuts = match.description_class_for_shortcuts;
suggestion_group_id = match.suggestion_group_id;
swap_contents_and_description = match.swap_contents_and_description;
answer_template = match.answer_template;
suggest_template = match.suggest_template;
answer_type = match.answer_type;
transition = match.transition;
type = match.type;
suggest_type = match.suggest_type;
subtypes = match.subtypes;
has_tab_match = match.has_tab_match;
associated_keyword.reset(
match.associated_keyword
? new AutocompleteMatch(*match.associated_keyword)
: nullptr);
keyword = match.keyword;
from_keyword = match.from_keyword;
actions = match.actions;
takeover_action = match.takeover_action;
from_previous = match.from_previous;
session = match.session;
search_terms_args.reset(
match.search_terms_args
? new TemplateURLRef::SearchTermsArgs(*match.search_terms_args)
: nullptr);
post_content.reset(match.post_content
? new TemplateURLRef::PostContent(*match.post_content)
: nullptr);
additional_info = match.additional_info;
duplicate_matches = match.duplicate_matches;
suggest_tiles = match.suggest_tiles;
scoring_signals = match.scoring_signals;
culled_by_provider = match.culled_by_provider;
shortcut_boosted = match.shortcut_boosted;
iph_type = match.iph_type;
iph_link_text = match.iph_link_text;
iph_link_url = match.iph_link_url;
history_embeddings_answer_header_text =
match.history_embeddings_answer_header_text;
history_embeddings_answer_header_loading =
match.history_embeddings_answer_header_loading;
feedback_type = match.feedback_type;
matching_tab_group_uuid = match.matching_tab_group_uuid;
#if BUILDFLAG(IS_ANDROID)
// In case the target element previously held a java object, release it.
// This happens, when in an expression "match1 = match2;" match1 already
// is initialized and linked to a Java object: we rewrite the contents of the
// match1 object and it would be desired to either update its corresponding
// Java element, or drop it and construct it lazily the next time it is
// needed.
// Note that because Java<>C++ AutocompleteMatch relation is 1:1, we do not
// want to copy the object here.
DestroyJavaObject();
#endif
return *this;
}
#if (!BUILDFLAG(IS_ANDROID) || BUILDFLAG(ENABLE_VR)) && !BUILDFLAG(IS_IOS)
// static
const gfx::VectorIcon& AutocompleteMatch::AnswerTypeToAnswerIcon(
omnibox::AnswerType type) {
switch (type) {
case omnibox::ANSWER_TYPE_CURRENCY:
return omnibox::kAnswerCurrencyChromeRefreshIcon;
case omnibox::ANSWER_TYPE_DICTIONARY:
return omnibox::kAnswerDictionaryChromeRefreshIcon;
case omnibox::ANSWER_TYPE_FINANCE:
return omnibox::kAnswerFinanceChromeRefreshIcon;
case omnibox::ANSWER_TYPE_SUNRISE_SUNSET:
return omnibox::kAnswerSunriseChromeRefreshIcon;
case omnibox::ANSWER_TYPE_TRANSLATION:
return omnibox::kAnswerTranslationChromeRefreshIcon;
default:
return omnibox::kAnswerDefaultIcon;
}
}
const gfx::VectorIcon& AutocompleteMatch::GetVectorIcon(
bool is_bookmark,
const TemplateURL* turl) const {
if (suggest_template.has_value() && suggest_template->has_type_icon()) {
// Update this assertion and the switch below whenever values are added.
static_assert(omnibox::SuggestTemplateInfo::IconType_MAX ==
omnibox::SuggestTemplateInfo::SUB_ARROW_RIGHT);
switch (suggest_template->type_icon()) {
case omnibox::SuggestTemplateInfo::ICON_TYPE_UNSPECIFIED:
// When not specified, fall back on regular match icon logic below.
break;
case omnibox::SuggestTemplateInfo::HISTORY:
return vector_icons::kHistoryChromeRefreshIcon;
case omnibox::SuggestTemplateInfo::SEARCH_LOOP:
return vector_icons::kSearchChromeRefreshIcon;
case omnibox::SuggestTemplateInfo::SEARCH_LOOP_WITH_SPARKLE:
return omnibox::kSearchSparkIcon;
case omnibox::SuggestTemplateInfo::TRENDING:
return omnibox::kTrendingUpChromeRefreshIcon;
case omnibox::SuggestTemplateInfo::SUB_ARROW_RIGHT:
return omnibox::kSubdirectoryArrowRightIcon;
default:
// Out of range value defaults to search loupe.
return vector_icons::kSearchChromeRefreshIcon;
}
}
// Some match types should retain their traditional icon even when bookmarked.
if (is_bookmark && type != Type::HISTORY_EMBEDDINGS_ANSWER &&
type != Type::STARTER_PACK) {
return omnibox::kBookmarkChromeRefreshIcon;
}
if (answer_type != omnibox::ANSWER_TYPE_UNSPECIFIED) {
return AnswerTypeToAnswerIcon(answer_type);
}
switch (type) {
case Type::URL_WHAT_YOU_TYPED:
case Type::HISTORY_URL:
case Type::HISTORY_TITLE:
case Type::HISTORY_BODY:
case Type::HISTORY_KEYWORD:
case Type::NAVSUGGEST:
case Type::BOOKMARK_TITLE:
case Type::NAVSUGGEST_PERSONALIZED:
case Type::CLIPBOARD_URL:
case Type::PHYSICAL_WEB_DEPRECATED:
case Type::PHYSICAL_WEB_OVERFLOW_DEPRECATED:
case Type::TAB_SEARCH_DEPRECATED:
case Type::TILE_NAVSUGGEST:
case Type::TILE_MOST_VISITED_SITE:
case Type::OPEN_TAB:
case Type::HISTORY_EMBEDDINGS:
return omnibox::kPageChromeRefreshIcon;
case Type::SEARCH_SUGGEST:
return IsTrendSuggestion() ? omnibox::kTrendingUpChromeRefreshIcon
: (IsContextualSearchSuggestion() &&
omnibox_feature_configs::ContextualSearch::Get()
.contextual_zero_suggest_lens_fulfillment)
? omnibox::kSubdirectoryArrowRightIcon
: vector_icons::kSearchChromeRefreshIcon;
case Type::PEDAL:
return takeover_action ? takeover_action->GetVectorIcon()
: vector_icons::kSearchChromeRefreshIcon;
case Type::SEARCH_OTHER_ENGINE:
if (provider != nullptr &&
provider->type() == AutocompleteProvider::TYPE_UNSCOPED_EXTENSION) {
return omnibox::kExtensionAppIcon;
}
return vector_icons::kSearchChromeRefreshIcon;
case Type::SEARCH_WHAT_YOU_TYPED:
case Type::SEARCH_SUGGEST_ENTITY:
case Type::SEARCH_SUGGEST_PROFILE:
case Type::CONTACT_DEPRECATED:
case Type::VOICE_SUGGEST:
case Type::CLIPBOARD_TEXT:
case Type::CLIPBOARD_IMAGE:
case Type::TILE_SUGGESTION:
case Type::TILE_REPEATABLE_QUERY:
return vector_icons::kSearchChromeRefreshIcon;
case Type::SEARCH_HISTORY:
case Type::SEARCH_SUGGEST_PERSONALIZED:
DCHECK(IsSearchHistoryType(type));
return vector_icons::kHistoryChromeRefreshIcon;
case Type::EXTENSION_APP_DEPRECATED:
return omnibox::kExtensionAppIcon;
case Type::CALCULATOR:
return omnibox::kCalculatorChromeRefreshIcon;
case Type::NULL_RESULT_MESSAGE:
// Select the icon according to the type of IPH. Otherwise (for No Results
// Found), fallthrough to use the empty icon.
switch (iph_type) {
case IphType::kNone:
return gfx::VectorIcon::EmptyIcon();
case IphType::kGemini:
return omnibox::kSparkIcon;
case IphType::kFeaturedEnterpriseSiteSearch:
case IphType::kEnterpriseSearchAggregator:
return omnibox::kEnterpriseIcon;
case IphType::kHistoryEmbeddingsSettingsPromo:
return omnibox::kSparkIcon;
case IphType::kHistoryEmbeddingsDisclaimer:
return gfx::VectorIcon::EmptyIcon();
case IphType::kHistoryScopePromo:
return vector_icons::kHistoryChromeRefreshIcon;
case IphType::kHistoryEmbeddingsScopePromo:
return omnibox::kSparkIcon;
}
case Type::SEARCH_SUGGEST_TAIL:
case Type::HISTORY_EMBEDDINGS_ANSWER:
return gfx::VectorIcon::EmptyIcon();
case Type::DOCUMENT_SUGGESTION:
switch (document_type) {
case DocumentType::DRIVE_DOCS:
return omnibox::kDriveDocsIcon;
case DocumentType::DRIVE_FORMS:
return omnibox::kDriveFormsIcon;
case DocumentType::DRIVE_SHEETS:
return omnibox::kDriveSheetsIcon;
case DocumentType::DRIVE_SLIDES:
return omnibox::kDriveSlidesIcon;
case DocumentType::DRIVE_IMAGE:
return omnibox::kDriveImageIcon;
case DocumentType::DRIVE_PDF:
return omnibox::kDrivePdfIcon;
case DocumentType::DRIVE_VIDEO:
return omnibox::kDriveVideoIcon;
case DocumentType::DRIVE_FOLDER:
return omnibox::kDriveFolderIcon;
case DocumentType::DRIVE_OTHER:
return omnibox::kDriveLogoIcon;
default:
return omnibox::kPageChromeRefreshIcon;
}
case Type::HISTORY_CLUSTER:
return omnibox::kJourneysChromeRefreshIcon;
case Type::STARTER_PACK:
if (turl) {
switch (turl->GetBuiltinEngineType()) {
case KEYWORD_MODE_STARTER_PACK_BOOKMARKS:
return omnibox::kStarActiveChromeRefreshIcon;
case KEYWORD_MODE_STARTER_PACK_HISTORY:
return vector_icons::kHistoryChromeRefreshIcon;
case KEYWORD_MODE_STARTER_PACK_TABS:
return omnibox::kProductChromeRefreshIcon;
case KEYWORD_MODE_STARTER_PACK_GEMINI:
return omnibox::kSparkIcon;
case KEYWORD_MODE_STARTER_PACK_AI_MODE:
return omnibox::kSearchSparkIcon;
default:
break;
}
}
return omnibox::kProductChromeRefreshIcon;
case Type::FEATURED_ENTERPRISE_SEARCH:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
if (turl && turl->CreatedByEnterpriseSearchAggregatorPolicy()) {
return vector_icons::kGoogleAgentspaceMonochromeLogoIcon;
}
#endif
return omnibox::kPageChromeRefreshIcon;
case Type::NUM_TYPES:
NOTREACHED() << "Unexpected AutocompleteMatchType value: "
<< static_cast<int>(type);
default:
return omnibox::kPageChromeRefreshIcon;
}
}
#endif
// static
bool AutocompleteMatch::MoreRelevant(const AutocompleteMatch& match1,
const AutocompleteMatch& match2) {
// For equal-relevance matches, we sort alphabetically, so that providers
// who return multiple elements at the same priority get a "stable" sort
// across multiple updates.
return match1.relevance == match2.relevance
? match1.contents < match2.contents
: match1.relevance > match2.relevance;
}
// static
bool AutocompleteMatch::BetterDuplicate(const AutocompleteMatch& match1,
const AutocompleteMatch& match2) {
if (kIsDesktop) {
// Prefer featured Enterprise site search matches.
if (match1.type == AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH &&
match2.type != AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH) {
return true;
}
if (match1.type != AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH &&
match2.type == AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH) {
return false;
}
// Prefer starter pack matches.
if (match1.type == AutocompleteMatchType::STARTER_PACK &&
match2.type != AutocompleteMatchType::STARTER_PACK) {
return true;
}
if (match1.type != AutocompleteMatchType::STARTER_PACK &&
match2.type == AutocompleteMatchType::STARTER_PACK) {
return false;
}
}
// Prefer the Entity Match over the non-entity match, if they have the same
// |fill_into_edit| value.
if (match1.type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY &&
match2.type != AutocompleteMatchType::SEARCH_SUGGEST_ENTITY &&
match1.fill_into_edit == match2.fill_into_edit) {
return true;
}
if (match1.type != AutocompleteMatchType::SEARCH_SUGGEST_ENTITY &&
match2.type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY &&
match1.fill_into_edit == match2.fill_into_edit) {
return false;
}
// Prefer open tab matches over other types of matches.
if (match1.type == AutocompleteMatchType::OPEN_TAB &&
match2.type != AutocompleteMatchType::OPEN_TAB) {
return true;
}
if (match1.type != AutocompleteMatchType::OPEN_TAB &&
match2.type == AutocompleteMatchType::OPEN_TAB) {
return false;
}
// Prefer matches allowed to be the default match.
if (match1.allowed_to_be_default_match && !match2.allowed_to_be_default_match)
return true;
if (!match1.allowed_to_be_default_match && match2.allowed_to_be_default_match)
return false;
// Prefer some providers above others according to score (default is zero).
const int match1_score =
GetDeduplicationProviderPreferenceScore(match1.provider);
const int match2_score =
GetDeduplicationProviderPreferenceScore(match2.provider);
if (match1_score != match2_score) {
return match1_score > match2_score;
}
// By default, simply prefer the more relevant match.
return MoreRelevant(match1, match2);
}
// static
bool AutocompleteMatch::BetterDuplicateByIterator(
const std::vector<AutocompleteMatch>::const_iterator it1,
const std::vector<AutocompleteMatch>::const_iterator it2) {
return BetterDuplicate(*it1, *it2);
}
// static
AutocompleteMatch::ACMatchClassifications
AutocompleteMatch::MergeClassifications(
const ACMatchClassifications& classifications1,
const ACMatchClassifications& classifications2) {
// We must return the empty vector only if both inputs are truly empty.
// The result of merging an empty vector with a single (0, NONE)
// classification is the latter one-entry vector.
if (IsTrivialClassification(classifications1))
return classifications2.empty() ? classifications1 : classifications2;
if (IsTrivialClassification(classifications2))
return classifications1;
ACMatchClassifications output;
for (auto i = classifications1.begin(), j = classifications2.begin();
i != classifications1.end();) {
AutocompleteMatch::AddLastClassificationIfNecessary(
&output, std::max(i->offset, j->offset), i->style | j->style);
const size_t next_i_offset = (i + 1) == classifications1.end()
? static_cast<size_t>(-1)
: (i + 1)->offset;
const size_t next_j_offset = (j + 1) == classifications2.end()
? static_cast<size_t>(-1)
: (j + 1)->offset;
if (next_i_offset >= next_j_offset)
++j;
if (next_j_offset >= next_i_offset)
++i;
}
return output;
}
// static
std::string AutocompleteMatch::ClassificationsToString(
const ACMatchClassifications& classifications) {
std::string serialized_classifications;
for (size_t i = 0; i < classifications.size(); ++i) {
if (i)
serialized_classifications += ',';
serialized_classifications +=
base::NumberToString(classifications[i].offset) + ',' +
base::NumberToString(classifications[i].style);
}
return serialized_classifications;
}
// static
ACMatchClassifications AutocompleteMatch::ClassificationsFromString(
const std::string& serialized_classifications) {
ACMatchClassifications classifications;
std::vector<std::string_view> tokens =
base::SplitStringPiece(serialized_classifications, ",",
base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
DCHECK(!(tokens.size() & 1)); // The number of tokens should be even.
for (size_t i = 0; i < tokens.size(); i += 2) {
int classification_offset = 0;
int classification_style = ACMatchClassification::NONE;
if (!base::StringToInt(tokens[i], &classification_offset) ||
!base::StringToInt(tokens[i + 1], &classification_style)) {
NOTREACHED();
}
classifications.push_back(
ACMatchClassification(classification_offset, classification_style));
}
return classifications;
}
// static
void AutocompleteMatch::AddLastClassificationIfNecessary(
ACMatchClassifications* classifications,
size_t offset,
int style) {
DCHECK(classifications);
if (classifications->empty() || classifications->back().style != style) {
DCHECK(classifications->empty() ||
(offset > classifications->back().offset));
classifications->push_back(ACMatchClassification(offset, style));
}
}
// static
std::u16string AutocompleteMatch::SanitizeString(const std::u16string& text) {
// NOTE: This logic is mirrored by |sanitizeString()| in
// omnibox_custom_bindings.js.
std::u16string result;
base::TrimWhitespace(text, base::TRIM_LEADING, &result);
base::RemoveChars(result, kInvalidChars, &result);
return result;
}
// static
bool AutocompleteMatch::IsFeaturedEnterpriseSearchType(Type type) {
return type == AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH;
}
// static
bool AutocompleteMatch::IsFeaturedSearchType(Type type) {
return IsStarterPackType(type) || IsFeaturedEnterpriseSearchType(type);
}
// static
bool AutocompleteMatch::IsPreconnectableType(Type type) {
CHECK(base::FeatureList::IsEnabled(
omnibox::kPreconnectNonSearchOmniboxSuggestions));
return IsSearchType(type) ||
type == AutocompleteMatchType::URL_WHAT_YOU_TYPED ||
type == AutocompleteMatchType::HISTORY_URL ||
type == AutocompleteMatchType::HISTORY_TITLE ||
type == AutocompleteMatchType::HISTORY_BODY ||
type == AutocompleteMatchType::HISTORY_KEYWORD ||
type == AutocompleteMatchType::NAVSUGGEST ||
type == AutocompleteMatchType::BOOKMARK_TITLE ||
type == AutocompleteMatchType::NAVSUGGEST_PERSONALIZED ||
type == AutocompleteMatchType::CLIPBOARD_URL ||
type == AutocompleteMatchType::DOCUMENT_SUGGESTION ||
type == AutocompleteMatchType::TILE_NAVSUGGEST ||
type == AutocompleteMatchType::OPEN_TAB ||
type == AutocompleteMatchType::TILE_MOST_VISITED_SITE ||
type == AutocompleteMatchType::HISTORY_EMBEDDINGS;
}
// static
bool AutocompleteMatch::IsSearchType(Type type) {
return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
type == AutocompleteMatchType::SEARCH_HISTORY ||
type == AutocompleteMatchType::SEARCH_SUGGEST ||
type == AutocompleteMatchType::SEARCH_OTHER_ENGINE ||
type == AutocompleteMatchType::CALCULATOR ||
type == AutocompleteMatchType::VOICE_SUGGEST ||
type == AutocompleteMatchType::CLIPBOARD_TEXT ||
type == AutocompleteMatchType::CLIPBOARD_IMAGE ||
IsSpecializedSearchType(type);
}
// static
bool AutocompleteMatch::IsSpecializedSearchType(Type type) {
return type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY ||
type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL ||
type == AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED ||
type == AutocompleteMatchType::TILE_SUGGESTION ||
type == AutocompleteMatchType::TILE_REPEATABLE_QUERY ||
type == AutocompleteMatchType::SEARCH_SUGGEST_PROFILE;
}
// static
bool AutocompleteMatch::IsSearchHistoryType(Type type) {
return type == AutocompleteMatchType::SEARCH_HISTORY ||
type == AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED;
}
// static
bool AutocompleteMatch::IsStarterPackType(Type type) {
return type == AutocompleteMatchType::STARTER_PACK;
}
// static
bool AutocompleteMatch::IsClipboardType(Type type) {
return type == AutocompleteMatchType::CLIPBOARD_URL ||
type == AutocompleteMatchType::CLIPBOARD_TEXT ||
type == AutocompleteMatchType::CLIPBOARD_IMAGE;
}
// static
bool AutocompleteMatch::ShouldBeSkippedForGroupBySearchVsUrl(Type type) {
return IsClipboardType(type) ||
type == AutocompleteMatchType::TILE_NAVSUGGEST ||
type == AutocompleteMatchType::TILE_MOST_VISITED_SITE ||
type == AutocompleteMatchType::TILE_REPEATABLE_QUERY ||
type == AutocompleteMatchType::TILE_SUGGESTION;
}
// static
omnibox::GroupId AutocompleteMatch::GetDefaultGroupId(Type type) {
if (type == AutocompleteMatchType::TILE_NAVSUGGEST ||
type == AutocompleteMatchType::TILE_SUGGESTION ||
type == AutocompleteMatchType::TILE_MOST_VISITED_SITE ||
type == AutocompleteMatchType::TILE_REPEATABLE_QUERY) {
return omnibox::GROUP_MOBILE_MOST_VISITED;
}
if (type == AutocompleteMatchType::CLIPBOARD_URL ||
type == AutocompleteMatchType::CLIPBOARD_TEXT ||
type == AutocompleteMatchType::CLIPBOARD_IMAGE) {
return omnibox::GROUP_MOBILE_CLIPBOARD;
}
// TODO(b/309458788): Create a separate group for Featured Enterprise search.
// In the meantime, reusing the same group as starter pack
// is OK, since they are shown together anyway.
if (IsStarterPackType(type) || IsFeaturedEnterpriseSearchType(type)) {
return omnibox::GROUP_STARTER_PACK;
}
if (IsSearchType(type))
return omnibox::GROUP_SEARCH;
if (type == AutocompleteMatchType::HISTORY_CLUSTER)
return omnibox::GROUP_HISTORY_CLUSTER;
return omnibox::GROUP_OTHER_NAVS;
}
// static
TemplateURL* AutocompleteMatch::GetTemplateURLWithKeyword(
TemplateURLService* template_url_service,
const std::u16string& keyword,
const std::string& host) {
return const_cast<TemplateURL*>(GetTemplateURLWithKeyword(
static_cast<const TemplateURLService*>(template_url_service), keyword,
host));
}
// static
const TemplateURL* AutocompleteMatch::GetTemplateURLWithKeyword(
const TemplateURLService* template_url_service,
const std::u16string& keyword,
const std::string& host) {
if (template_url_service == nullptr)
return nullptr;
const TemplateURL* template_url =
keyword.empty() ? nullptr
: template_url_service->GetTemplateURLForKeyword(keyword);
return (template_url || host.empty())
? template_url
: template_url_service->GetTemplateURLForHost(host);
}
// static
GURL AutocompleteMatch::GURLToStrippedGURL(
const GURL& url,
const AutocompleteInput& input,
const TemplateURLService* template_url_service,
const std::u16string& keyword,
const bool keep_search_intent_params) {
if (!url.is_valid())
return url;
// Special-case canonicalization Docs URLs. This logic is self-contained and
// will not participate in the TemplateURL canonicalization.
GURL docs_url = DocumentProvider::GetURLForDeduping(url);
if (docs_url.is_valid())
return docs_url;
GURL stripped_destination_url = url;
// If the destination URL looks like it was generated from a TemplateURL,
// remove all substitutions other than the search terms and optionally the
// search intent params. This allows eliminating cases like past search URLs
// from history that differ only by some obscure query param from each other
// or from the search/keyword provider matches.
const TemplateURL* template_url = GetTemplateURLWithKeyword(
template_url_service, keyword, stripped_destination_url.host());
if (template_url && template_url->SupportsReplacement(
template_url_service->search_terms_data())) {
using CacheKey = std::tuple<const TemplateURL*, GURL, bool>;
static base::NoDestructor<base::LRUCache<CacheKey, GURL>> template_cache(
30);
const CacheKey cache_key = {template_url, url, keep_search_intent_params};
const auto& cached = template_cache->Get(cache_key);
if (cached != template_cache->end()) {
stripped_destination_url = cached->second;
} else if (template_url->KeepSearchTermsInURL(
url, template_url_service->search_terms_data(),
keep_search_intent_params,
/*normalize_search_terms=*/false,
&stripped_destination_url)) {
template_cache->Put(cache_key, stripped_destination_url);
}
}
// |replacements| keeps all the substitutions we're going to make to
// from {destination_url} to {stripped_destination_url}. |need_replacement|
// is a helper variable that helps us keep track of whether we need
// to apply the replacement.
bool needs_replacement = false;
GURL::Replacements replacements;
// Remove the www. prefix from the host.
static const char prefix[] = "www.";
static const size_t prefix_len = std::size(prefix) - 1;
std::string host = stripped_destination_url.host();
if (host.compare(0, prefix_len, prefix) == 0 && host.length() > prefix_len) {
replacements.SetHostStr(std::string_view(host).substr(prefix_len));
needs_replacement = true;
}
// Replace https protocol with http, as long as the user didn't explicitly
// specify one of the two.
if (stripped_destination_url.SchemeIs(url::kHttpsScheme) &&
(input.terms_prefixed_by_http_or_https().empty() ||
!WordMatchesURLContent(input.terms_prefixed_by_http_or_https(), url))) {
replacements.SetSchemeStr(url::kHttpScheme);
needs_replacement = true;
}
if (input.parts().ref.is_empty() && url.has_ref()) {
replacements.ClearRef();
needs_replacement = true;
}
if (needs_replacement)
stripped_destination_url =
stripped_destination_url.ReplaceComponents(replacements);
return stripped_destination_url;
}
// static
void AutocompleteMatch::GetMatchComponents(
const GURL& url,
const std::vector<MatchPosition>& match_positions,
bool* match_in_scheme,
bool* match_in_subdomain) {
DCHECK(match_in_scheme);
DCHECK(match_in_subdomain);
size_t domain_length =
net::registry_controlled_domains::GetDomainAndRegistry(
url, net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES)
.size();
const url::Parsed& parsed = url.parsed_for_possibly_invalid_spec();
size_t host_pos = parsed.CountCharactersBefore(url::Parsed::HOST, false);
bool has_subdomain =
domain_length > 0 && domain_length < url.host_piece().length();
// Subtract an extra character from the domain start to exclude the '.'
// delimiter between subdomain and domain.
size_t subdomain_end =
has_subdomain ? host_pos + url.host_piece().length() - domain_length - 1
: std::string::npos;
for (auto& position : match_positions) {
// Only flag |match_in_scheme| if the match starts at the very beginning.
if (position.first == 0 && parsed.scheme.is_nonempty())
*match_in_scheme = true;
// Subdomain matches must begin before the domain, and end somewhere within
// the host or later.
if (has_subdomain && position.first < subdomain_end &&
position.second > host_pos && parsed.host.is_nonempty()) {
*match_in_subdomain = true;
}
}
}
// static
url_formatter::FormatUrlTypes AutocompleteMatch::GetFormatTypes(
bool preserve_scheme,
bool preserve_subdomain) {
auto format_types = url_formatter::kFormatUrlOmitDefaults;
if (preserve_scheme) {
format_types &= ~url_formatter::kFormatUrlOmitHTTP;
} else {
format_types |= url_formatter::kFormatUrlOmitHTTPS;
}
if (!preserve_subdomain) {
format_types |= url_formatter::kFormatUrlOmitTrivialSubdomains;
}
return format_types;
}
// static
void AutocompleteMatch::LogSearchEngineUsed(
const AutocompleteMatch& match,
TemplateURLService* template_url_service) {
DCHECK(template_url_service);
TemplateURL* template_url = match.GetTemplateURL(template_url_service, false);
if (!template_url) {
return;
}
SearchEngineType search_engine_type =
match.destination_url.is_valid()
? search_engine_utils::GetEngineType(match.destination_url)
: SEARCH_ENGINE_OTHER;
// Check for search engines types not present in prepopulated_engines.json.
// TODO(https://issues.chromium.org/405167888): Remove this check once it is
// no longer necessary to track these additional search engine types.
if (search_engine_type == SEARCH_ENGINE_OTHER) {
if (match.destination_url.is_valid() &&
url::DomainIs(match.destination_url.host_piece(), "siteadvisor.com")) {
search_engine_type = SEARCH_ENGINE_MCAFEE;
}
}
UMA_HISTOGRAM_ENUMERATION("Omnibox.SearchEngineType", search_engine_type,
SEARCH_ENGINE_MAX);
if (template_url->CreatedByPolicy()) {
UMA_HISTOGRAM_ENUMERATION("Omnibox.SearchEngineType.SetByEnterprisePolicy",
search_engine_type, SEARCH_ENGINE_MAX);
switch (template_url->policy_origin()) {
case TemplateURLData::PolicyOrigin::kDefaultSearchProvider:
UMA_HISTOGRAM_ENUMERATION(
"Omnibox.SearchEngineType.SetByEnterprisePolicy."
"DefaultSearchProvider",
search_engine_type, SEARCH_ENGINE_MAX);
break;
case TemplateURLData::PolicyOrigin::kSiteSearch:
UMA_HISTOGRAM_ENUMERATION(
"Omnibox.SearchEngineType.SetByEnterprisePolicy."
"SiteSearchSettings",
search_engine_type, SEARCH_ENGINE_MAX);
break;
case TemplateURLData::PolicyOrigin::kSearchAggregator:
UMA_HISTOGRAM_ENUMERATION(
"Omnibox.SearchEngineType.SetByEnterprisePolicy."
"EnterpriseSearchAggregatorSettings",
search_engine_type, SEARCH_ENGINE_MAX);
break;
default:
NOTREACHED();
}
} else if (template_url->type() == TemplateURL::NORMAL) {
if (template_url_service->GetDefaultSearchProvider() == template_url) {
DefaultSearchManager::Source source =
template_url_service->default_search_provider_source();
if (source == DefaultSearchManager::FROM_USER) {
UMA_HISTOGRAM_ENUMERATION(
"Omnibox.SearchEngineType.SetByUser."
"DefaultSearchProvider",
search_engine_type, SEARCH_ENGINE_MAX);
} else if (source == DefaultSearchManager::FROM_FALLBACK) {
UMA_HISTOGRAM_ENUMERATION(
"Omnibox.SearchEngineType.Fallback."
"DefaultSearchProvider",
search_engine_type, SEARCH_ENGINE_MAX);
}
}
} else if (template_url->type() ==
TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION) {
if (template_url_service->GetDefaultSearchProvider() == template_url) {
UMA_HISTOGRAM_ENUMERATION(
"Omnibox.SearchEngineType.SetByExtension."
"SettingsOverrideDefaultSearchProvider",
search_engine_type, SEARCH_ENGINE_MAX);
} else {
// TODO(crbug.com/367330704): Find an extension that uses the Chrome
// Settings override to add an engine but that doesn't set is_default to
// true in order to manually test this code path.
UMA_HISTOGRAM_ENUMERATION(
"Omnibox.SearchEngineType.SetByExtension."
"SettingsOverrideNonDefaultSearchProvider",
search_engine_type, SEARCH_ENGINE_MAX);
}
} else if (template_url->type() == TemplateURL::OMNIBOX_API_EXTENSION) {
// The omnibox API only allows for keyword/site search entries, not default
// search engines so only one type of histogram needs to be recorded here.
UMA_HISTOGRAM_ENUMERATION(
"Omnibox.SearchEngineType.SetByExtension.OmniboxAPI",
search_engine_type, SEARCH_ENGINE_MAX);
}
}
void AutocompleteMatch::ComputeStrippedDestinationURL(
const AutocompleteInput& input,
TemplateURLService* template_url_service) {
// Other than document suggestions, computing `stripped_destination_url` will
// have the same result during a match's lifecycle, so it's safe to skip
// re-computing it if it's already computed. Document provider and history
// quick provider document suggestions' `stripped_url`s are pre-computed by
// the document provider, and overwriting them here would be wasteful and, in
// the case of the document provider, prevent potential deduping.
if (stripped_destination_url.is_empty()) {
stripped_destination_url = GURLToStrippedGURL(
destination_url, input, template_url_service, keyword,
/*keep_search_intent_params=*/false);
}
}
bool AutocompleteMatch::IsDocumentSuggestion() {
const GURL docs_url = DocumentProvider::GetURLForDeduping(destination_url);
// May as well set `stripped_destination_url` to avoid duplicate computation
// later in `ComputeStrippedDestinationURL()`. Additionally tracking if the
// suggestion is not a doc would add more clutter than benefit.
if (docs_url.is_valid())
stripped_destination_url = docs_url;
return docs_url.is_valid();
}
bool AutocompleteMatch::IsActionCompatible() const {
return type != AutocompleteMatchType::SEARCH_SUGGEST_ENTITY &&
// Attaching to Tail Suggest types looks weird, and is actually
// technically wrong because the Pedals annotator (and history clusters
// annotator) both use match.contents. If we do want to turn on Actions
// for tail suggest in the future, we should switch to using
// match.fill_into_edit or maybe page title for URL matches, and come
// up with a UI design for the button in the tail suggest layout.
type != AutocompleteMatchType::SEARCH_SUGGEST_TAIL;
}
bool AutocompleteMatch::HasInstantKeyword(
TemplateURLService* template_url_service) const {
if (!associated_keyword) {
return false;
}
TemplateURL* turl =
associated_keyword->GetTemplateURL(template_url_service, false);
return turl && (turl->starter_pack_id() != 0 || turl->featured_by_policy());
}
void AutocompleteMatch::GetKeywordUIState(
TemplateURLService* template_url_service,
bool is_history_embeddings_enabled,
std::u16string* keyword_out,
std::u16string* keyword_placeholder_out,
bool* is_keyword_hint) const {
*is_keyword_hint = associated_keyword != nullptr;
keyword_out->assign(
*is_keyword_hint
? associated_keyword->keyword
: GetSubstitutingExplicitlyInvokedKeyword(template_url_service));
*keyword_placeholder_out =
GetKeywordPlaceholder(GetTemplateURL(template_url_service, false),
is_history_embeddings_enabled);
}
std::u16string AutocompleteMatch::GetSubstitutingExplicitlyInvokedKeyword(
TemplateURLService* template_url_service) const {
if (!ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_KEYWORD) ||
template_url_service == nullptr) {
return std::u16string();
}
const TemplateURL* t_url = GetTemplateURL(template_url_service, false);
return (t_url &&
t_url->SupportsReplacement(template_url_service->search_terms_data()))
? keyword
: std::u16string();
}
// static
std::u16string AutocompleteMatch::GetKeywordPlaceholder(
const TemplateURL* template_url,
bool is_history_embeddings_enabled) {
#if BUILDFLAG(IS_IOS)
// `kOmniboxScoped` isn't defined on iOS and all history embedding subfeatures
// are disabled on iOS.
return u"";
#else
if (!template_url) {
return u"";
}
if (!history_embeddings::GetFeatureParameters().omnibox_scoped) {
return u"";
}
int message_id;
switch (template_url->starter_pack_id()) {
case template_url_starter_pack_data::kBookmarks:
message_id = IDS_OMNIBOX_BOOKMARKS_SCOPE_PLACEHOLDER_TEXT;
break;
case template_url_starter_pack_data::kHistory:
message_id = is_history_embeddings_enabled
? IDS_OMNIBOX_HISTORY_EMBEDDINGS_SCOPE_PLACEHOLDER_TEXT
: IDS_OMNIBOX_HISTORY_SCOPE_PLACEHOLDER_TEXT;
break;
case template_url_starter_pack_data::kTabs:
message_id = IDS_OMNIBOX_TABS_SCOPE_PLACEHOLDER_TEXT;
break;
case template_url_starter_pack_data::kGemini:
message_id = IDS_OMNIBOX_GEMINI_SCOPE_PLACEHOLDER_TEXT;
break;
case template_url_starter_pack_data::kPage:
message_id = IDS_OMNIBOX_PAGE_SCOPE_PLACEHOLDER_TEXT;
break;
case template_url_starter_pack_data::kAiMode:
message_id = IDS_OMNIBOX_AI_MODE_SCOPE_PLACEHOLDER_TEXT;
break;
default:
return u"";
}
return l10n_util::GetStringUTF16(message_id);
#endif
}
TemplateURL* AutocompleteMatch::GetTemplateURL(
TemplateURLService* template_url_service,
bool allow_fallback_to_destination_host) const {
return GetTemplateURLWithKeyword(template_url_service, keyword,
allow_fallback_to_destination_host
? destination_url.host()
: std::string());
}
GURL AutocompleteMatch::ImageUrl() const {
return answer_template.has_value()
? GURL(answer_template->answers(0).image().url())
: image_url;
}
void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
const std::string& value) {
DCHECK(!property.empty());
additional_info[property] = value;
}
void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
const std::u16string& value) {
RecordAdditionalInfo(property, base::UTF16ToUTF8(value));
}
void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
int value) {
RecordAdditionalInfo(property, base::NumberToString(value));
}
void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
double value) {
RecordAdditionalInfo(property, base::NumberToString(value));
}
void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
base::Time value) {
RecordAdditionalInfo(
property, base::StringPrintf("%d hours ago",
(base::Time::Now() - value).InHours()));
}
std::string AutocompleteMatch::GetAdditionalInfoForDebugging(
const std::string& property) const {
auto i(additional_info.find(property));
return (i == additional_info.end()) ? std::string() : i->second;
}
metrics::OmniboxEventProto::ProviderType
AutocompleteMatch::GetOmniboxEventProviderType(int action_index) const {
using metrics::OmniboxEventProto;
// Mostly the `provider` provides the provider type below, but a few
// action types have meaningful overrides here.
if (action_index >= 0 && static_cast<size_t>(action_index) < actions.size()) {
switch (actions[action_index]->ActionId()) {
case OmniboxActionId::PEDAL:
return OmniboxEventProto::PEDALS;
case OmniboxActionId::TAB_SWITCH:
return OmniboxEventProto::TAB_SWITCH;
default:
break;
}
}
if (provider) {
return provider->AsOmniboxEventProviderType();
}
return OmniboxEventProto::UNKNOWN_PROVIDER;
}
metrics::OmniboxEventProto::Suggestion::ResultType
AutocompleteMatch::GetOmniboxEventResultType(int action_index) const {
using metrics::OmniboxEventProto;
if (action_index >= 0 && static_cast<size_t>(action_index) < actions.size()) {
switch (actions[action_index]->ActionId()) {
case OmniboxActionId::PEDAL:
case OmniboxActionId::CONTEXTUAL_SEARCH_ASK_ABOUT_PAGE:
case OmniboxActionId::CONTEXTUAL_SEARCH_SELECT_REGION:
case OmniboxActionId::CONTEXTUAL_SEARCH_OPEN_LENS:
return OmniboxEventProto::Suggestion::PEDAL;
case OmniboxActionId::TAB_SWITCH:
return OmniboxEventProto::Suggestion::TAB_SWITCH;
case OmniboxActionId::HISTORY_CLUSTERS:
case OmniboxActionId::ACTION_IN_SUGGEST:
case OmniboxActionId::ANSWER_ACTION:
case OmniboxActionId::EXTENSION_ACTION:
case OmniboxActionId::CONTEXTUAL_SEARCH_FULFILLMENT:
// Preserve existing behavior by continuing on to use the match `type`.
break;
case OmniboxActionId::STARTER_PACK_BOOKMARKS:
case OmniboxActionId::STARTER_PACK_HISTORY:
case OmniboxActionId::STARTER_PACK_TABS:
case OmniboxActionId::STARTER_PACK_AI_MODE:
return OmniboxEventProto::Suggestion::STARTER_PACK;
case OmniboxActionId::UNKNOWN:
case OmniboxActionId::LAST:
NOTREACHED();
}
}
switch (type) {
case AutocompleteMatchType::URL_WHAT_YOU_TYPED:
return OmniboxEventProto::Suggestion::URL_WHAT_YOU_TYPED;
case AutocompleteMatchType::HISTORY_URL:
return OmniboxEventProto::Suggestion::HISTORY_URL;
case AutocompleteMatchType::HISTORY_TITLE:
return OmniboxEventProto::Suggestion::HISTORY_TITLE;
case AutocompleteMatchType::HISTORY_BODY:
return OmniboxEventProto::Suggestion::HISTORY_BODY;
case AutocompleteMatchType::HISTORY_KEYWORD:
return OmniboxEventProto::Suggestion::HISTORY_KEYWORD;
case AutocompleteMatchType::NAVSUGGEST:
return OmniboxEventProto::Suggestion::NAVSUGGEST;
case AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED:
return OmniboxEventProto::Suggestion::SEARCH_WHAT_YOU_TYPED;
case AutocompleteMatchType::SEARCH_HISTORY:
case AutocompleteMatchType::TILE_REPEATABLE_QUERY:
return OmniboxEventProto::Suggestion::SEARCH_HISTORY;
case AutocompleteMatchType::SEARCH_SUGGEST:
return OmniboxEventProto::Suggestion::SEARCH_SUGGEST;
case AutocompleteMatchType::SEARCH_SUGGEST_ENTITY:
return OmniboxEventProto::Suggestion::SEARCH_SUGGEST_ENTITY;
case AutocompleteMatchType::SEARCH_SUGGEST_TAIL:
return OmniboxEventProto::Suggestion::SEARCH_SUGGEST_TAIL;
case AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED:
return OmniboxEventProto::Suggestion::SEARCH_SUGGEST_PERSONALIZED;
case AutocompleteMatchType::SEARCH_SUGGEST_PROFILE:
return OmniboxEventProto::Suggestion::SEARCH_SUGGEST_PROFILE;
case AutocompleteMatchType::CALCULATOR:
return OmniboxEventProto::Suggestion::CALCULATOR;
case AutocompleteMatchType::SEARCH_OTHER_ENGINE:
return OmniboxEventProto::Suggestion::SEARCH_OTHER_ENGINE;
case AutocompleteMatchType::EXTENSION_APP_DEPRECATED:
return OmniboxEventProto::Suggestion::EXTENSION_APP;
case AutocompleteMatchType::BOOKMARK_TITLE:
return OmniboxEventProto::Suggestion::BOOKMARK_TITLE;
case AutocompleteMatchType::NAVSUGGEST_PERSONALIZED:
return OmniboxEventProto::Suggestion::NAVSUGGEST_PERSONALIZED;
case AutocompleteMatchType::CLIPBOARD_URL:
return OmniboxEventProto::Suggestion::CLIPBOARD_URL;
case AutocompleteMatchType::DOCUMENT_SUGGESTION:
return OmniboxEventProto::Suggestion::DOCUMENT;
case AutocompleteMatchType::CLIPBOARD_TEXT:
return OmniboxEventProto::Suggestion::CLIPBOARD_TEXT;
case AutocompleteMatchType::CLIPBOARD_IMAGE:
return OmniboxEventProto::Suggestion::CLIPBOARD_IMAGE;
case AutocompleteMatchType::TILE_SUGGESTION:
return OmniboxEventProto::Suggestion::TILE_SUGGESTION;
case AutocompleteMatchType::TILE_NAVSUGGEST:
case AutocompleteMatchType::TILE_MOST_VISITED_SITE:
return OmniboxEventProto::Suggestion::NAVSUGGEST;
case AutocompleteMatchType::OPEN_TAB:
return OmniboxEventProto::Suggestion::OPEN_TAB;
case AutocompleteMatchType::HISTORY_CLUSTER:
return OmniboxEventProto::Suggestion::HISTORY_CLUSTER;
case AutocompleteMatchType::STARTER_PACK:
return OmniboxEventProto::Suggestion::STARTER_PACK;
case AutocompleteMatchType::VOICE_SUGGEST:
return OmniboxEventProto::Suggestion::SEARCH_SUGGEST;
case AutocompleteMatchType::PEDAL:
return OmniboxEventProto::Suggestion::PEDAL;
case AutocompleteMatchType::HISTORY_EMBEDDINGS:
return OmniboxEventProto::Suggestion::HISTORY_EMBEDDINGS;
case AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH:
return OmniboxEventProto::Suggestion::FEATURED_ENTERPRISE_SEARCH;
case AutocompleteMatchType::NULL_RESULT_MESSAGE:
return OmniboxEventProto::Suggestion::NULL_RESULT_MESSAGE;
case AutocompleteMatchType::HISTORY_EMBEDDINGS_ANSWER:
return OmniboxEventProto::Suggestion::HISTORY_EMBEDDINGS_ANSWER;
case AutocompleteMatchType::TAB_GROUP:
return OmniboxEventProto::Suggestion::TAB_GROUP;
case AutocompleteMatchType::CONTACT_DEPRECATED:
case AutocompleteMatchType::PHYSICAL_WEB_DEPRECATED:
case AutocompleteMatchType::PHYSICAL_WEB_OVERFLOW_DEPRECATED:
case AutocompleteMatchType::TAB_SEARCH_DEPRECATED:
case AutocompleteMatchType::NUM_TYPES:
default:
break;
}
DUMP_WILL_BE_NOTREACHED() << "Unknown AutocompleteMatchType: " << type;
return OmniboxEventProto::Suggestion::UNKNOWN_RESULT_TYPE;
}
bool AutocompleteMatch::IsVerbatimType() const {
const bool is_keyword_verbatim_match =
(type == AutocompleteMatchType::SEARCH_OTHER_ENGINE &&
provider != nullptr &&
provider->type() == AutocompleteProvider::TYPE_SEARCH);
return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
type == AutocompleteMatchType::URL_WHAT_YOU_TYPED ||
is_keyword_verbatim_match;
}
bool AutocompleteMatch::IsVerbatimUrlSuggestion() const {
return type == AutocompleteMatchType::URL_WHAT_YOU_TYPED ||
std::ranges::any_of(duplicate_matches, [](const auto& match) {
return match.type == AutocompleteMatchType::URL_WHAT_YOU_TYPED;
});
}
bool AutocompleteMatch::IsSearchProviderSearchSuggestion() const {
const bool from_search_provider =
(provider && provider->type() == AutocompleteProvider::TYPE_SEARCH);
return from_search_provider &&
type != AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED;
}
bool AutocompleteMatch::IsOnDeviceSearchSuggestion() const {
const bool from_on_device_provider =
(provider &&
provider->type() == AutocompleteProvider::TYPE_ON_DEVICE_HEAD);
return from_on_device_provider && subtypes.contains(271);
}
int AutocompleteMatch::GetSortingOrder() const {
if (IsFeaturedEnterpriseSearchType(type)) {
return 0;
}
if (IsStarterPackType(type)) {
return 1;
}
if constexpr (kIsAndroid) {
if (IsClipboardType(type)) {
return 1;
}
}
#if !BUILDFLAG(IS_IOS)
// Group history cluster suggestions with searches.
if (type == AutocompleteMatchType::HISTORY_CLUSTER) {
return 3;
}
#endif // !BUILDFLAG(IS_IOS)
if (answer_template && actions.size() > 0 &&
OmniboxFieldTrial::kAnswerActionsShowAboveKeyboard.Get()) {
return 4;
}
switch (enterprise_search_aggregator_type) {
case EnterpriseSearchAggregatorType::NONE:
break;
case EnterpriseSearchAggregatorType::QUERY:
return 5;
case EnterpriseSearchAggregatorType::PEOPLE:
return 6;
case EnterpriseSearchAggregatorType::CONTENT:
return 7;
}
if (IsSearchType(type)) {
return 3;
}
// Group boosted shortcuts above searches.
if (shortcut_boosted) {
return 2;
}
if (type == AutocompleteMatchType::HISTORY_EMBEDDINGS_ANSWER) {
return 8;
}
if (IsIphSuggestion()) {
return 9;
}
if (IsToolbelt()) {
return 10;
}
return 4;
}
bool AutocompleteMatch::HasCustomDescription() const {
if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_DESKTOP &&
type == AutocompleteMatchType::CALCULATOR) {
return true;
}
if (suggest_template.has_value() &&
!suggest_template->secondary_text().text().empty()) {
return true;
}
return type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY ||
type == AutocompleteMatchType::SEARCH_SUGGEST_PROFILE ||
type == AutocompleteMatchType::CLIPBOARD_TEXT ||
type == AutocompleteMatchType::CLIPBOARD_IMAGE;
}
bool AutocompleteMatch::IsMlSignalLoggingEligible() const {
const auto& ml_config = OmniboxFieldTrial::GetMLConfig();
if (answer_type != omnibox::ANSWER_TYPE_UNSPECIFIED) {
return false;
}
return type == AutocompleteMatchType::URL_WHAT_YOU_TYPED ||
type == AutocompleteMatchType::HISTORY_URL ||
type == AutocompleteMatchType::HISTORY_TITLE ||
type == AutocompleteMatchType::BOOKMARK_TITLE ||
type == AutocompleteMatchType::NAVSUGGEST ||
type == AutocompleteMatchType::NAVSUGGEST_PERSONALIZED ||
type == AutocompleteMatchType::TILE_NAVSUGGEST ||
(ml_config.shortcut_document_signals &&
type == AutocompleteMatchType::DOCUMENT_SUGGESTION &&
relevance != 0) ||
AutocompleteMatch::IsSearchType(type) ||
AutocompleteMatch::IsVerbatimType();
}
bool AutocompleteMatch::IsMlScoringEligible() const {
if (!IsMlSignalLoggingEligible() || !scoring_signals.has_value()) {
return false;
}
// Do not apply ML scoring to calculator or answer suggestions as the ML model
// currently doesn't provide accurate scores for suggestions that have a low
// click-through rate.
if (type == AutocompleteMatchType::CALCULATOR ||
answer_type != omnibox::ANSWER_TYPE_UNSPECIFIED) {
return false;
}
// Do not apply ML scoring to stale suggestions sourced from the
// DocumentProvider cache.
if (type == AutocompleteMatchType::DOCUMENT_SUGGESTION && relevance == 0) {
return false;
}
const auto& ml_config = OmniboxFieldTrial::GetMLConfig();
// Search suggestions are conditionally eligible for ML scoring.
if (AutocompleteMatch::IsSearchType(type)) {
return ml_config.enable_ml_scoring_for_searches;
}
// Verbatim URL suggestions are conditionally eligible for ML scoring.
// A "verbatim URL" suggestion is any suggestion that is UWYT itself or has
// been deduped with a UWYT suggestion.
if (AutocompleteMatch::IsVerbatimUrlSuggestion()) {
return ml_config.enable_ml_scoring_for_verbatim_urls;
}
// Certain suggestion types are manually excluded from ML scoring (since
// applying ML scoring to these suggestions currently results in suboptimal
// behavior).
if (type == AutocompleteMatchType::NAVSUGGEST ||
type == AutocompleteMatchType::NAVSUGGEST_PERSONALIZED ||
type == AutocompleteMatchType::TILE_NAVSUGGEST) {
return false;
}
// If any of the duplicates under this match are ineligible for ML scoring,
// then the top-level match (this) is also considered ineligible for ML
// scoring.
if (std::ranges::any_of(duplicate_matches, [](const auto& match) {
return !match.IsMlScoringEligible();
})) {
return false;
}
return true;
}
bool AutocompleteMatch::IsTrendSuggestion() const {
return subtypes.contains(/*omnibox::SUBTYPE_TRENDS=*/143);
}
bool AutocompleteMatch::IsIphSuggestion() const {
return iph_type != IphType::kNone;
}
bool AutocompleteMatch::HasAction(OmniboxActionId action_id) const {
return std::ranges::any_of(actions, [&](const auto& action) {
return action && action->ActionId() == action_id;
});
}
bool AutocompleteMatch::IsContextualSearchSuggestion() const {
return subtypes.contains(omnibox::SuggestSubtype::SUBTYPE_CONTEXTUAL_SEARCH);
}
bool AutocompleteMatch::IsToolbelt() const {
return type == AutocompleteMatchType::NULL_RESULT_MESSAGE &&
!actions.empty() && omnibox_feature_configs::Toolbelt::Get().enabled;
}
bool AutocompleteMatch::HasLensSearchAction() const {
return (
suggest_template.has_value() &&
std::ranges::any_of(
suggest_template->action_suggestions(), [](const auto& action) {
return action.action_type() ==
omnibox::SuggestTemplateInfo::TemplateAction::ActionType::
SuggestTemplateInfo_TemplateAction_ActionType_CHROME_LENS;
}));
}
bool AutocompleteMatch::IsSearchAimSuggestion() const {
return suggest_template.has_value() &&
suggest_template->default_search_parameters().contains("udm") &&
suggest_template->default_search_parameters().at("udm") == "50";
}
void AutocompleteMatch::FilterOmniboxActions(
const std::vector<OmniboxActionId>& allowed_action_ids) {
// Short circuit if there's nothing to do.
if (actions.empty()) {
return;
}
// Find the type of object we can keep.
auto allowed_action_id_iter =
std::ranges::find_if(allowed_action_ids, [this](auto allowed_action_id) {
return GetActionWhere([allowed_action_id](const auto& action) {
return action->ActionId() == allowed_action_id;
}) != nullptr;
});
OmniboxActionId allowed_action_id =
allowed_action_id_iter != allowed_action_ids.end()
? *allowed_action_id_iter
: OmniboxActionId::LAST;
std::erase_if(actions, [&](const auto& action) {
return action->ActionId() != allowed_action_id;
});
}
void AutocompleteMatch::FilterAndSortActionsInSuggest() {
if (actions.empty()) {
return;
}
// Sort: Call -> Directions -> Reviews, or Reviews -> Directions -> Call.
auto less_comparator = [](auto k1, auto k2) -> bool {
bool is_less_ascending =
(k1 == omnibox::SuggestTemplateInfo_TemplateAction_ActionType_CALL) ||
(k2 == omnibox::SuggestTemplateInfo_TemplateAction_ActionType_REVIEWS);
return is_less_ascending;
};
std::multimap<omnibox::SuggestTemplateInfo_TemplateAction_ActionType,
scoped_refptr<OmniboxAction>, decltype(less_comparator)>
actions_in_suggest_to_reinsert(less_comparator);
// Collect all Actions in Suggest.
std::erase_if(actions, [&actions_in_suggest_to_reinsert](
const scoped_refptr<OmniboxAction>& action) {
auto* ais = OmniboxActionInSuggest::FromAction(action.get());
if (ais != nullptr) {
actions_in_suggest_to_reinsert.emplace(ais->Type(), action);
}
return ais != nullptr;
});
for (auto pair : actions_in_suggest_to_reinsert) {
actions.emplace_back(std::move(pair.second));
}
}
void AutocompleteMatch::RemoveAnswerActions() {
if (actions.empty()) {
return;
}
std::erase_if(actions, [&](const auto& action) {
auto* ans_action = OmniboxAnswerAction::FromAction(action.get());
return ans_action != nullptr;
});
}
bool AutocompleteMatch::IsTrivialAutocompletion() const {
return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
type == AutocompleteMatchType::URL_WHAT_YOU_TYPED ||
type == AutocompleteMatchType::SEARCH_OTHER_ENGINE;
}
bool AutocompleteMatch::SupportsDeletion() const {
return deletable || std::ranges::any_of(duplicate_matches, [](const auto& m) {
return m.deletable;
});
}
AutocompleteMatch
AutocompleteMatch::GetMatchWithContentsAndDescriptionPossiblySwapped() const {
AutocompleteMatch copy(*this);
if (copy.swap_contents_and_description) {
std::swap(copy.contents, copy.description);
std::swap(copy.contents_class, copy.description_class);
copy.description_for_shortcuts.clear();
copy.description_class_for_shortcuts.clear();
// Clear bit to prevent accidentally performing the swap again.
copy.swap_contents_and_description = false;
}
return copy;
}
void AutocompleteMatch::SetAllowedToBeDefault(const AutocompleteInput& input) {
if (input.text().starts_with('@') && type != Type::SEARCH_WHAT_YOU_TYPED) {
// @ inputs are very special. The only kind of match that can be default is
// a search-what-you-typed sentinel suggestion, so as to not distract from
// the starter pack suggestions. Note: There may be some edge cases to
// consider if more providers are updated to use of this method; then we
// may want to avoid applying this rule when in keyword mode.
allowed_to_be_default_match = false;
} else if (inline_autocompletion.empty()) {
allowed_to_be_default_match = true;
} else if (input.prevent_inline_autocomplete()) {
allowed_to_be_default_match = false;
} else if (input.text().empty() ||
!base::IsUnicodeWhitespace(input.text().back())) {
allowed_to_be_default_match = true;
} else {
// If we've reached here, the input ends in trailing whitespace. If the
// trailing whitespace prefixes |inline_autocompletion|, then allow the
// match to be default and remove the whitespace from
// |inline_autocompletion|.
size_t last_non_whitespace_pos =
input.text().find_last_not_of(base::kWhitespaceUTF16);
DCHECK_NE(last_non_whitespace_pos, std::string::npos);
auto whitespace_suffix = input.text().substr(last_non_whitespace_pos + 1);
if (base::StartsWith(inline_autocompletion, whitespace_suffix,
base::CompareCase::SENSITIVE)) {
inline_autocompletion =
inline_autocompletion.substr(whitespace_suffix.size());
allowed_to_be_default_match = true;
} else {
allowed_to_be_default_match = false;
}
}
}
size_t AutocompleteMatch::EstimateMemoryUsage() const {
size_t res = 0;
res += base::trace_event::EstimateMemoryUsage(fill_into_edit);
res += base::trace_event::EstimateMemoryUsage(additional_text);
res += base::trace_event::EstimateMemoryUsage(inline_autocompletion);
res += base::trace_event::EstimateMemoryUsage(destination_url);
res += base::trace_event::EstimateMemoryUsage(stripped_destination_url);
res += base::trace_event::EstimateMemoryUsage(extra_headers);
res += base::trace_event::EstimateMemoryUsage(image_dominant_color);
res += base::trace_event::EstimateMemoryUsage(image_url);
res += base::trace_event::EstimateMemoryUsage(icon_url);
res += base::trace_event::EstimateMemoryUsage(entity_id);
res += base::trace_event::EstimateMemoryUsage(website_uri);
res += base::trace_event::EstimateMemoryUsage(tail_suggest_common_prefix);
res += base::trace_event::EstimateMemoryUsage(contents);
res += base::trace_event::EstimateMemoryUsage(contents_class);
res += base::trace_event::EstimateMemoryUsage(description);
res += base::trace_event::EstimateMemoryUsage(description_class);
res += base::trace_event::EstimateMemoryUsage(description_for_shortcuts);
res +=
base::trace_event::EstimateMemoryUsage(description_class_for_shortcuts);
res += base::trace_event::EstimateMemoryUsage(associated_keyword);
res += base::trace_event::EstimateMemoryUsage(keyword);
res += base::trace_event::EstimateMemoryUsage(search_terms_args);
res += base::trace_event::EstimateMemoryUsage(post_content);
res += base::trace_event::EstimateMemoryUsage(additional_info);
res += base::trace_event::EstimateMemoryUsage(duplicate_matches);
return res;
}
void AutocompleteMatch::UpgradeMatchWithPropertiesFrom(
AutocompleteMatch& duplicate_match) {
// TODO(manukh): There's some duplicate logic between `BetterDuplicate()` and
// `UpgradeMatchWithPropertiesFrom()`. This is unavoidable due to having to
// taking different fields from different duplicates, rather having 1 match
// that's absolutely overrides all other matches. Perhaps we can avoid this
// if we join the 2 functions.
// For Entity Matches, absorb the duplicate match's |allowed_to_be_default|
// and |inline_autocompletion| properties.
if (type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY &&
fill_into_edit == duplicate_match.fill_into_edit &&
duplicate_match.allowed_to_be_default_match) {
allowed_to_be_default_match = true;
if (inline_autocompletion.empty()) {
inline_autocompletion = duplicate_match.inline_autocompletion;
}
}
// For Search Suggest and Search What-You-Typed matches, absorb any
// Search History type.
if ((type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
type == AutocompleteMatchType::SEARCH_SUGGEST) &&
fill_into_edit == duplicate_match.fill_into_edit &&
IsSearchHistoryType(duplicate_match.type)) {
type = duplicate_match.type;
suggest_type = duplicate_match.suggest_type;
}
// And always absorb the higher relevance score of duplicates.
if (duplicate_match.relevance > relevance) {
RecordAdditionalInfo(kACMatchPropertyScoreBoostedFrom, relevance);
relevance = duplicate_match.relevance;
shortcut_boosted |= duplicate_match.shortcut_boosted;
}
from_previous = from_previous && duplicate_match.from_previous;
// Absorb the `actions` and `takeover_action` so they won't be buried.
// Don't absorb answer actions; they should always be created fresh.
if (actions.empty() && !duplicate_match.actions.empty() &&
IsActionCompatible() &&
OmniboxAnswerAction::FromAction(duplicate_match.actions[0].get()) ==
nullptr) {
actions = std::move(duplicate_match.actions);
takeover_action = std::move(duplicate_match.takeover_action);
}
// Prefer fresh suggestion text over potentially stale shortcut text for
// bookmark paths and document metadata. Don't edit the omnibox text (i.e.
// `fill_into_edit`, `inline_autocompletion`, and `additional_text`) as the
// duplicate may not be `allowed_to_be_default_match`.
if (GetDeduplicationProviderPreferenceScore(duplicate_match.provider) >
GetDeduplicationProviderPreferenceScore(provider)) {
contents = duplicate_match.contents;
contents_class = duplicate_match.contents_class;
description = duplicate_match.description;
description_class = duplicate_match.description_class;
description_for_shortcuts = duplicate_match.description_for_shortcuts;
description_class_for_shortcuts =
duplicate_match.description_class_for_shortcuts;
swap_contents_and_description =
duplicate_match.swap_contents_and_description;
// Image data should stay in sync with the suggestion text.
image_dominant_color = duplicate_match.image_dominant_color;
image_url = duplicate_match.image_url;
icon_url = duplicate_match.icon_url;
// Prefer to keep the original `type` for more helpful metric logging.
// However, searches and non-searches have different ranking & text display
// (see `swap_contents_and_description` and `UpdateKeywordDescriptions()`).
// Using text from a search/URL match but displaying the match like a
// URL/search could look very broken.
if (IsSearchType(type) != IsSearchType(duplicate_match.type))
type = duplicate_match.type;
}
// Copy `rich_autocompletion_triggered` for counterfactual logging.
if (rich_autocompletion_triggered == RichAutocompletionType::kNone) {
rich_autocompletion_triggered =
duplicate_match.rich_autocompletion_triggered;
}
// Merge ML scoring signals from duplicate match when appropriate.
if (OmniboxFieldTrial::IsPopulatingUrlScoringSignalsEnabled() &&
IsMlSignalLoggingEligible()) {
MergeScoringSignals(duplicate_match);
}
}
void AutocompleteMatch::MergeScoringSignals(const AutocompleteMatch& other) {
// Keep consistent:
// - omnibox_event.proto `ScoringSignals`
// - omnibox_scoring_signals.proto `OmniboxScoringSignals`
// - autocomplete_scoring_model_handler.cc
// `AutocompleteScoringModelHandler::ExtractInputFromScoringSignals()`
// - autocomplete_match.cc `AutocompleteMatch::MergeScoringSignals()`
// - autocomplete_controller.cc `RecordScoringSignalCoverageForProvider()`
// - omnibox_metrics_provider.cc `GetScoringSignalsForLogging()`
// - omnibox.mojom `struct Signals`
// - omnibox_page_handler.cc
// `TypeConverter<AutocompleteMatch::ScoringSignals, mojom::SignalsPtr>`
// - omnibox_page_handler.cc `TypeConverter<mojom::SignalsPtr,
// AutocompleteMatch::ScoringSignals>`
// - omnibox_util.ts `signalNames`
// - omnibox/histograms.xml
// `Omnibox.URLScoringModelExecuted.ScoringSignalCoverage`
if (!other.scoring_signals.has_value()) {
return;
}
// Records the ACMatch type of the duplicate match when two or more matches
// with different ml scoring signals are merged.
const char kACMatchPropertyScoringSignalsMerged[] = "Scoring signals merged";
RecordAdditionalInfo(
kACMatchPropertyScoringSignalsMerged,
GetAdditionalInfoForDebugging(kACMatchPropertyScoringSignalsMerged) +
AutocompleteMatchType::ToString(other.type) + ", " +
other.GetAdditionalInfoForDebugging(
kACMatchPropertyScoringSignalsMerged));
if (!scoring_signals.has_value()) {
scoring_signals = std::make_optional<ScoringSignals>();
}
// Take the maximum.
if (other.scoring_signals->has_typed_count()) {
scoring_signals->set_typed_count(std::max(
scoring_signals->typed_count(), other.scoring_signals->typed_count()));
}
// Take the maximum.
if (other.scoring_signals->has_visit_count()) {
scoring_signals->set_visit_count(std::max(
scoring_signals->visit_count(), other.scoring_signals->visit_count()));
}
// Take the minimum.
if (scoring_signals->has_elapsed_time_last_visit_secs() &&
other.scoring_signals->has_elapsed_time_last_visit_secs()) {
scoring_signals->set_elapsed_time_last_visit_secs(
std::min(scoring_signals->elapsed_time_last_visit_secs(),
other.scoring_signals->elapsed_time_last_visit_secs()));
} else if (other.scoring_signals->has_elapsed_time_last_visit_secs()) {
scoring_signals->set_elapsed_time_last_visit_secs(
other.scoring_signals->elapsed_time_last_visit_secs());
}
// Take the maximum.
if (other.scoring_signals->has_shortcut_visit_count()) {
scoring_signals->set_shortcut_visit_count(
std::max(scoring_signals->shortcut_visit_count(),
other.scoring_signals->shortcut_visit_count()));
}
// Take the minimum.
if (scoring_signals->has_shortest_shortcut_len() &&
other.scoring_signals->has_shortest_shortcut_len()) {
scoring_signals->set_shortest_shortcut_len(
std::min(scoring_signals->shortest_shortcut_len(),
other.scoring_signals->shortest_shortcut_len()));
} else if (other.scoring_signals->has_shortest_shortcut_len()) {
scoring_signals->set_shortest_shortcut_len(
other.scoring_signals->shortest_shortcut_len());
}
// Take the minimum.
if (scoring_signals->has_elapsed_time_last_shortcut_visit_sec() &&
other.scoring_signals->has_elapsed_time_last_shortcut_visit_sec()) {
scoring_signals->set_elapsed_time_last_shortcut_visit_sec(std::min(
scoring_signals->elapsed_time_last_shortcut_visit_sec(),
other.scoring_signals->elapsed_time_last_shortcut_visit_sec()));
} else if (other.scoring_signals
->has_elapsed_time_last_shortcut_visit_sec()) {
scoring_signals->set_elapsed_time_last_shortcut_visit_sec(
other.scoring_signals->elapsed_time_last_shortcut_visit_sec());
}
// Take the OR result.
if (other.scoring_signals->has_is_host_only()) {
scoring_signals->set_is_host_only(scoring_signals->is_host_only() ||
other.scoring_signals->is_host_only());
}
// Take the maximum.
if (other.scoring_signals->has_num_bookmarks_of_url()) {
scoring_signals->set_num_bookmarks_of_url(
std::max(scoring_signals->num_bookmarks_of_url(),
other.scoring_signals->num_bookmarks_of_url()));
}
// Take the minimum.
if (scoring_signals->has_first_bookmark_title_match_position() &&
other.scoring_signals->has_first_bookmark_title_match_position()) {
scoring_signals->set_first_bookmark_title_match_position(
std::min(scoring_signals->first_bookmark_title_match_position(),
other.scoring_signals->first_bookmark_title_match_position()));
} else if (other.scoring_signals->has_first_bookmark_title_match_position()) {
scoring_signals->set_first_bookmark_title_match_position(
other.scoring_signals->first_bookmark_title_match_position());
}
// Take the maximum.
if (other.scoring_signals->has_total_bookmark_title_match_length()) {
scoring_signals->set_total_bookmark_title_match_length(
std::max(scoring_signals->total_bookmark_title_match_length(),
other.scoring_signals->total_bookmark_title_match_length()));
}
// Take the maximum.
if (other.scoring_signals->has_num_input_terms_matched_by_bookmark_title()) {
scoring_signals->set_num_input_terms_matched_by_bookmark_title(std::max(
scoring_signals->num_input_terms_matched_by_bookmark_title(),
other.scoring_signals->num_input_terms_matched_by_bookmark_title()));
}
// Take the minimum.
if (scoring_signals->has_first_url_match_position() &&
other.scoring_signals->has_first_url_match_position()) {
scoring_signals->set_first_url_match_position(
std::min(scoring_signals->first_url_match_position(),
other.scoring_signals->first_url_match_position()));
} else if (other.scoring_signals->has_first_url_match_position()) {
scoring_signals->set_first_url_match_position(
other.scoring_signals->first_url_match_position());
}
// Take the maximum.
if (other.scoring_signals->has_total_url_match_length()) {
scoring_signals->set_total_url_match_length(
std::max(scoring_signals->total_url_match_length(),
other.scoring_signals->total_url_match_length()));
}
// Take the OR result.
if (other.scoring_signals->has_host_match_at_word_boundary()) {
scoring_signals->set_host_match_at_word_boundary(
scoring_signals->host_match_at_word_boundary() ||
other.scoring_signals->host_match_at_word_boundary());
}
// Take the maximum.
if (other.scoring_signals->has_total_host_match_length()) {
scoring_signals->set_total_host_match_length(
std::max(scoring_signals->total_host_match_length(),
other.scoring_signals->total_host_match_length()));
}
// Take the maximum.
if (other.scoring_signals->has_total_path_match_length()) {
scoring_signals->set_total_path_match_length(
std::max(scoring_signals->total_path_match_length(),
other.scoring_signals->total_path_match_length()));
}
// Take the maximum.
if (other.scoring_signals->has_total_query_or_ref_match_length()) {
scoring_signals->set_total_query_or_ref_match_length(
std::max(scoring_signals->total_query_or_ref_match_length(),
other.scoring_signals->total_query_or_ref_match_length()));
}
// Take the maximum.
if (other.scoring_signals->has_total_title_match_length()) {
scoring_signals->set_total_title_match_length(
std::max(scoring_signals->total_title_match_length(),
other.scoring_signals->total_title_match_length()));
}
// Take the OR result.
if (other.scoring_signals->has_has_non_scheme_www_match()) {
scoring_signals->set_has_non_scheme_www_match(
scoring_signals->has_non_scheme_www_match() ||
other.scoring_signals->has_non_scheme_www_match());
}
// Take the maximum.
if (other.scoring_signals->has_num_input_terms_matched_by_title()) {
scoring_signals->set_num_input_terms_matched_by_title(
std::max(scoring_signals->num_input_terms_matched_by_title(),
other.scoring_signals->num_input_terms_matched_by_title()));
}
// Take the maximum.
if (other.scoring_signals->has_num_input_terms_matched_by_url()) {
scoring_signals->set_num_input_terms_matched_by_url(
std::max(scoring_signals->num_input_terms_matched_by_url(),
other.scoring_signals->num_input_terms_matched_by_url()));
}
// Take the minimum.
if (scoring_signals->has_length_of_url() &&
other.scoring_signals->has_length_of_url()) {
scoring_signals->set_length_of_url(
std::min(scoring_signals->length_of_url(),
other.scoring_signals->length_of_url()));
} else if (other.scoring_signals->has_length_of_url()) {
scoring_signals->set_length_of_url(other.scoring_signals->length_of_url());
}
// Take the maximum.
if (other.scoring_signals->has_site_engagement()) {
scoring_signals->set_site_engagement(
std::max(scoring_signals->site_engagement(),
other.scoring_signals->site_engagement()));
}
// Take the OR result.
if (other.scoring_signals->has_allowed_to_be_default_match()) {
scoring_signals->set_allowed_to_be_default_match(
scoring_signals->allowed_to_be_default_match() ||
other.scoring_signals->allowed_to_be_default_match());
}
// Take the maximum.
if (other.scoring_signals->has_search_suggest_relevance()) {
scoring_signals->set_search_suggest_relevance(
std::max(scoring_signals->search_suggest_relevance(),
other.scoring_signals->search_suggest_relevance()));
}
// Take the OR result.
if (other.scoring_signals->has_is_search_suggest_entity()) {
scoring_signals->set_is_search_suggest_entity(
scoring_signals->is_search_suggest_entity() ||
other.scoring_signals->is_search_suggest_entity());
}
// Take the OR result.
if (other.scoring_signals->has_is_verbatim()) {
scoring_signals->set_is_verbatim(scoring_signals->is_verbatim() ||
other.scoring_signals->is_verbatim());
}
// Take the OR result.
if (other.scoring_signals->has_is_navsuggest()) {
scoring_signals->set_is_navsuggest(scoring_signals->is_navsuggest() ||
other.scoring_signals->is_navsuggest());
}
// Take the OR result.
if (other.scoring_signals->has_is_search_suggest_tail()) {
scoring_signals->set_is_search_suggest_tail(
scoring_signals->is_search_suggest_tail() ||
other.scoring_signals->is_search_suggest_tail());
}
// Take the OR result.
if (other.scoring_signals->has_is_answer_suggest()) {
scoring_signals->set_is_answer_suggest(
scoring_signals->is_answer_suggest() ||
other.scoring_signals->is_answer_suggest());
}
// Take the OR result.
if (other.scoring_signals->has_is_calculator_suggest()) {
scoring_signals->set_is_calculator_suggest(
scoring_signals->is_calculator_suggest() ||
other.scoring_signals->is_calculator_suggest());
}
}
bool AutocompleteMatch::TryRichAutocompletion(
const AutocompleteInput& input,
const std::u16string& primary_text,
const std::u16string& secondary_text,
const std::u16string& shortcut_text) {
const auto& params = RichAutocompletionParams::GetParams();
if (!params.enabled)
return false;
if (input.prevent_inline_autocomplete())
return false;
// Lowercase the strings for case-insensitive comparisons.
const std::u16string primary_text_lower{base::i18n::ToLower(primary_text)};
const std::u16string input_text_lower{base::i18n::ToLower(input.text())};
// Try matching the prefix of |primary_text|.
if (base::StartsWith(primary_text_lower, input_text_lower,
base::CompareCase::SENSITIVE)) {
// This case intentionally doesn't set |rich_autocompletion_triggered| since
// presumably non-rich autocompletion should also be able to handle this
// case.
inline_autocompletion = primary_text.substr(input_text_lower.length());
allowed_to_be_default_match = true;
RecordAdditionalInfo("autocompletion", "primary & prefix");
return true;
}
// Only shortcuts are allowed to autocomplete `secondary_text` and
// `shortcut_text`.
if (shortcut_text.empty()) {
return false;
}
const std::u16string secondary_text_lower{
base::i18n::ToLower(secondary_text)};
// Try matching the prefix of `secondary_text`.
if (input.text().size() >= params.autocomplete_titles_min_char &&
base::StartsWith(secondary_text_lower, input_text_lower,
base::CompareCase::SENSITIVE)) {
rich_autocompletion_triggered = RichAutocompletionType::kTitlePrefix;
additional_text = primary_text;
inline_autocompletion = secondary_text.substr(input_text_lower.length());
allowed_to_be_default_match = true;
RecordAdditionalInfo("autocompletion", "secondary & prefix");
return true;
}
// Try matching the prefix of `shortcut_text`.
if (input.text().size() >= params.autocomplete_shortcut_text_min_char &&
base::StartsWith(base::i18n::ToLower(shortcut_text), input_text_lower,
base::CompareCase::SENSITIVE)) {
rich_autocompletion_triggered = RichAutocompletionType::kShortcutTextPrefix;
additional_text = primary_text;
inline_autocompletion = shortcut_text.substr(input_text_lower.length());
allowed_to_be_default_match = true;
RecordAdditionalInfo("autocompletion", "shortcut text & prefix");
return true;
}
// Don't try matching a non-prefix of `shortcut_text`. Shortcut matches are
// intended for repeated inputs, i.e. inputs that are prefixes of previous
// inputs.
return false;
}
void AutocompleteMatch::WriteIntoTrace(perfetto::TracedValue context) const {
perfetto::TracedDictionary dict = std::move(context).WriteDictionary();
dict.Add("fill_into_edit", fill_into_edit);
dict.Add("additional_text", additional_text);
dict.Add("destination_url", destination_url);
dict.Add("keyword", keyword);
}
OmniboxAction* AutocompleteMatch::GetActionAt(size_t index) const {
return index >= actions.size() ? nullptr : actions[index].get();
}
bool AutocompleteMatch::HasTakeoverAction(OmniboxActionId id) const {
return takeover_action && takeover_action->ActionId() == id;
}
AutocompleteMatch AutocompleteMatch::CreateActionMatch(
size_t action_index) const {
CHECK_LT(action_index, actions.size());
CHECK_EQ(actions[action_index]->ActionId(), OmniboxActionId::PEDAL);
AutocompleteMatch action_match(provider, relevance, false,
AutocompleteMatchType::PEDAL);
action_match.takeover_action = actions[action_index];
action_match.transition = ui::PAGE_TRANSITION_GENERATED;
action_match.suggest_type = omnibox::SuggestType::TYPE_NATIVE_CHROME;
action_match.suggestion_group_id = suggestion_group_id;
// Use the pedal text as primary match `contents`.
action_match.contents = action_match.takeover_action->GetLabelStrings().hint;
action_match.fill_into_edit = action_match.contents;
if (action_match.contents.empty()) {
action_match.contents_class.clear();
} else {
action_match.contents_class = {{0, ACMatchClassification::NONE}};
}
return action_match;
}
#if DCHECK_IS_ON()
void AutocompleteMatch::Validate() const {
std::string provider_name = provider ? provider->GetName() : "None";
ValidateClassifications(contents, contents_class, provider_name);
ValidateClassifications(description, description_class, provider_name);
ValidateClassifications(description_for_shortcuts,
description_class_for_shortcuts, provider_name);
}
#endif // DCHECK_IS_ON()
// static
void AutocompleteMatch::ValidateClassifications(
const std::u16string& text,
const ACMatchClassifications& classifications,
const std::string& provider_name) {
#if DCHECK_IS_ON()
std::string debug_string = " text: " + base::UTF16ToUTF8(text) +
", provider: " + provider_name +
", classifications (offset, style): ";
for (const auto& classification : classifications) {
debug_string += " (" + base::NumberToString(classification.offset) + ", " +
base::NumberToString(classification.style) + ")";
}
if (text.empty()) {
DCHECK(classifications.empty()) << debug_string;
return;
}
// The classifications should always cover the whole string.
DCHECK(!classifications.empty()) << " No classification;" << debug_string;
DCHECK_EQ(0U, classifications[0].offset)
<< " Classification misses beginning;" << debug_string;
if (classifications.size() == 1)
return;
// The classifications should always be sorted.
size_t last_offset = classifications[0].offset;
for (auto i(classifications.begin() + 1); i != classifications.end(); ++i) {
DCHECK_GT(i->offset, last_offset)
<< " Unsorted classification;" << debug_string;
DCHECK_LT(i->offset, text.length())
<< " Out of bounds classification;" << debug_string;
last_offset = i->offset;
}
#endif // DCHECK_IS_ON()
}