| // 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() |
| } |