| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/webui/realbox/realbox_handler.h" |
| |
| #include "base/base64.h" |
| #include "base/base64url.h" |
| #include "base/containers/contains.h" |
| #include "base/functional/bind.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/metrics/user_metrics_action.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/branding_buildflags.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h" |
| #include "chrome/browser/autocomplete/chrome_autocomplete_provider_client.h" |
| #include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h" |
| #include "chrome/browser/bookmarks/bookmark_model_factory.h" |
| #include "chrome/browser/predictors/autocomplete_action_predictor.h" |
| #include "chrome/browser/predictors/autocomplete_action_predictor_factory.h" |
| #include "chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service.h" |
| #include "chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/search_engines/template_url_service_factory.h" |
| #include "chrome/browser/ui/bookmarks/bookmark_stats.h" |
| #include "chrome/browser/ui/omnibox/omnibox_pedal_implementations.h" |
| #include "chrome/browser/ui/search/omnibox_utils.h" |
| #include "chrome/browser/ui/webui/metrics_reporter/metrics_reporter.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chrome/grit/new_tab_page_resources.h" |
| #include "components/bookmarks/browser/bookmark_model.h" |
| #include "components/navigation_metrics/navigation_metrics.h" |
| #include "components/omnibox/browser/autocomplete_classifier.h" |
| #include "components/omnibox/browser/autocomplete_input.h" |
| #include "components/omnibox/browser/autocomplete_match.h" |
| #include "components/omnibox/browser/autocomplete_match_type.h" |
| #include "components/omnibox/browser/autocomplete_result.h" |
| #include "components/omnibox/browser/omnibox_client.h" |
| #include "components/omnibox/browser/omnibox_controller.h" |
| #include "components/omnibox/browser/omnibox_edit_model.h" |
| #include "components/omnibox/browser/omnibox_event_global_tracker.h" |
| #include "components/omnibox/browser/omnibox_log.h" |
| #include "components/omnibox/browser/omnibox_prefs.h" |
| #include "components/omnibox/browser/search_suggestion_parser.h" |
| #include "components/omnibox/browser/vector_icons.h" |
| #include "components/omnibox/common/omnibox_features.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/profile_metrics/browser_profile_type.h" |
| #include "components/search/ntp_features.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "components/sessions/content/session_tab_helper.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "components/variations/variations_client.h" |
| #include "components/vector_icons/vector_icons.h" |
| #include "content/public/browser/web_ui_data_source.h" |
| #include "net/cookies/cookie_util.h" |
| #include "realbox_handler.h" |
| #include "third_party/metrics_proto/omnibox_focus_type.pb.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/webui/resource_path.h" |
| #include "ui/base/webui/web_ui_util.h" |
| #include "ui/base/window_open_disposition_utils.h" |
| #include "ui/resources/grit/webui_resources.h" |
| |
| namespace { |
| |
| constexpr char kAnswerCurrencyIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/currency.svg"; |
| constexpr char kAnswerDefaultIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/default.svg"; |
| constexpr char kAnswerDictionaryIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/definition.svg"; |
| constexpr char kAnswerFinanceIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/finance.svg"; |
| constexpr char kAnswerSunriseIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/sunrise.svg"; |
| constexpr char kAnswerTranslationIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/translation.svg"; |
| constexpr char kAnswerWhenIsIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/when_is.svg"; |
| constexpr char kBookmarkIconResourceName[] = |
| "//resources/images/icon_bookmark.svg"; |
| constexpr char kCalculatorIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/calculator.svg"; |
| constexpr char kChromeProductIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/chrome_product.svg"; |
| constexpr char kClockIconResourceName[] = "//resources/images/icon_clock.svg"; |
| constexpr char kDinoIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/dino.svg"; |
| constexpr char kDriveDocsIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/drive_docs.svg"; |
| constexpr char kDriveFolderIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/drive_folder.svg"; |
| constexpr char kDriveFormIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/drive_form.svg"; |
| constexpr char kDriveImageIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/drive_image.svg"; |
| constexpr char kDriveLogoIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/drive_logo.svg"; |
| constexpr char kDrivePdfIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/drive_pdf.svg"; |
| constexpr char kDriveSheetsIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/drive_sheets.svg"; |
| constexpr char kDriveSlidesIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/drive_slides.svg"; |
| constexpr char kDriveVideoIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/drive_video.svg"; |
| constexpr char kExtensionAppIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/extension_app.svg"; |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| constexpr char kGoogleCalendarIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/calendar.svg"; |
| constexpr char kGoogleGIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/google_g.svg"; |
| constexpr char kGoogleKeepNoteIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/note.svg"; |
| constexpr char kGoogleSitesIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/sites.svg"; |
| #endif |
| constexpr char kIncognitoIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/incognito.svg"; |
| constexpr char kJourneysIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/journeys.svg"; |
| constexpr char kPageIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/page.svg"; |
| constexpr char kPedalsIconResourceName[] = "//theme/current-channel-logo"; |
| constexpr char kSearchIconResourceName[] = "//resources/images/icon_search.svg"; |
| constexpr char kTabIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/tab.svg"; |
| constexpr char kTrendingUpIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/trending_up.svg"; |
| |
| #if BUILDFLAG(IS_MAC) |
| constexpr char kMacShareIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/mac_share.svg"; |
| #elif BUILDFLAG(IS_WIN) |
| constexpr char kWinShareIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/win_share.svg"; |
| #else |
| constexpr char kShareIconResourceName[] = |
| "//resources/cr_components/omnibox/icons/share.svg"; |
| #endif |
| |
| base::flat_map<int32_t, omnibox::mojom::SuggestionGroupPtr> |
| CreateSuggestionGroupsMap( |
| const AutocompleteResult& result, |
| PrefService* prefs, |
| const omnibox::GroupConfigMap& suggestion_groups_map) { |
| base::flat_map<int32_t, omnibox::mojom::SuggestionGroupPtr> result_map; |
| for (const auto& pair : suggestion_groups_map) { |
| omnibox::mojom::SuggestionGroupPtr suggestion_group = |
| omnibox::mojom::SuggestionGroup::New(); |
| suggestion_group->header = base::UTF8ToUTF16(pair.second.header_text()); |
| suggestion_group->side_type = |
| static_cast<omnibox::mojom::SideType>(pair.second.side_type()); |
| suggestion_group->hidden = |
| result.IsSuggestionGroupHidden(prefs, pair.first); |
| suggestion_group->show_group_a11y_label = l10n_util::GetStringFUTF16( |
| IDS_ACC_HEADER_SHOW_SUGGESTIONS_BUTTON, suggestion_group->header); |
| suggestion_group->hide_group_a11y_label = l10n_util::GetStringFUTF16( |
| IDS_ACC_HEADER_HIDE_SUGGESTIONS_BUTTON, suggestion_group->header); |
| |
| result_map.emplace(static_cast<int>(pair.first), |
| std::move(suggestion_group)); |
| } |
| return result_map; |
| } |
| |
| absl::optional<std::u16string> GetAdditionalText( |
| const SuggestionAnswer::ImageLine& line) { |
| if (line.additional_text()) { |
| const auto additional_text = line.additional_text()->text(); |
| if (!additional_text.empty()) |
| return additional_text; |
| } |
| return absl::nullopt; |
| } |
| |
| std::u16string ImageLineToString16(const SuggestionAnswer::ImageLine& line) { |
| std::vector<std::u16string> text; |
| for (const auto& text_field : line.text_fields()) { |
| text.push_back(text_field.text()); |
| } |
| const auto& additional_text = GetAdditionalText(line); |
| if (additional_text) { |
| text.push_back(additional_text.value()); |
| } |
| // TODO(crbug.com/1130372): Use placeholders or a l10n-friendly way to |
| // construct this string instead of concatenation. This currently only happens |
| // for stock ticker symbols. |
| return base::JoinString(text, u" "); |
| } |
| |
| bool MatchHasSideTypeAndRenderType( |
| const AutocompleteMatch& match, |
| omnibox::GroupConfig_SideType side_type, |
| omnibox::GroupConfig_RenderType render_type, |
| const omnibox::GroupConfigMap& suggestion_groups_map) { |
| omnibox::GroupId group_id = |
| match.suggestion_group_id.value_or(omnibox::GROUP_INVALID); |
| return base::Contains(suggestion_groups_map, group_id) && |
| suggestion_groups_map.at(group_id).side_type() == side_type && |
| suggestion_groups_map.at(group_id).render_type() == render_type; |
| } |
| |
| std::u16string GetAdditionalA11yMessage(const AutocompleteMatch& match, |
| RealboxHandler::FocusState state) { |
| switch (state) { |
| case RealboxHandler::FocusState::kFocusedMatch: { |
| if (match.has_tab_match.value_or(false) && |
| base::FeatureList::IsEnabled(omnibox::kNtpRealboxPedals)) { |
| return l10n_util::GetStringUTF16(IDS_ACC_TAB_SWITCH_SUFFIX); |
| } |
| const OmniboxAction* action = match.GetActionAt(0u); |
| if (action) { |
| return action->GetLabelStrings().accessibility_suffix; |
| } |
| if (match.SupportsDeletion()) { |
| return l10n_util::GetStringUTF16(IDS_ACC_REMOVE_SUGGESTION_SUFFIX); |
| } |
| break; |
| } |
| case RealboxHandler::FocusState::kFocusedButtonRemoveSuggestion: |
| return l10n_util::GetStringUTF16( |
| IDS_ACC_REMOVE_SUGGESTION_FOCUSED_PREFIX); |
| default: |
| NOTREACHED(); |
| break; |
| } |
| return std::u16string(); |
| } |
| |
| std::vector<omnibox::mojom::AutocompleteMatchPtr> CreateAutocompleteMatches( |
| const AutocompleteResult& result, |
| bookmarks::BookmarkModel* bookmark_model, |
| const omnibox::GroupConfigMap& suggestion_groups_map) { |
| std::vector<omnibox::mojom::AutocompleteMatchPtr> matches; |
| int line = 0; |
| for (const AutocompleteMatch& match : result) { |
| // Skip the primary column horizontal matches. This check guards against |
| // this unexpected scenario as the UI expects the primary column matches to |
| // be vertical ones. |
| if (MatchHasSideTypeAndRenderType( |
| match, omnibox::GroupConfig_SideType_DEFAULT_PRIMARY, |
| omnibox::GroupConfig_RenderType_HORIZONTAL, |
| suggestion_groups_map)) { |
| continue; |
| } |
| |
| // Skip the secondary column horizontal matches that are not entities or do |
| // not have images. This check guards against this unexpected scenario as |
| // the UI expects the secondary column horizontal matches to be entity |
| // suggestions with images. |
| if (MatchHasSideTypeAndRenderType( |
| match, omnibox::GroupConfig_SideType_SECONDARY, |
| omnibox::GroupConfig_RenderType_HORIZONTAL, |
| suggestion_groups_map) && |
| (match.type != AutocompleteMatchType::SEARCH_SUGGEST_ENTITY || |
| !match.image_url.is_valid())) { |
| continue; |
| } |
| |
| omnibox::mojom::AutocompleteMatchPtr mojom_match = |
| omnibox::mojom::AutocompleteMatch::New(); |
| mojom_match->allowed_to_be_default_match = |
| match.allowed_to_be_default_match; |
| mojom_match->contents = match.contents; |
| for (const auto& contents_class : match.contents_class) { |
| mojom_match->contents_class.push_back( |
| omnibox::mojom::ACMatchClassification::New(contents_class.offset, |
| contents_class.style)); |
| } |
| mojom_match->description = match.description; |
| for (const auto& description_class : match.description_class) { |
| mojom_match->description_class.push_back( |
| omnibox::mojom::ACMatchClassification::New(description_class.offset, |
| description_class.style)); |
| } |
| mojom_match->destination_url = match.destination_url; |
| mojom_match->suggestion_group_id = |
| match.suggestion_group_id.value_or(omnibox::GROUP_INVALID); |
| const bool is_bookmarked = |
| bookmark_model->IsBookmarked(match.destination_url); |
| mojom_match->icon_url = |
| RealboxHandler::AutocompleteMatchVectorIconToResourceName( |
| match.GetVectorIcon(is_bookmarked)); |
| mojom_match->image_dominant_color = match.image_dominant_color; |
| mojom_match->image_url = match.image_url.spec(); |
| mojom_match->fill_into_edit = match.fill_into_edit; |
| mojom_match->inline_autocompletion = match.inline_autocompletion; |
| mojom_match->is_search_type = AutocompleteMatch::IsSearchType(match.type); |
| mojom_match->swap_contents_and_description = |
| match.swap_contents_and_description; |
| mojom_match->type = AutocompleteMatchType::ToString(match.type); |
| mojom_match->supports_deletion = match.SupportsDeletion(); |
| if (match.answer.has_value()) { |
| const auto& additional_text = |
| GetAdditionalText(match.answer->first_line()); |
| mojom_match->answer = omnibox::mojom::SuggestionAnswer::New( |
| additional_text ? base::JoinString( |
| {match.contents, additional_text.value()}, u" ") |
| : match.contents, |
| ImageLineToString16(match.answer->second_line())); |
| mojom_match->image_url = match.ImageUrl().spec(); |
| } |
| mojom_match->is_rich_suggestion = |
| !mojom_match->image_url.empty() || |
| match.type == AutocompleteMatchType::CALCULATOR || |
| (match.answer.has_value()); |
| |
| // The realbox only supports one action and priority is given to the actions |
| // instead of the switch to tab button. |
| if (match.has_tab_match.value_or(false) && |
| base::FeatureList::IsEnabled(omnibox::kNtpRealboxPedals)) { |
| mojom_match->action = omnibox::mojom::Action::New( |
| l10n_util::GetStringUTF16(IDS_ACC_TAB_SWITCH_BUTTON), |
| l10n_util::GetStringUTF16(IDS_OMNIBOX_TAB_SUGGEST_HINT), |
| std::u16string(), kTabIconResourceName); |
| } |
| |
| const OmniboxAction* action = match.GetActionAt(0u); |
| if (action && base::FeatureList::IsEnabled(omnibox::kNtpRealboxPedals)) { |
| const OmniboxAction::LabelStrings& label_strings = |
| action->GetLabelStrings(); |
| mojom_match->action = omnibox::mojom::Action::New( |
| label_strings.accessibility_hint, label_strings.hint, |
| label_strings.suggestion_contents, |
| RealboxHandler::PedalVectorIconToResourceName( |
| action->GetVectorIcon())); |
| } |
| mojom_match->a11y_label = AutocompleteMatchType::ToAccessibilityLabel( |
| match, match.contents, line, 0, |
| GetAdditionalA11yMessage(match, |
| RealboxHandler::FocusState::kFocusedMatch)); |
| |
| mojom_match->remove_button_a11y_label = |
| AutocompleteMatchType::ToAccessibilityLabel( |
| match, match.contents, line, 0, |
| GetAdditionalA11yMessage( |
| match, |
| RealboxHandler::FocusState::kFocusedButtonRemoveSuggestion)); |
| |
| mojom_match->tail_suggest_common_prefix = match.tail_suggest_common_prefix; |
| |
| matches.push_back(std::move(mojom_match)); |
| line++; |
| } |
| return matches; |
| } |
| |
| omnibox::mojom::AutocompleteResultPtr CreateAutocompleteResult( |
| const std::u16string& input, |
| const AutocompleteResult& result, |
| bookmarks::BookmarkModel* bookmark_model, |
| PrefService* prefs) { |
| return omnibox::mojom::AutocompleteResult::New( |
| input, |
| CreateSuggestionGroupsMap(result, prefs, result.suggestion_groups_map()), |
| CreateAutocompleteMatches(result, bookmark_model, |
| result.suggestion_groups_map())); |
| } |
| |
| std::string GetBase64UrlVariations(Profile* profile) { |
| variations::VariationsClient* provider = profile->GetVariationsClient(); |
| |
| variations::mojom::VariationsHeadersPtr headers = |
| provider->GetVariationsHeaders(); |
| if (headers.is_null()) { |
| return std::string(); |
| } |
| const std::string variations_base64 = headers->headers_map.at( |
| variations::mojom::GoogleWebVisibility::FIRST_PARTY); |
| |
| // Variations headers are base64 encoded, however, we're attaching the value |
| // to a URL query parameter so they need to be base64url encoded. |
| std::string variations_decoded; |
| base::Base64Decode(variations_base64, &variations_decoded); |
| |
| std::string variations_base64url; |
| base::Base64UrlEncode(variations_decoded, |
| base::Base64UrlEncodePolicy::OMIT_PADDING, |
| &variations_base64url); |
| |
| return variations_base64url; |
| } |
| |
| // TODO(crbug.com/1431513): Consider inheriting from `ChromeOmniboxClient` |
| // to avoid reimplementation of methods like `OnBookmarkLaunched`. |
| class RealboxOmniboxClient : public OmniboxClient { |
| public: |
| explicit RealboxOmniboxClient(Profile* profile, |
| content::WebContents* web_contents); |
| ~RealboxOmniboxClient() override; |
| |
| // OmniboxClient: |
| std::unique_ptr<AutocompleteProviderClient> CreateAutocompleteProviderClient() |
| override; |
| bool IsPasteAndGoEnabled() const override; |
| SessionID GetSessionID() const override; |
| bookmarks::BookmarkModel* GetBookmarkModel() override; |
| AutocompleteControllerEmitter* GetAutocompleteControllerEmitter() override; |
| TemplateURLService* GetTemplateURLService() override; |
| const AutocompleteSchemeClassifier& GetSchemeClassifier() const override; |
| AutocompleteClassifier* GetAutocompleteClassifier() override; |
| bool ShouldDefaultTypedNavigationsToHttps() const override; |
| int GetHttpsPortForTesting() const override; |
| bool IsUsingFakeHttpsForHttpsUpgradeTesting() const override; |
| gfx::Image GetSizedIcon(const gfx::VectorIcon& vector_icon_type, |
| SkColor vector_icon_color) const override; |
| gfx::Image GetFaviconForPageUrl( |
| const GURL& page_url, |
| FaviconFetchedCallback on_favicon_fetched) override; |
| void OnBookmarkLaunched() override; |
| void OnURLOpenedFromOmnibox(OmniboxLog* log) override; |
| |
| private: |
| raw_ptr<Profile> profile_; |
| raw_ptr<content::WebContents> web_contents_; |
| ChromeAutocompleteSchemeClassifier scheme_classifier_; |
| }; |
| |
| RealboxOmniboxClient::RealboxOmniboxClient(Profile* profile, |
| content::WebContents* web_contents) |
| : profile_(profile), |
| web_contents_(web_contents), |
| scheme_classifier_(ChromeAutocompleteSchemeClassifier(profile)) {} |
| |
| RealboxOmniboxClient::~RealboxOmniboxClient() = default; |
| |
| std::unique_ptr<AutocompleteProviderClient> |
| RealboxOmniboxClient::CreateAutocompleteProviderClient() { |
| return std::make_unique<ChromeAutocompleteProviderClient>(profile_); |
| } |
| |
| bool RealboxOmniboxClient::IsPasteAndGoEnabled() const { |
| return false; |
| } |
| |
| SessionID RealboxOmniboxClient::GetSessionID() const { |
| return sessions::SessionTabHelper::IdForTab(web_contents_); |
| } |
| |
| bookmarks::BookmarkModel* RealboxOmniboxClient::GetBookmarkModel() { |
| return BookmarkModelFactory::GetForBrowserContext(profile_); |
| } |
| |
| AutocompleteControllerEmitter* |
| RealboxOmniboxClient::GetAutocompleteControllerEmitter() { |
| return AutocompleteControllerEmitter::GetForBrowserContext(profile_); |
| } |
| |
| TemplateURLService* RealboxOmniboxClient::GetTemplateURLService() { |
| return TemplateURLServiceFactory::GetForProfile(profile_); |
| } |
| |
| const AutocompleteSchemeClassifier& RealboxOmniboxClient::GetSchemeClassifier() |
| const { |
| return scheme_classifier_; |
| } |
| |
| AutocompleteClassifier* RealboxOmniboxClient::GetAutocompleteClassifier() { |
| return AutocompleteClassifierFactory::GetForProfile(profile_); |
| } |
| |
| bool RealboxOmniboxClient::ShouldDefaultTypedNavigationsToHttps() const { |
| return false; |
| } |
| |
| int RealboxOmniboxClient::GetHttpsPortForTesting() const { |
| return 0; |
| } |
| |
| bool RealboxOmniboxClient::IsUsingFakeHttpsForHttpsUpgradeTesting() const { |
| return false; |
| } |
| |
| gfx::Image RealboxOmniboxClient::GetSizedIcon( |
| const gfx::VectorIcon& vector_icon_type, |
| SkColor vector_icon_color) const { |
| return gfx::Image(); |
| } |
| |
| gfx::Image RealboxOmniboxClient::GetFaviconForPageUrl( |
| const GURL& page_url, |
| FaviconFetchedCallback on_favicon_fetched) { |
| return gfx::Image(); |
| } |
| |
| void RealboxOmniboxClient::OnBookmarkLaunched() { |
| RecordBookmarkLaunch(BookmarkLaunchLocation::kOmnibox, |
| profile_metrics::GetBrowserProfileType(profile_)); |
| } |
| |
| void RealboxOmniboxClient::OnURLOpenedFromOmnibox(OmniboxLog* log) { |
| if (auto* search_prefetch_service = |
| SearchPrefetchServiceFactory::GetForProfile(profile_)) { |
| search_prefetch_service->OnURLOpenedFromOmnibox(log); |
| } |
| predictors::AutocompleteActionPredictorFactory::GetForProfile(profile_) |
| ->OnOmniboxOpenedUrl(*log); |
| } |
| |
| } // namespace |
| |
| // static |
| void RealboxHandler::SetupWebUIDataSource(content::WebUIDataSource* source, |
| Profile* profile) { |
| RealboxHandler::SetupDropdownWebUIDataSource(source, profile); |
| |
| static constexpr webui::LocalizedString kStrings[] = { |
| {"searchBoxHint", IDS_GOOGLE_SEARCH_BOX_EMPTY_HINT_MD}}; |
| source->AddLocalizedStrings(kStrings); |
| |
| source->AddBoolean( |
| "realboxMatchSearchboxTheme", |
| base::FeatureList::IsEnabled(ntp_features::kRealboxMatchSearchboxTheme)); |
| |
| source->AddString( |
| "realboxDefaultIcon", |
| base::FeatureList::IsEnabled(ntp_features::kRealboxUseGoogleGIcon) |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| ? kGoogleGIconResourceName |
| #else |
| ? kSearchIconResourceName |
| #endif |
| : kSearchIconResourceName); |
| |
| source->AddBoolean( |
| "realboxLensSearch", |
| base::FeatureList::IsEnabled(ntp_features::kNtpRealboxLensSearch) && |
| profile->GetPrefs()->GetBoolean(prefs::kLensDesktopNTPSearchEnabled)); |
| source->AddString("realboxLensVariations", GetBase64UrlVariations(profile)); |
| } |
| |
| // static |
| void RealboxHandler::SetupDropdownWebUIDataSource( |
| content::WebUIDataSource* source, |
| Profile* profile) { |
| static constexpr webui::LocalizedString kStrings[] = { |
| {"realboxSeparator", IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR}, |
| {"removeSuggestion", IDS_OMNIBOX_REMOVE_SUGGESTION}, |
| {"hideSuggestions", IDS_TOOLTIP_HEADER_HIDE_SUGGESTIONS_BUTTON}, |
| {"showSuggestions", IDS_TOOLTIP_HEADER_SHOW_SUGGESTIONS_BUTTON}}; |
| source->AddLocalizedStrings(kStrings); |
| |
| source->AddBoolean("roundCorners", base::FeatureList::IsEnabled( |
| ntp_features::kRealboxRoundedCorners)); |
| } |
| |
| // static |
| std::string RealboxHandler::AutocompleteMatchVectorIconToResourceName( |
| const gfx::VectorIcon& icon) { |
| if (icon.name == omnibox::kAnswerCurrencyIcon.name) { |
| return kAnswerCurrencyIconResourceName; |
| } else if (icon.name == omnibox::kAnswerDefaultIcon.name) { |
| return kAnswerDefaultIconResourceName; |
| } else if (icon.name == omnibox::kAnswerDictionaryIcon.name) { |
| return kAnswerDictionaryIconResourceName; |
| } else if (icon.name == omnibox::kAnswerFinanceIcon.name) { |
| return kAnswerFinanceIconResourceName; |
| } else if (icon.name == omnibox::kAnswerSunriseIcon.name) { |
| return kAnswerSunriseIconResourceName; |
| } else if (icon.name == omnibox::kAnswerTranslationIcon.name) { |
| return kAnswerTranslationIconResourceName; |
| } else if (icon.name == omnibox::kAnswerWhenIsIcon.name) { |
| return kAnswerWhenIsIconResourceName; |
| } else if (icon.name == omnibox::kBookmarkIcon.name) { |
| return kBookmarkIconResourceName; |
| } else if (icon.name == omnibox::kCalculatorIcon.name) { |
| return kCalculatorIconResourceName; |
| } else if (icon.name == omnibox::kClockIcon.name) { |
| return kClockIconResourceName; |
| } else if (icon.name == omnibox::kDriveDocsIcon.name) { |
| return kDriveDocsIconResourceName; |
| } else if (icon.name == omnibox::kDriveFolderIcon.name) { |
| return kDriveFolderIconResourceName; |
| } else if (icon.name == omnibox::kDriveFormsIcon.name) { |
| return kDriveFormIconResourceName; |
| } else if (icon.name == omnibox::kDriveImageIcon.name) { |
| return kDriveImageIconResourceName; |
| } else if (icon.name == omnibox::kDriveLogoIcon.name) { |
| return kDriveLogoIconResourceName; |
| } else if (icon.name == omnibox::kDrivePdfIcon.name) { |
| return kDrivePdfIconResourceName; |
| } else if (icon.name == omnibox::kDriveSheetsIcon.name) { |
| return kDriveSheetsIconResourceName; |
| } else if (icon.name == omnibox::kDriveSlidesIcon.name) { |
| return kDriveSlidesIconResourceName; |
| } else if (icon.name == omnibox::kDriveVideoIcon.name) { |
| return kDriveVideoIconResourceName; |
| } else if (icon.name == omnibox::kExtensionAppIcon.name) { |
| return kExtensionAppIconResourceName; |
| } else if (icon.name == omnibox::kJourneysIcon.name) { |
| return kJourneysIconResourceName; |
| } else if (icon.name == omnibox::kPageIcon.name) { |
| return kPageIconResourceName; |
| } else if (icon.name == omnibox::kPedalIcon.name) { |
| return kPedalsIconResourceName; |
| } else if (icon.name == omnibox::kProductIcon.name) { |
| return kChromeProductIconResourceName; |
| } else if (icon.name == vector_icons::kSearchIcon.name) { |
| return kSearchIconResourceName; |
| } else if (icon.name == omnibox::kTrendingUpIcon.name) { |
| return kTrendingUpIconResourceName; |
| } else if (icon.is_empty()) { |
| return ""; // An empty resource name is effectively a blank icon. |
| } else { |
| NOTREACHED() |
| << "Every vector icon returned by AutocompleteMatch::GetVectorIcon " |
| "must have an equivalent SVG resource for the NTP Realbox. " |
| "icon.name: '" |
| << icon.name << "'"; |
| } |
| return ""; |
| } |
| |
| // static |
| std::string RealboxHandler::PedalVectorIconToResourceName( |
| const gfx::VectorIcon& icon) { |
| if (icon.name == omnibox::kSwitchIcon.name) { |
| return kTabIconResourceName; |
| } |
| if (icon.name == omnibox::kDinoIcon.name) { |
| return kDinoIconResourceName; |
| } |
| if (icon.name == omnibox::kDriveFormsIcon.name) { |
| return kDriveFormIconResourceName; |
| } |
| if (icon.name == omnibox::kDriveDocsIcon.name) { |
| return kDriveDocsIconResourceName; |
| } |
| if (icon.name == omnibox::kDriveSheetsIcon.name) { |
| return kDriveSheetsIconResourceName; |
| } |
| if (icon.name == omnibox::kDriveSlidesIcon.name) { |
| return kDriveSlidesIconResourceName; |
| } |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| if (icon.name == vector_icons::kGoogleCalendarIcon.name) { |
| return kGoogleCalendarIconResourceName; |
| } |
| if (icon.name == vector_icons::kGoogleKeepNoteIcon.name) { |
| return kGoogleKeepNoteIconResourceName; |
| } |
| if (icon.name == vector_icons::kGoogleSitesIcon.name) { |
| return kGoogleSitesIconResourceName; |
| } |
| if (icon.name == vector_icons::kGoogleSuperGIcon.name) { |
| return kGoogleGIconResourceName; |
| } |
| #endif |
| if (icon.name == omnibox::kIncognitoIcon.name) { |
| return kIncognitoIconResourceName; |
| } |
| if (icon.name == omnibox::kJourneysIcon.name) { |
| return kJourneysIconResourceName; |
| } |
| if (icon.name == omnibox::kPedalIcon.name) { |
| return kPedalsIconResourceName; |
| } |
| #if BUILDFLAG(IS_MAC) |
| if (icon.name == omnibox::kShareMacIcon.name) { |
| return kMacShareIconResourceName; |
| } |
| #elif BUILDFLAG(IS_WIN) |
| if (icon.name == omnibox::kShareWinIcon.name) { |
| return kWinShareIconResourceName; |
| } |
| #else |
| if (icon.name == omnibox::kShareIcon.name) { |
| return kShareIconResourceName; |
| } |
| #endif |
| NOTREACHED() << "Every vector icon returned by OmniboxAction::GetVectorIcon " |
| "must have an equivalent SVG resource for the NTP Realbox. " |
| "icon.name: '" |
| << icon.name << "'"; |
| return ""; |
| } |
| |
| RealboxHandler::RealboxHandler( |
| mojo::PendingReceiver<omnibox::mojom::PageHandler> pending_page_handler, |
| Profile* profile, |
| content::WebContents* web_contents, |
| MetricsReporter* metrics_reporter) |
| : profile_(profile), |
| web_contents_(web_contents), |
| metrics_reporter_(metrics_reporter), |
| page_handler_(this, std::move(pending_page_handler)) { |
| controller_emitter_observation_.Observe( |
| AutocompleteControllerEmitter::GetForBrowserContext(profile_)); |
| |
| controller_ = std::make_unique<OmniboxController>( |
| /*edit_model_delegate=*/this, |
| std::make_unique<AutocompleteController>( |
| std::make_unique<ChromeAutocompleteProviderClient>(profile_), |
| AutocompleteClassifier::DefaultOmniboxProviders()), |
| std::make_unique<RealboxOmniboxClient>(profile_, web_contents_)); |
| } |
| |
| RealboxHandler::~RealboxHandler() = default; |
| |
| void RealboxHandler::SetPage( |
| mojo::PendingRemote<omnibox::mojom::Page> pending_page) { |
| page_.Bind(std::move(pending_page)); |
| } |
| |
| void RealboxHandler::QueryAutocomplete(const std::u16string& input, |
| bool prevent_inline_autocomplete) { |
| // TODO(tommycli): We use the input being empty as a signal we are requesting |
| // on-focus suggestions. It would be nice if we had a more explicit signal. |
| bool is_on_focus = input.empty(); |
| |
| // Early exit if a query is already in progress for on focus inputs. |
| if (!autocomplete_controller()->done() && is_on_focus) { |
| return; |
| } |
| |
| if (time_user_first_modified_realbox_.is_null() && !is_on_focus) |
| time_user_first_modified_realbox_ = base::TimeTicks::Now(); |
| |
| AutocompleteInput autocomplete_input( |
| input, metrics::OmniboxEventProto::NTP_REALBOX, |
| ChromeAutocompleteSchemeClassifier(profile_)); |
| autocomplete_input.set_focus_type( |
| is_on_focus ? metrics::OmniboxFocusType::INTERACTION_FOCUS |
| : metrics::OmniboxFocusType::INTERACTION_DEFAULT); |
| autocomplete_input.set_prevent_inline_autocomplete( |
| prevent_inline_autocomplete); |
| |
| // We do not want keyword matches for the NTP realbox, which has no UI |
| // facilities to support them. |
| autocomplete_input.set_prefer_keyword(false); |
| autocomplete_input.set_allow_exact_keyword_match(false); |
| |
| autocomplete_controller()->Start(autocomplete_input); |
| } |
| |
| void RealboxHandler::StopAutocomplete(bool clear_result) { |
| autocomplete_controller()->Stop(clear_result); |
| |
| if (clear_result) |
| time_user_first_modified_realbox_ = base::TimeTicks(); |
| } |
| |
| void RealboxHandler::OpenAutocompleteMatch( |
| uint8_t line, |
| const GURL& url, |
| bool are_matches_showing, |
| base::TimeDelta time_elapsed_since_last_focus, |
| uint8_t mouse_button, |
| bool alt_key, |
| bool ctrl_key, |
| bool meta_key, |
| bool shift_key) { |
| const AutocompleteMatch* match = GetMatchWithUrl(line, url); |
| if (!match) { |
| // This can happen due to asynchronous updates changing the result while |
| // the web UI is referencing a stale match. |
| return; |
| } |
| const base::TimeTicks timestamp = base::TimeTicks::Now(); |
| const WindowOpenDisposition disposition = ui::DispositionFromClick( |
| /*middle_button=*/mouse_button == 1, alt_key, ctrl_key, meta_key, |
| shift_key); |
| edit_model()->OpenSelection(OmniboxPopupSelection(line), timestamp, |
| disposition); |
| } |
| |
| void RealboxHandler::OnNavigationLikely( |
| uint8_t line, |
| const GURL& url, |
| omnibox::mojom::NavigationPredictor navigation_predictor) { |
| const AutocompleteMatch* match = GetMatchWithUrl(line, url); |
| if (!match) { |
| // This can happen due to asynchronous updates changing the result while |
| // the web UI is referencing a stale match. |
| return; |
| } |
| if (auto* search_prefetch_service = |
| SearchPrefetchServiceFactory::GetForProfile(profile_)) { |
| search_prefetch_service->OnNavigationLikely( |
| line, *match, navigation_predictor, web_contents_); |
| } |
| } |
| |
| void RealboxHandler::DeleteAutocompleteMatch(uint8_t line, const GURL& url) { |
| const AutocompleteMatch* match = GetMatchWithUrl(line, url); |
| if (!match || !match->SupportsDeletion()) { |
| // This can happen due to asynchronous updates changing the result while |
| // the web UI is referencing a stale match. |
| return; |
| } |
| autocomplete_controller()->Stop(false); |
| autocomplete_controller()->DeleteMatch(*match); |
| } |
| |
| void RealboxHandler::ToggleSuggestionGroupIdVisibility( |
| int32_t suggestion_group_id) { |
| const auto& group_id = omnibox::GroupIdForNumber(suggestion_group_id); |
| DCHECK_NE(omnibox::GROUP_INVALID, group_id); |
| const bool current_visibility = |
| autocomplete_controller()->result().IsSuggestionGroupHidden( |
| profile_->GetPrefs(), group_id); |
| autocomplete_controller()->result().SetSuggestionGroupHidden( |
| profile_->GetPrefs(), group_id, !current_visibility); |
| } |
| |
| void RealboxHandler::ExecuteAction(uint8_t line, |
| const GURL& url, |
| base::TimeTicks match_selection_timestamp, |
| uint8_t mouse_button, |
| bool alt_key, |
| bool ctrl_key, |
| bool meta_key, |
| bool shift_key) { |
| const AutocompleteMatch* match = GetMatchWithUrl(line, url); |
| if (!match) { |
| // This can happen due to asynchronous updates changing the result while |
| // the web UI is referencing a stale match. |
| return; |
| } |
| const WindowOpenDisposition disposition = ui::DispositionFromClick( |
| /*middle_button=*/mouse_button == 1, alt_key, ctrl_key, meta_key, |
| shift_key); |
| OmniboxPopupSelection selection( |
| line, OmniboxPopupSelection::LineState::FOCUSED_BUTTON_ACTION); |
| edit_model()->OpenSelection(selection, match_selection_timestamp, |
| disposition); |
| } |
| |
| void RealboxHandler::OnResultChanged(AutocompleteController* controller, |
| bool default_match_changed) { |
| if (metrics_reporter_ && !metrics_reporter_->HasLocalMark("ResultChanged")) { |
| metrics_reporter_->Mark("ResultChanged"); |
| } |
| |
| // Update the omnibox if the controller does not belong to the realbox. |
| if (controller != autocomplete_controller()) { |
| if (base::FeatureList::IsEnabled(omnibox::kWebUIOmniboxPopup)) { |
| page_->OmniboxAutocompleteResultChanged(CreateAutocompleteResult( |
| controller->input().text(), controller->result(), |
| BookmarkModelFactory::GetForBrowserContext(profile_), |
| profile_->GetPrefs())); |
| } |
| return; |
| } |
| |
| // Update the realbox only if the controller belongs to the realbox. |
| page_->AutocompleteResultChanged(CreateAutocompleteResult( |
| autocomplete_controller()->input().text(), |
| autocomplete_controller()->result(), |
| BookmarkModelFactory::GetForBrowserContext(profile_), |
| profile_->GetPrefs())); |
| |
| if (autocomplete_controller()->done()) { |
| if (SearchPrefetchService* search_prefetch_service = |
| SearchPrefetchServiceFactory::GetForProfile(profile_)) { |
| search_prefetch_service->OnResultChanged( |
| web_contents_, autocomplete_controller()->result()); |
| } |
| } |
| } |
| |
| void RealboxHandler::SelectMatchAtLine(size_t old_line, size_t new_line) { |
| page_->SelectMatchAtLine(new_line); |
| } |
| |
| // OmniboxEditModelDelegate: |
| void RealboxHandler::OnAutocompleteAccept( |
| const GURL& destination_url, |
| TemplateURLRef::PostContent* post_content, |
| WindowOpenDisposition disposition, |
| ui::PageTransition transition, |
| AutocompleteMatchType::Type match_type, |
| base::TimeTicks match_selection_timestamp, |
| bool destination_url_entered_without_scheme, |
| const std::u16string& text, |
| const AutocompleteMatch& match, |
| const AutocompleteMatch& alternative_nav_match, |
| IDNA2008DeviationCharacter deviation_char_in_hostname) { |
| destination_url_ = destination_url; |
| post_content_ = post_content; |
| disposition_ = disposition; |
| transition_ = transition; |
| match_selection_timestamp_ = match_selection_timestamp; |
| destination_url_entered_without_scheme_ = |
| destination_url_entered_without_scheme; |
| |
| web_contents_->OpenURL(content::OpenURLParams( |
| destination_url_, content::Referrer(), disposition_, transition_, false)); |
| } |
| |
| void RealboxHandler::OnInputInProgress(bool in_progress) {} |
| |
| void RealboxHandler::OnChanged() {} |
| |
| void RealboxHandler::OnPopupVisibilityChanged() {} |
| |
| LocationBarModel* RealboxHandler::GetLocationBarModel() { |
| return this; |
| } |
| |
| const LocationBarModel* RealboxHandler::GetLocationBarModel() const { |
| return this; |
| } |
| |
| // LocationBarModel: |
| // Note, the implementation here is mostly not needed but the |
| // `OmniboxEditModelDelegate` implementation currently needs to |
| // provide a full working instance and some parts are used. |
| std::u16string RealboxHandler::GetFormattedFullURL() const { |
| return u""; |
| } |
| |
| std::u16string RealboxHandler::GetURLForDisplay() const { |
| return u""; |
| } |
| |
| GURL RealboxHandler::GetURL() const { |
| return GURL(); |
| } |
| |
| security_state::SecurityLevel RealboxHandler::GetSecurityLevel() const { |
| return security_state::SecurityLevel::NONE; |
| } |
| |
| net::CertStatus RealboxHandler::GetCertStatus() const { |
| return 0; |
| } |
| |
| metrics::OmniboxEventProto::PageClassification |
| RealboxHandler::GetPageClassification(OmniboxFocusSource focus_source, |
| bool is_prefetch) { |
| return metrics::OmniboxEventProto::NTP_REALBOX; |
| } |
| |
| const gfx::VectorIcon& RealboxHandler::GetVectorIcon() const { |
| return vector_icon_; |
| } |
| |
| std::u16string RealboxHandler::GetSecureDisplayText() const { |
| return u""; |
| } |
| |
| std::u16string RealboxHandler::GetSecureAccessibilityText() const { |
| return u""; |
| } |
| |
| bool RealboxHandler::ShouldDisplayURL() const { |
| return false; |
| } |
| |
| bool RealboxHandler::IsOfflinePage() const { |
| return false; |
| } |
| |
| bool RealboxHandler::ShouldPreventElision() const { |
| return false; |
| } |
| |
| bool RealboxHandler::ShouldUseUpdatedConnectionSecurityIndicators() const { |
| return false; |
| } |
| |
| OmniboxEditModel* RealboxHandler::edit_model() const { |
| return controller_->edit_model(); |
| } |
| |
| AutocompleteController* RealboxHandler::autocomplete_controller() const { |
| return controller_->autocomplete_controller(); |
| } |
| |
| const AutocompleteMatch* RealboxHandler::GetMatchWithUrl(size_t index, |
| const GURL& url) { |
| const AutocompleteResult& result = autocomplete_controller()->result(); |
| if (index >= result.size()) { |
| // This can happen due to asynchronous updates changing the result while |
| // the web UI is referencing a stale match. |
| return nullptr; |
| } |
| const AutocompleteMatch& match = result.match_at(index); |
| if (match.destination_url != url) { |
| // This can happen also, for the same reason. We could search the result |
| // for the match with this URL, but there would be no guarantee that it's |
| // the same match, so for this edge case we treat result mismatch as none. |
| return nullptr; |
| } |
| return &match; |
| } |