| // Copyright 2025 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/contextual_search_provider.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "components/lens/proto/server/lens_overlay_response.pb.h" |
| #include "components/omnibox/browser/actions/contextual_search_action.h" |
| #include "components/omnibox/browser/actions/omnibox_action_concepts.h" |
| #include "components/omnibox/browser/autocomplete_enums.h" |
| #include "components/omnibox/browser/autocomplete_input.h" |
| #include "components/omnibox/browser/autocomplete_match.h" |
| #include "components/omnibox/browser/autocomplete_match_classification.h" |
| #include "components/omnibox/browser/autocomplete_match_type.h" |
| #include "components/omnibox/browser/autocomplete_provider_client.h" |
| #include "components/omnibox/browser/autocomplete_provider_debouncer.h" |
| #include "components/omnibox/browser/autocomplete_provider_listener.h" |
| #include "components/omnibox/browser/base_search_provider.h" |
| #include "components/omnibox/browser/lens_suggest_inputs_utils.h" |
| #include "components/omnibox/browser/match_compare.h" |
| #include "components/omnibox/browser/omnibox_field_trial.h" |
| #include "components/omnibox/browser/omnibox_prefs.h" |
| #include "components/omnibox/browser/page_classification_functions.h" |
| #include "components/omnibox/browser/remote_suggestions_service.h" |
| #include "components/omnibox/browser/search_suggestion_parser.h" |
| #include "components/omnibox/browser/suggestion_group_util.h" |
| #include "components/omnibox/browser/zero_suggest_provider.h" |
| #include "components/omnibox/common/omnibox_feature_configs.h" |
| #include "components/search_engines/search_engine_type.h" |
| #include "components/search_engines/search_terms_data.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 "components/strings/grit/components_strings.h" |
| #include "components/url_formatter/url_formatter.h" |
| #include "services/network/public/cpp/simple_url_loader.h" |
| #include "third_party/metrics_proto/omnibox_event.pb.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "url/gurl.h" |
| |
| namespace { |
| |
| // The internal default verbatim match relevance. |
| constexpr int kDefaultVerbatimMatchRelevance = 1500; |
| |
| // Creates a URL to drive the lens API to open the side panel lens for 'whole |
| // page mode' with the given `query`. |
| GURL ComputeDestinationUrlForLensQueryText( |
| const std::u16string& query, |
| const TemplateURLService* turl_service) { |
| CHECK(!query.empty()); |
| // This algorithm was reverse engineered as there's no documentation. So it's |
| // very possible some of this is unnecessary or even wrong. Used these as |
| // references: |
| // - `ContextualSearchProvider::AddDefaultVerbatimMatch()` |
| // - `SearchSuggestionParser::SuggestResult::SuggestResult()` |
| // - `BaseSearchProvider::CreateSearchSuggestion()`. |
| // TODO(b/403629222): Once we have a lens API that accepts a query string |
| // instead of codifying it within a GURL, we may be able to bypass |
| // `ComputeDestinationUrlForLensQueryText()`. |
| auto search_terms_args = |
| std::make_unique<TemplateURLRef::SearchTermsArgs>(query); |
| search_terms_args->request_source = SearchTermsData::RequestSource::SEARCHBOX; |
| search_terms_args->original_query = query; |
| // 0 isn't even one of the valid enum value. Valid enum values are -2 and -1. |
| search_terms_args->accepted_suggestion = 0; |
| search_terms_args->append_extra_query_params_from_command_line = true; |
| const TemplateURLRef& search_url = |
| turl_service->GetDefaultSearchProvider()->url_ref(); |
| const SearchTermsData& search_terms_data = turl_service->search_terms_data(); |
| return GURL( |
| search_url.ReplaceSearchTerms(*search_terms_args, search_terms_data)); |
| } |
| |
| // Populates |results| with the response if it can be successfully parsed for |
| // |input|. Returns true if the response can be successfully parsed. |
| bool ParseRemoteResponse(const std::string& response_json, |
| AutocompleteProviderClient* client, |
| const AutocompleteInput& input, |
| SearchSuggestionParser::Results* results) { |
| DCHECK(results); |
| if (response_json.empty()) { |
| return false; |
| } |
| |
| std::optional<base::Value::List> response_data = |
| SearchSuggestionParser::DeserializeJsonData(response_json); |
| if (!response_data) { |
| return false; |
| } |
| |
| return SearchSuggestionParser::ParseSuggestResults( |
| *response_data, input, client->GetSchemeClassifier(), |
| /*default_result_relevance=*/omnibox::kDefaultRemoteZeroSuggestRelevance, |
| /*is_keyword_result=*/true, results); |
| } |
| |
| // Helper to determine which matches to show. Since this is the primary |
| // contributor to this provider's complexity, it's easier to manage when |
| // centralized than distributed. |
| struct EligibleMatchesAndActions { |
| EligibleMatchesAndActions(const AutocompleteInput& input, |
| const TemplateURL* template_url, |
| AutocompleteProviderClient* client) { |
| // - Hide toolbelt in realbox. |
| // - Check feature/params for zero and typed inputs. |
| // - Hide toolbelt if user has disabled the context menu option. |
| // - Check feature param for removing toolbelt when in keyword mode. |
| const auto& toolbelt_config = omnibox_feature_configs::Toolbelt::Get(); |
| toolbelt = |
| input.current_page_classification() != |
| metrics::OmniboxEventProto::NTP_REALBOX && |
| toolbelt_config.enabled && |
| (toolbelt_config.keep_toolbelt_after_input || input.IsZeroSuggest()) && |
| client->GetPrefs()->GetBoolean(omnibox::kShowSearchTools) && |
| (toolbelt_config.keep_toolbelt_in_keyword_mode || !template_url); |
| |
| // - Restricted to DSE google, which is already checked in |
| // `client->IsLensEnabled()`. |
| // - Not restricted by locale. |
| // - `LensEntrypointEligible()` restricts lens to web & SRP. |
| // - Unlike `lens_entry_match`, `toolbelt_lens` is not restricted to zero |
| // inputs. |
| toolbelt_lens = |
| toolbelt && |
| ToolbeltActionEligible( |
| input, client, toolbelt_config.show_lens_action_on_non_ntp, |
| toolbelt_config.show_lens_action_on_ntp, std::nullopt) && |
| (toolbelt_config.always_include_lens_action || |
| LensEntrypointEligible(input, client)); |
| |
| // - Restricted to when `kAiModeOmniboxEntryPoint` is disabled |
| // - Restricted to DSE google |
| // - Restricted to country US |
| // - Restricted to locale en-US |
| // - Restricted to when `kAIModeSettings` policy is enabled |
| toolbelt_ai_mode = |
| toolbelt && |
| !base::FeatureList::IsEnabled(omnibox::kAiModeOmniboxEntryPoint) && |
| client->IsAimEligible() && |
| ToolbeltActionEligible( |
| input, client, toolbelt_config.show_ai_mode_action_on_non_ntp, |
| toolbelt_config.show_ai_mode_action_on_ntp, |
| template_url_starter_pack_data::StarterPackId::kAiMode); |
| |
| toolbelt_history = |
| toolbelt && |
| ToolbeltActionEligible( |
| input, client, toolbelt_config.show_history_action_on_non_ntp, |
| toolbelt_config.show_history_action_on_ntp, |
| template_url_starter_pack_data::StarterPackId::kHistory); |
| |
| toolbelt_bookmarks = |
| toolbelt && |
| ToolbeltActionEligible( |
| input, client, toolbelt_config.show_bookmarks_action_on_non_ntp, |
| toolbelt_config.show_bookmarks_action_on_ntp, |
| template_url_starter_pack_data::StarterPackId::kBookmarks); |
| |
| toolbelt_tabs = |
| toolbelt && |
| ToolbeltActionEligible( |
| input, client, toolbelt_config.show_tabs_action_on_non_ntp, |
| toolbelt_config.show_tabs_action_on_ntp, |
| template_url_starter_pack_data::StarterPackId::kTabs); |
| |
| // Hide toolbelt if it would be empty. |
| toolbelt = |
| toolbelt && (toolbelt_lens || toolbelt_ai_mode || toolbelt_history || |
| toolbelt_bookmarks || toolbelt_tabs); |
| |
| // - Check feature/params. |
| // - Restricted to DSE google, which is already checked in |
| // `client->IsLensEnabled()`. |
| // - Not restricted by locale. |
| // - `LensEntrypointEligible()` restricts lens to web & SRP. |
| // - Unlike `toolbelt_lens`, `lens_entry_match` is restricted to zero |
| // inputs. `lens_entry_match`, `toolbelt_lens` is not restricted to zero |
| // inputs. |
| // - Only shown if toolbelt lens not shown. |
| const auto& contextual_search_config = |
| omnibox_feature_configs::ContextualSearch::Get(); |
| lens_entry_match = contextual_search_config.show_open_lens_action && |
| !toolbelt_lens && input.IsZeroSuggest() && |
| LensEntrypointEligible(input, client); |
| |
| // - Check feature/params. |
| // - Disabled if either `toolbelt` or `contextual_search_config` are shown. |
| // They are not compatible. Enabling this in parallel will require |
| // splitting the provider or making them play nicely. |
| // - Shown only when the user is in '@page' scope. |
| // - Hidden in incognito. |
| page_verbatim = !toolbelt_config.enabled && |
| !contextual_search_config.show_open_lens_action && |
| template_url && |
| template_url->starter_pack_id() == |
| template_url_starter_pack_data::StarterPackId::kPage && |
| !client->IsOffTheRecord(); |
| |
| // - Same base requirements as `page_verbatim` |
| // - Hidden on zero input. |
| page_suggestions = page_verbatim && !input.text().empty() && |
| !input.omit_asynchronous_matches(); |
| } |
| |
| // Show on web & SRP, but not NTP. |
| // Http, https, & local files are allowed but not other local schemes. |
| // Do not show if Lens is already opened. |
| static bool LensEntrypointEligible(const AutocompleteInput& input, |
| AutocompleteProviderClient* client) { |
| return (omnibox::IsOtherWebPage(input.current_page_classification()) || |
| omnibox::IsSearchResultsPage( |
| input.current_page_classification())) && |
| (input.current_url().SchemeIsHTTPOrHTTPS() || |
| input.current_url().SchemeIs(url::kFileScheme)) && |
| client->IsLensEnabled() && client->AreLensEntrypointsVisible(); |
| } |
| |
| // - Show on non-NTP depending on finch param passed in via |
| // `enabled_non_ntp` |
| // - Show on NTP depending on finch param passed in via `enabled_ntp` |
| // - Show only if corresponding starter pack is enabled. `starter_pack_id` |
| // is `nullopt` when the action is not associated with a starter pack. |
| static bool ToolbeltActionEligible(const AutocompleteInput& input, |
| AutocompleteProviderClient* client, |
| bool enabled_non_ntp, |
| bool enabled_ntp, |
| std::optional<int> starter_pack_id) { |
| // Only show on NTP if the NTP param is enabled. |
| if (!enabled_ntp && input.current_page_classification() == |
| metrics::OmniboxEventProto:: |
| INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS) { |
| return false; |
| } |
| |
| // Only show on non-NTP if the non-NTP param is enabled. |
| if (!enabled_non_ntp && |
| input.current_page_classification() != |
| metrics::OmniboxEventProto:: |
| INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS) { |
| return false; |
| } |
| |
| // If it's a starterpack action, the starterpack must be enabled. |
| if (starter_pack_id.has_value()) { |
| auto* turl_service = client->GetTemplateURLService(); |
| const TemplateURL* turl = |
| turl_service->FindStarterPackTemplateURL(starter_pack_id.value()); |
| if (!turl || turl->is_active() != TemplateURLData::ActiveStatus::kTrue) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| // Return the toolbelt actions that are eligible. |
| std::vector<scoped_refptr<OmniboxAction>> GetToolbeltActions( |
| const AutocompleteInput& input, |
| const TemplateURLService* turl_service) const { |
| CHECK(toolbelt); |
| std::vector<scoped_refptr<OmniboxAction>> actions = {}; |
| |
| if (toolbelt_lens) { |
| // If there is no query yet, trigger the overlay CSB so the user can start |
| // creating their input (text & page selection). Otherwise, if the user |
| // has already formed a textual input, bypass the overlay CSB and trigger |
| // the lens side panel directly. This will unfortunately prevent the user |
| // from making a page selection. Treat on focus inputs like empty inputs; |
| // it's unlikely the user wants to query with the page URL. |
| if (input.IsZeroSuggest() || input.text().empty()) { |
| actions.push_back( |
| base::MakeRefCounted<ContextualSearchOpenLensAction>()); |
| } else { |
| GURL url = |
| ComputeDestinationUrlForLensQueryText(input.text(), turl_service); |
| actions.push_back( |
| base::MakeRefCounted<ContextualSearchFulfillmentAction>( |
| url, AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false)); |
| } |
| } |
| if (toolbelt_ai_mode) { |
| actions.push_back(base::MakeRefCounted<StarterPackAiModeAction>()); |
| } |
| if (toolbelt_history) { |
| actions.push_back(base::MakeRefCounted<StarterPackHistoryAction>()); |
| } |
| if (toolbelt_bookmarks) { |
| actions.push_back(base::MakeRefCounted<StarterPackBookmarksAction>()); |
| } |
| if (toolbelt_tabs) { |
| actions.push_back(base::MakeRefCounted<StarterPackTabsAction>()); |
| } |
| |
| // `toolbelt` should be set false if it would be empty. |
| CHECK(!actions.empty()); |
| return actions; |
| } |
| |
| bool toolbelt; |
| bool toolbelt_lens; |
| bool toolbelt_ai_mode; |
| bool toolbelt_history; |
| bool toolbelt_bookmarks; |
| bool toolbelt_tabs; |
| bool lens_entry_match; |
| bool page_verbatim; |
| bool page_suggestions; |
| }; |
| |
| } // namespace |
| |
| void ContextualSearchProvider::Start(const AutocompleteInput& input, |
| bool minimal_changes) { |
| TRACE_EVENT0("omnibox", "ContextualSearchProvider::Start"); |
| // Clear the cached results to remove the page search action matches. Also, |
| // matches the behavior of the `ZeroSuggestProvider`. |
| Stop(AutocompleteStopReason::kClobbered); |
| |
| // Determine keyword (may be nullptr, a starter pack, or some other keyword). |
| AutocompleteInput keyword_input = input; |
| const TemplateURL* template_url = |
| input.prefer_keyword() |
| ? AutocompleteInput::GetSubstitutingTemplateURLForInput( |
| client()->GetTemplateURLService(), &keyword_input) |
| : nullptr; |
| |
| const EligibleMatchesAndActions eligibility(input, template_url, client()); |
| |
| if (eligibility.toolbelt) { |
| AddToolbeltMatch(keyword_input, |
| eligibility.GetToolbeltActions( |
| keyword_input, client()->GetTemplateURLService())); |
| } |
| |
| if (eligibility.lens_entry_match) { |
| AddLensEntrypointMatch(keyword_input); |
| } |
| |
| if (eligibility.page_verbatim) { |
| // `template_url` can't be nullptr here; see `page_verbatim` assignment. |
| DCHECK(template_url); |
| input_keyword_ = template_url->keyword(); |
| AddDefaultVerbatimMatch(input); |
| } |
| |
| if (eligibility.page_suggestions) { |
| done_ = false; |
| AddDefaultVerbatimMatch(keyword_input); |
| StartSuggestRequest(std::move(keyword_input)); |
| } |
| } |
| |
| void ContextualSearchProvider::Stop(AutocompleteStopReason stop_reason) { |
| // If the stop is due to user inactivity, the request will continue so the |
| // suggestions can be shown when they are ready. |
| if (stop_reason == AutocompleteStopReason::kInactivity) { |
| return; |
| } |
| AutocompleteProvider::Stop(stop_reason); |
| lens_suggest_inputs_subscription_ = {}; |
| loader_.reset(); |
| input_keyword_.clear(); |
| } |
| |
| void ContextualSearchProvider::AddProviderInfo( |
| ProvidersInfo* provider_info) const { |
| BaseSearchProvider::AddProviderInfo(provider_info); |
| if (!matches().empty()) { |
| provider_info->back().set_times_returned_results_in_session(1); |
| } |
| } |
| |
| bool ContextualSearchProvider::HasToolbeltLensAction() const { |
| return std::ranges::any_of(matches_, [](const auto& match) { |
| return match.IsToolbelt() && |
| match.HasAction(OmniboxActionId::CONTEXTUAL_SEARCH_OPEN_LENS); |
| }); |
| } |
| |
| ContextualSearchProvider::ContextualSearchProvider( |
| AutocompleteProviderClient* client, |
| AutocompleteProviderListener* listener) |
| : BaseSearchProvider(AutocompleteProvider::TYPE_CONTEXTUAL_SEARCH, client) { |
| AddListener(listener); |
| } |
| |
| ContextualSearchProvider::~ContextualSearchProvider() = default; |
| |
| bool ContextualSearchProvider::ShouldAppendExtraParams( |
| const SearchSuggestionParser::SuggestResult& result) const { |
| // We always use the default provider for search, so append the params. |
| return true; |
| } |
| |
| void ContextualSearchProvider::StartSuggestRequest(AutocompleteInput input) { |
| if (AreLensSuggestInputsReady(input.lens_overlay_suggest_inputs()) || |
| !omnibox_feature_configs::ContextualSearch::Get() |
| .csp_async_suggest_inputs) { |
| // If the suggest inputs are ready, make the suggest request immediately. |
| // Also, skip the async wait if the feature is disabled. |
| MakeSuggestRequest(std::move(input)); |
| return; |
| } |
| |
| // Wait for the suggest inputs to be generated and then make the suggest |
| // request. Safe to use base::Unretained(this) because the subscription is |
| // reset and cancelled if this provider is destroyed. |
| lens_suggest_inputs_subscription_ = client()->GetLensSuggestInputsWhenReady( |
| base::BindOnce(&ContextualSearchProvider::OnLensSuggestInputsReady, |
| base::Unretained(this), std::move(input))); |
| } |
| |
| void ContextualSearchProvider::OnLensSuggestInputsReady( |
| AutocompleteInput input, |
| std::optional<lens::proto::LensOverlaySuggestInputs> lens_suggest_inputs) { |
| CHECK(!AreLensSuggestInputsReady(input.lens_overlay_suggest_inputs())); |
| if (lens_suggest_inputs) { |
| input.set_lens_overlay_suggest_inputs(*lens_suggest_inputs); |
| } |
| MakeSuggestRequest(std::move(input)); |
| } |
| |
| void ContextualSearchProvider::MakeSuggestRequest(AutocompleteInput input) { |
| TemplateURLRef::SearchTermsArgs search_terms_args; |
| |
| // TODO(crbug.com/404608703): Consider new types or taking from `input`. |
| search_terms_args.page_classification = |
| metrics::OmniboxEventProto::CONTEXTUAL_SEARCHBOX; |
| search_terms_args.request_source = |
| SearchTermsData::RequestSource::LENS_OVERLAY; |
| |
| search_terms_args.focus_type = input.focus_type(); |
| search_terms_args.current_page_url = input.current_url().spec(); |
| search_terms_args.lens_overlay_suggest_inputs = |
| input.lens_overlay_suggest_inputs(); |
| |
| // Make the request and store the loader to keep it alive. Destroying the |
| // loader will cancel the request. Safe to use base::Unretained(this) because |
| // the loader is reset and destroyed if this provider is destroyed. |
| loader_ = |
| client() |
| ->GetRemoteSuggestionsService(/*create_if_necessary=*/true) |
| ->StartZeroPrefixSuggestionsRequest( |
| RemoteRequestType::kZeroSuggest, client()->IsOffTheRecord(), |
| client()->GetTemplateURLService()->GetDefaultSearchProvider(), |
| search_terms_args, |
| client()->GetTemplateURLService()->search_terms_data(), |
| base::BindOnce(&ContextualSearchProvider::SuggestRequestCompleted, |
| base::Unretained(this), std::move(input))); |
| } |
| |
| void ContextualSearchProvider::SuggestRequestCompleted( |
| AutocompleteInput input, |
| const network::SimpleURLLoader* source, |
| const int response_code, |
| std::unique_ptr<std::string> response_body) { |
| DCHECK(!done_); |
| DCHECK_EQ(loader_.get(), source); |
| |
| if (response_code != 200) { |
| loader_.reset(); |
| done_ = true; |
| return; |
| } |
| |
| // Some match must be available in order to stay in keyword mode, |
| // but an empty result set is possible. The default match will |
| // always be added first for a consistent keyword experience. |
| matches_.clear(); |
| suggestion_groups_map_.clear(); |
| AddDefaultVerbatimMatch(input); |
| |
| // Note: Queries are not yet supported. If it is kept, the current behavior |
| // will be to mismatch between input text and query empty string, failing |
| // the parse early in SearchSuggestionParser::ParseSuggestResults. |
| input.UpdateText(u"", 0u, {}); |
| |
| SearchSuggestionParser::Results results; |
| if (!ParseRemoteResponse(SearchSuggestionParser::ExtractJsonData( |
| source, std::move(response_body)), |
| client(), input, &results)) { |
| loader_.reset(); |
| done_ = true; |
| return; |
| } |
| |
| loader_.reset(); |
| done_ = true; |
| |
| // Convert the results into |matches_| and notify the listeners. |
| ConvertSuggestResultsToAutocompleteMatches(results, input); |
| NotifyListeners(/*updated_matches=*/true); |
| } |
| |
| void ContextualSearchProvider::ConvertSuggestResultsToAutocompleteMatches( |
| const SearchSuggestionParser::Results& results, |
| const AutocompleteInput& input) { |
| const TemplateURL* template_url = GetKeywordTemplateURL(); |
| if (!template_url) { |
| return; |
| } |
| // Add all the SuggestResults to the map. We display all ZeroSuggest search |
| // suggestions as unbolded. |
| MatchMap map; |
| for (size_t i = 0; i < results.suggest_results.size(); ++i) { |
| AddMatchToMap(results.suggest_results[i], input, template_url, |
| client()->GetTemplateURLService()->search_terms_data(), i, |
| /*mark_as_deletable*/ false, /*in_keyword_mode=*/true, &map); |
| } |
| |
| const int num_query_results = map.size(); |
| const int num_nav_results = results.navigation_results.size(); |
| const int num_results = num_query_results + num_nav_results; |
| |
| if (num_results == 0) { |
| return; |
| } |
| |
| for (MatchMap::const_iterator it(map.begin()); it != map.end(); ++it) { |
| matches_.push_back(it->second); |
| } |
| |
| const SearchSuggestionParser::NavigationResults& nav_results( |
| results.navigation_results); |
| for (const auto& nav_result : nav_results) { |
| matches_.push_back( |
| ZeroSuggestProvider::NavigationToMatch(this, client(), nav_result)); |
| } |
| |
| // Update the suggestion groups information from the server response. |
| for (const auto& entry : results.suggestion_groups_map) { |
| suggestion_groups_map_[entry.first].MergeFrom(entry.second); |
| } |
| } |
| |
| void ContextualSearchProvider::AddLensEntrypointMatch( |
| const AutocompleteInput& input) { |
| // This match is effectively a pedal that doesn't require any query matching. |
| // Relevance depends on the page class, and selecting an appropriate score is |
| // necessary to avoid downstream conflicts in grouping framework sort order. |
| AutocompleteMatch match( |
| this, |
| omnibox::IsSearchResultsPage(input.current_page_classification()) |
| ? omnibox::kContextualActionZeroSuggestRelevanceLow |
| : omnibox::kContextualActionZeroSuggestRelevance, |
| false, AutocompleteMatchType::PEDAL); |
| match.transition = ui::PAGE_TRANSITION_GENERATED; |
| match.suggest_type = omnibox::SuggestType::TYPE_NATIVE_CHROME; |
| match.suggestion_group_id = omnibox::GroupId::GROUP_CONTEXTUAL_SEARCH_ACTION; |
| |
| // Lens invocation action with secondary text that shows URL host. |
| match.takeover_action = |
| base::MakeRefCounted<ContextualSearchOpenLensAction>(); |
| match.contents = |
| base::UTF8ToUTF16(url_formatter::StripWWW(input.current_url().host())); |
| if (!match.contents.empty()) { |
| match.contents_class = {{0, ACMatchClassification::DIM}}; |
| } |
| match.description = match.takeover_action->GetLabelStrings().hint; |
| if (!match.description.empty()) { |
| match.description_class = {{0, ACMatchClassification::NONE}}; |
| } |
| match.fill_into_edit = match.description; |
| matches_.push_back(match); |
| } |
| |
| void ContextualSearchProvider::AddDefaultVerbatimMatch( |
| const AutocompleteInput& input) { |
| const TemplateURL* template_url = GetKeywordTemplateURL(); |
| std::u16string text = base::CollapseWhitespace(input.text(), false); |
| |
| AutocompleteMatch match(this, kDefaultVerbatimMatchRelevance, false, |
| AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED); |
| if (text.empty()) { |
| // Inert/static keyword mode helper text match for empty input. This match |
| // doesn't commit the omnibox when selected, it just stands in to inform the |
| // user about how to use the keyword scope they've entered. |
| match.contents = |
| l10n_util::GetStringUTF16(IDS_STARTER_PACK_PAGE_EMPTY_QUERY_MATCH_TEXT); |
| match.contents_class = {{0, ACMatchClassification::DIM}}; |
| |
| // These are necessary to avoid the omnibox dropping out of keyword mode. |
| if (template_url) { |
| match.keyword = template_url->keyword(); |
| } |
| match.transition = ui::PAGE_TRANSITION_KEYWORD; |
| match.allowed_to_be_default_match = true; |
| } else { |
| // Verbatim search suggestion, using the keyword `template_url` (@page) |
| // instead of default search engine, for a more consistent keyword UX. |
| // Note, the SUBTYPE_CONTEXTUAL_SEARCH subtype will cause this match |
| // to be fulfilled via ContextualSearchFulfillmentAction `takeover_action`. |
| SearchSuggestionParser::SuggestResult verbatim( |
| /*suggestion=*/text, |
| /*type=*/AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, |
| /*suggest_type=*/omnibox::TYPE_NATIVE_CHROME, |
| /*subtypes=*/{omnibox::SUBTYPE_CONTEXTUAL_SEARCH}, |
| /*from_keyword=*/true, |
| /*navigational_intent=*/omnibox::NAV_INTENT_NONE, |
| /*relevance=*/kDefaultVerbatimMatchRelevance, |
| /*relevance_from_server=*/false, |
| /*input_text=*/text); |
| match = CreateSearchSuggestion( |
| this, input, /*in_keyword_mode=*/true, verbatim, template_url, |
| client()->GetTemplateURLService()->search_terms_data(), |
| /*accepted_suggestion=*/0, ShouldAppendExtraParams(verbatim)); |
| } |
| matches_.push_back(match); |
| } |
| |
| void ContextualSearchProvider::AddToolbeltMatch( |
| const AutocompleteInput& input, |
| std::vector<scoped_refptr<OmniboxAction>> actions) { |
| AutocompleteMatch match(this, omnibox::kToolbeltRelevance, false, |
| AutocompleteMatchType::NULL_RESULT_MESSAGE); |
| match.transition = ui::PAGE_TRANSITION_GENERATED; |
| match.suggest_type = omnibox::SuggestType::TYPE_NATIVE_CHROME; |
| match.suggestion_group_id = omnibox::GroupId::GROUP_SEARCH_TOOLBELT; |
| match.fill_into_edit = input.text(); |
| |
| match.description = l10n_util::GetStringUTF16(IDS_OMNIBOX_TOOLBELT_LABEL); |
| if (!match.description.empty()) { |
| match.description_class = {{0, ACMatchClassification::TOOLBELT}}; |
| } |
| |
| match.actions = actions; |
| matches_.push_back(match); |
| } |
| |
| const TemplateURL* ContextualSearchProvider::GetKeywordTemplateURL() const { |
| return client()->GetTemplateURLService()->GetTemplateURLForKeyword( |
| input_keyword_); |
| } |