// Copyright 2013 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/omnibox/chrome_omnibox_client.h"

#include <stddef.h>

#include <algorithm>
#include <memory>
#include <utility>

#include "base/functional/bind.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/rand_util.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/trace_event/typed_macros.h"
#include "chrome/app/chrome_command_ids.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/autocomplete/shortcuts_backend_factory.h"
#include "chrome/browser/bitmap_fetcher/bitmap_fetcher_service_factory.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/command_updater.h"
#include "chrome/browser/extensions/api/omnibox/omnibox_api.h"
#include "chrome/browser/favicon/favicon_service_factory.h"
#include "chrome/browser/feedback/public/feedback_source.h"
#include "chrome/browser/feedback/show_feedback_page.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/history_embeddings/history_embeddings_utils.h"
#include "chrome/browser/omnibox/autocomplete_controller_emitter_factory.h"
#include "chrome/browser/predictors/autocomplete_action_predictor.h"
#include "chrome/browser/predictors/autocomplete_action_predictor_factory.h"
#include "chrome/browser/predictors/loading_predictor.h"
#include "chrome/browser/predictors/loading_predictor_factory.h"
#include "chrome/browser/preloading/autocomplete_dictionary_preload_service.h"
#include "chrome/browser/preloading/autocomplete_dictionary_preload_service_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/preloading/preloading_features.h"
#include "chrome/browser/preloading/prerender/prerender_manager.h"
#include "chrome/browser/preloading/prerender/prerender_utils.h"
#include "chrome/browser/preloading/search_preload/search_preload_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ssl/typed_navigation_upgrade_throttle.h"
#include "chrome/browser/ui/bookmarks/bookmark_stats.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/hats/hats_service.h"
#include "chrome/browser/ui/hats/hats_service_factory.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/lens/lens_search_controller.h"
#include "chrome/browser/ui/lens/lens_searchbox_controller.h"
#include "chrome/browser/ui/location_bar/location_bar.h"
#include "chrome/browser/ui/omnibox/chrome_omnibox_navigation_observer.h"
#include "chrome/browser/ui/omnibox/omnibox_next_features.h"
#include "chrome/browser/ui/omnibox/omnibox_tab_helper.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
#include "chrome/browser/ui/views/omnibox/omnibox_popup_closer.h"
#include "chrome/browser/ui/views/search_engines/dse_reset_dialog.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/pref_names.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/favicon/content/content_favicon_driver.h"
#include "components/favicon/core/favicon_service.h"
#include "components/omnibox/browser/autocomplete_controller_emitter.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/autocomplete_result.h"
#include "components/omnibox/browser/base_search_provider.h"
#include "components/omnibox/browser/location_bar_model.h"
#include "components/omnibox/browser/most_visited_sites_provider.h"
#include "components/omnibox/browser/omnibox_prefs.h"
#include "components/omnibox/browser/page_classification_functions.h"
#include "components/omnibox/browser/search_provider.h"
#include "components/omnibox/browser/shortcuts_backend.h"
#include "components/omnibox/browser/zero_suggest_provider.h"
#include "components/omnibox/common/omnibox_feature_configs.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/profile_metrics/browser_profile_type.h"
#include "components/search_engines/template_url_service.h"
#include "components/search_engines/template_url_starter_pack_data.h"
#include "components/sessions/content/session_tab_helper.h"
#include "components/strings/grit/components_strings.h"
#include "components/viz/common/frame_sinks/copy_output_result.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/constants.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "skia/ext/image_operations.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/page_transition_types.h"
#include "ui/base/window_open_disposition.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/vector_icon_types.h"
#include "url/gurl.h"

#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_service.h"
#include "chrome/browser/ui/extensions/settings_api_bubble_helpers.h"
#endif

namespace {

using predictors::AutocompleteActionPredictor;

LensSearchController* GetLensSearchController(
    content::WebContents* web_contents) {
  return web_contents ? LensSearchController::FromTabWebContents(web_contents)
                      : nullptr;
}

}  // namespace

ChromeOmniboxClient::ChromeOmniboxClient(LocationBar* location_bar,
                                         Browser* browser,
                                         Profile* profile)
    : location_bar_(location_bar),
      browser_(browser),
      profile_(profile),
      scheme_classifier_(
          std::make_unique<ChromeAutocompleteSchemeClassifier>(profile)),
      favicon_cache_(FaviconServiceFactory::GetForProfile(
                         profile,
                         ServiceAccessType::EXPLICIT_ACCESS),
                     HistoryServiceFactory::GetForProfile(
                         profile,
                         ServiceAccessType::EXPLICIT_ACCESS)) {}

ChromeOmniboxClient::~ChromeOmniboxClient() {
  BitmapFetcherService* bitmap_fetcher_service =
      BitmapFetcherServiceFactory::GetForBrowserContext(profile_);
  for (auto request_id : request_ids_) {
    bitmap_fetcher_service->CancelRequest(request_id);
  }
}

std::unique_ptr<AutocompleteProviderClient>
ChromeOmniboxClient::CreateAutocompleteProviderClient() {
  // base::Unretained(location_bar_) is safe because `location_bar_` outlives
  // `ChromeOmniboxClient` which outlives `AutocompleteController` which owns
  // `ChromeAutocompleteProviderClient`.
  return std::make_unique<ChromeAutocompleteProviderClient>(
      profile_, base::BindRepeating(&LocationBar::GetWebContents,
                                    base::Unretained(location_bar_)));
}

bool ChromeOmniboxClient::CurrentPageExists() const {
  return (location_bar_->GetWebContents() != nullptr);
}

const GURL& ChromeOmniboxClient::GetURL() const {
  return CurrentPageExists() ? location_bar_->GetWebContents()->GetVisibleURL()
                             : GURL::EmptyGURL();
}

const std::u16string& ChromeOmniboxClient::GetTitle() const {
  return CurrentPageExists() ? location_bar_->GetWebContents()->GetTitle()
                             : base::EmptyString16();
}

gfx::Image ChromeOmniboxClient::GetFavicon() const {
  return favicon::ContentFaviconDriver::FromWebContents(
             location_bar_->GetWebContents())
      ->GetFavicon();
}

ukm::SourceId ChromeOmniboxClient::GetUKMSourceId() const {
  return CurrentPageExists() ? location_bar_->GetWebContents()
                                   ->GetPrimaryMainFrame()
                                   ->GetPageUkmSourceId()
                             : ukm::kInvalidSourceId;
}

bool ChromeOmniboxClient::IsLoading() const {
  return location_bar_->GetWebContents()->IsLoading();
}

bool ChromeOmniboxClient::IsPasteAndGoEnabled() const {
  return location_bar_->command_updater()->IsCommandEnabled(
      IDC_OPEN_CURRENT_URL);
}

bool ChromeOmniboxClient::IsDefaultSearchProviderEnabled() const {
  const base::Value::Dict& url_dict = profile_->GetPrefs()->GetDict(
      DefaultSearchManager::kDefaultSearchProviderDataPrefName);
  return !url_dict.FindBool(DefaultSearchManager::kDisabledByPolicy)
              .value_or(false);
}

SessionID ChromeOmniboxClient::GetSessionID() const {
  return sessions::SessionTabHelper::IdForTab(location_bar_->GetWebContents());
}

PrefService* ChromeOmniboxClient::GetPrefs() {
  return profile_->GetPrefs();
}

const PrefService* ChromeOmniboxClient::GetPrefs() const {
  return profile_->GetPrefs();
}

bookmarks::BookmarkModel* ChromeOmniboxClient::GetBookmarkModel() {
  return BookmarkModelFactory::GetForBrowserContext(profile_);
}

AutocompleteControllerEmitter*
ChromeOmniboxClient::GetAutocompleteControllerEmitter() {
  return AutocompleteControllerEmitterFactory::GetForBrowserContext(profile_);
}

TemplateURLService* ChromeOmniboxClient::GetTemplateURLService() {
  return TemplateURLServiceFactory::GetForProfile(profile_);
}

const AutocompleteSchemeClassifier& ChromeOmniboxClient::GetSchemeClassifier()
    const {
  return *scheme_classifier_;
}

AutocompleteClassifier* ChromeOmniboxClient::GetAutocompleteClassifier() {
  return AutocompleteClassifierFactory::GetForProfile(profile_);
}

omnibox::OmniboxPopupCloser* ChromeOmniboxClient::GetOmniboxPopupCloser() {
  if (!browser_) {
    return nullptr;
  }
  auto* bwf = browser_->browser_window_features();
  if (!bwf) {
    return nullptr;
  }
  return bwf->omnibox_popup_closer();
}

bool ChromeOmniboxClient::ShouldDefaultTypedNavigationsToHttps() const {
  return base::FeatureList::IsEnabled(omnibox::kDefaultTypedNavigationsToHttps);
}

int ChromeOmniboxClient::GetHttpsPortForTesting() const {
  return TypedNavigationUpgradeThrottle::GetHttpsPortForTesting();
}

bool ChromeOmniboxClient::IsUsingFakeHttpsForHttpsUpgradeTesting() const {
  // Tests on desktop/Android always use a real HTTPS server.
  return false;
}

gfx::Image ChromeOmniboxClient::GetExtensionIcon(
    const TemplateURL* template_url) const {
  CHECK_EQ(template_url->type(), TemplateURL::OMNIBOX_API_EXTENSION);
  return extensions::OmniboxAPI::Get(profile_)->GetOmniboxIcon(
      template_url->GetExtensionId());
}

gfx::Image ChromeOmniboxClient::GetSizedIcon(
    const gfx::VectorIcon& vector_icon_type,
    SkColor vector_icon_color) const {
  return gfx::Image(gfx::CreateVectorIcon(
      vector_icon_type, GetLayoutConstant(LOCATION_BAR_ICON_SIZE),
      vector_icon_color));
}

gfx::Image ChromeOmniboxClient::GetSizedIcon(const SkBitmap* bitmap) const {
  CHECK(bitmap);

  // First, resize the bitmap to `LOCATION_BAR_ICON_SIZE`.
  const int icon_size = GetLayoutConstant(LOCATION_BAR_ICON_SIZE);
  return gfx::Image(gfx::ImageSkiaOperations::CreateResizedImage(
      gfx::ImageSkia::CreateFrom1xBitmap(*bitmap),
      skia::ImageOperations::ResizeMethod::RESIZE_LANCZOS3,
      gfx::Size(icon_size, icon_size)));
}

gfx::Image ChromeOmniboxClient::GetSizedIcon(const gfx::Image& icon) const {
  if (icon.IsEmpty()) {
    return icon;
  }

  const int icon_size = GetLayoutConstant(LOCATION_BAR_ICON_SIZE);
  // In touch mode, icons are 20x20. FaviconCache and ExtensionIconManager both
  // guarantee favicons and extension icons will be 16x16, so add extra padding
  // around them to align them vertically with the other vector icons.
  DCHECK_GE(icon_size, icon.Height());
  DCHECK_GE(icon_size, icon.Width());
  auto padding_border = gfx::Insets::VH((icon_size - icon.Height()) / 2,
                                        (icon_size - icon.Width()) / 2);
  if (!padding_border.IsEmpty()) {
    return gfx::Image(gfx::CanvasImageSource::CreatePadded(*icon.ToImageSkia(),
                                                           padding_border));
  }
  return icon;
}

std::u16string ChromeOmniboxClient::GetFormattedFullURL() const {
  return location_bar_->GetLocationBarModel()->GetFormattedFullURL();
}

std::u16string ChromeOmniboxClient::GetURLForDisplay() const {
  return location_bar_->GetLocationBarModel()->GetURLForDisplay();
}

GURL ChromeOmniboxClient::GetNavigationEntryURL() const {
  return location_bar_->GetLocationBarModel()->GetURL();
}

metrics::OmniboxEventProto::PageClassification
ChromeOmniboxClient::GetPageClassification(bool is_prefetch) const {
  return location_bar_->GetLocationBarModel()->GetPageClassification(
      is_prefetch);
}

metrics::OmniboxEventProto::PageClassification
ChromeOmniboxClient::GetOmniboxComposeboxPageClassification() const {
  return location_bar_->GetLocationBarModel()
      ->GetOmniboxComposeboxPageClassification();
}

security_state::SecurityLevel ChromeOmniboxClient::GetSecurityLevel() const {
  return location_bar_->GetLocationBarModel()->GetSecurityLevel();
}

net::CertStatus ChromeOmniboxClient::GetCertStatus() const {
  return location_bar_->GetLocationBarModel()->GetCertStatus();
}

const gfx::VectorIcon& ChromeOmniboxClient::GetVectorIcon() const {
  return location_bar_->GetLocationBarModel()->GetVectorIcon();
}

void ChromeOmniboxClient::ProcessExtensionMatch(
    const std::u16string& text,
    const TemplateURL* template_url,
    const AutocompleteMatch& match,
    WindowOpenDisposition disposition) {
  // Strip the keyword + leading space (if present) off the input.
  std::u16string remaining_input;
  AutocompleteInput::SplitKeywordFromInput(match.fill_into_edit, true,
                                           &remaining_input);

  // In unscoped mode, the input is sent verbatim. In scoped (keyword) mode, the
  // keyword and input are split, and only the input after the keyword is sent.
  std::string input =
      match.provider->type() == AutocompleteProvider::TYPE_UNSCOPED_EXTENSION
          ? base::UTF16ToUTF8(match.fill_into_edit)
          : base::UTF16ToUTF8(remaining_input);
  extensions::ExtensionOmniboxEventRouter::OnInputEntered(
      location_bar_->GetWebContents(), template_url->GetExtensionId(), input,
      disposition);
}

void ChromeOmniboxClient::OnInputStateChanged() {
  if (!location_bar_->GetWebContents()) {
    return;
  }
  if (auto* helper =
          OmniboxTabHelper::FromWebContents(location_bar_->GetWebContents())) {
    helper->OnInputStateChanged();
  }
}

void ChromeOmniboxClient::OnFocusChanged(OmniboxFocusState state,
                                         OmniboxFocusChangeReason reason) {
  if (!location_bar_->GetWebContents()) {
    return;
  }
  if (auto* helper =
          OmniboxTabHelper::FromWebContents(location_bar_->GetWebContents())) {
    helper->OnFocusChanged(state, reason);
  }
}

void ChromeOmniboxClient::OnKeywordModeChanged(bool entered,
                                               const std::u16string& keyword) {
  if (entered) {
    // Note, entry into keyword mode is not sufficient signal to start lens and
    // that is handled by separate explicit actions; but whenever the '@page'
    // keyword mode is exited, lens should be closed.
    return;
  }

  TemplateURL* template_url =
      GetTemplateURLService()->GetTemplateURLForKeyword(keyword);
  if (!template_url || template_url->starter_pack_id() !=
                           template_url_starter_pack_data::kPage) {
    return;
  }

  if (LensSearchController* lens_search_controller =
          GetLensSearchController(location_bar_->GetWebContents())) {
    // TODO(crbug.com/408073216): Create and use new dismissal source.
    lens_search_controller->CloseLensAsync(
        lens::LensOverlayDismissalSource::kEscapeKeyPress);
  }
}

void ChromeOmniboxClient::MaybeShowOnFocusHatsSurvey(
    AutocompleteProviderClient* client) {
  if (!g_browser_process ||
      !base::StartsWith(g_browser_process->GetApplicationLocale(), "en")) {
    return;
  }

  // Check zero-suggest eligibility criteria.
  auto classification = GetPageClassification(/*is_prefetch=*/false);
  AutocompleteInput input(
      GetNavigationEntryURL().IsAboutBlank()
          ? u""
          : base::UTF8ToUTF16(GetNavigationEntryURL().spec()),
      classification, GetSchemeClassifier());
  input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS);
  input.set_current_url(GetNavigationEntryURL());
  if (!ZeroSuggestProvider::GetResultTypeAndEligibility(client, input).second ||
      !MostVisitedSitesProvider::AllowMostVisitedSitesSuggestions(client,
                                                                  input)) {
    return;
  }

  // If the content is for an SRP or Web Page.
  if (!omnibox::IsSearchResultsPage(classification) &&
      !omnibox::IsOtherWebPage(classification)) {
    return;
  }

  auto focus_count = GetPrefs()->GetInteger(omnibox::kFocusedSrpWebCount);
  focus_count += 1;

  if (focus_count <
      static_cast<int>(omnibox_feature_configs::
                           HappinessTrackingSurveyForOmniboxOnFocusZps::Get()
                               .focus_threshold)) {
    GetPrefs()->SetInteger(omnibox::kFocusedSrpWebCount, focus_count);
    return;
  }

  const auto survey_delay_time_ms =
      omnibox_feature_configs::HappinessTrackingSurveyForOmniboxOnFocusZps::
          Get()
              .survey_delay;
  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&ChromeOmniboxClient::CheckConditionsAndLaunchSurvey,
                     weak_factory_.GetWeakPtr()),
      base::Milliseconds(survey_delay_time_ms));
}

void ChromeOmniboxClient::CheckConditionsAndLaunchSurvey() {
  // Roll the dice as we want to show one of two surveys to the treatment
  // group but only one survey to the control group.
  bool show_happiness_survey = base::RandInt(0, 1) == 0;

  // Don't show the suggestions utility survey to control group.
  if (!omnibox_feature_configs::OmniboxUrlSuggestionsOnFocus::Get().enabled &&
      !show_happiness_survey) {
    return;
  }

  // Get channel string to return as PSD.
  std::string channel;
  switch (chrome::GetChannel()) {
    case version_info::Channel::STABLE:
      channel = "stable";
      break;
    case version_info::Channel::BETA:
      channel = "beta";
      break;
    case version_info::Channel::DEV:
      channel = "dev";
      break;
    case version_info::Channel::CANARY:
      channel = "canary";
      break;
    default:
      channel = "unknown";
  }

  const std::string& survey_trigger =
      show_happiness_survey ? kHatsSurveyTriggerOnFocusZpsSuggestionsHappiness
                            : kHatsSurveyTriggerOnFocusZpsSuggestionsUtility;

  const std::string& trigger_id =
      show_happiness_survey
          ? omnibox_feature_configs::
                HappinessTrackingSurveyForOmniboxOnFocusZps::Get()
                    .happiness_trigger_id
          : omnibox_feature_configs::
                HappinessTrackingSurveyForOmniboxOnFocusZps::Get()
                    .utility_trigger_id;

  HatsService* hats_service =
      HatsServiceFactory::GetForProfile(profile_, /*create_if_necessary=*/true);

  if (!browser_) {
    return;
  }

  auto* browser_view = BrowserView::GetBrowserViewForBrowser(browser_);

  if (!browser_view) {
    return;
  }

  auto* location_bar_view = browser_view->GetLocationBarView();

  // Don't show the HaTS survey if the location bar has focus.
  if (location_bar_view &&
      !location_bar_view->Contains(
          location_bar_view->GetFocusManager()->GetFocusedView())) {
    hats_service->LaunchSurvey(
        survey_trigger, base::DoNothing(), base::DoNothing(), {},
        {{"page classification",
          metrics::OmniboxEventProto::PageClassification_Name(
              GetPageClassification(/*is_prefetch=*/false))},
         {"channel", channel}},
        trigger_id, HatsService::SurveyOptions());
  }
}

void ChromeOmniboxClient::OnResultChanged(
    const AutocompleteResult& result,
    bool default_match_changed,
    bool should_preload,
    const BitmapFetchedCallback& on_bitmap_fetched) {
  if (should_preload) {
    if (auto* dictionary_preload_service =
            AutocompleteDictionaryPreloadServiceFactory::GetForProfile(
                profile_)) {
      dictionary_preload_service->MaybePreload(result);
    }
    if (SearchPrefetchService* search_prefetch_service =
            SearchPrefetchServiceFactory::GetForProfile(profile_)) {
      search_prefetch_service->OnResultChanged(location_bar_->GetWebContents(),
                                               result);
    }
    if (auto* search_preload_service =
            SearchPreloadService::GetForProfile(profile_)) {
      search_preload_service->OnAutocompleteResultChanged(
          location_bar_->GetWebContents(), result);
    }
  }

  BitmapFetcherService* bitmap_fetcher_service =
      BitmapFetcherServiceFactory::GetForBrowserContext(profile_);

  // Clear out the old requests.
  for (auto request_id : request_ids_) {
    bitmap_fetcher_service->CancelRequest(request_id);
  }
  request_ids_.clear();
  // Create new requests.
  int result_index = -1;
  for (const AutocompleteMatch& match : result) {
    ++result_index;
    if (!match.icon_url.is_empty()) {
      request_ids_.push_back(bitmap_fetcher_service->RequestImage(
          match.icon_url,
          base::BindOnce(on_bitmap_fetched, result_index, match.icon_url)));
    } else {
      const TemplateURL* turl = nullptr;
      if (!match.associated_keyword.empty()) {
        turl = AutocompleteMatch::GetTemplateURLWithKeyword(
            GetTemplateURLService(), match.associated_keyword, "");
      } else if (!match.keyword.empty()) {
        turl = match.GetTemplateURL(GetTemplateURLService());
      }
      // Fetch the favicon if the `TemplateURL` is from the enterprise search
      // aggregator policy. This covers both cases:
      // 1. Non-featured matches with an associated keyword hint (e.g.,
      //    verbatim match when typing 'aggregator').
      // 2. Matches originating from the aggregator keyword mode itself (e.g.
      //    shortcut suggestions in default mode).
      if (turl && turl->CreatedByEnterpriseSearchAggregatorPolicy()) {
        request_ids_.push_back(bitmap_fetcher_service->RequestImage(
            turl->favicon_url(), base::BindOnce(on_bitmap_fetched, result_index,
                                                turl->favicon_url())));
      }
    }
    if (match.HasTakeoverAction(OmniboxActionId::CONTEXTUAL_SEARCH_OPEN_LENS) &&
        omnibox_feature_configs::ContextualSearch::Get()
            .open_lens_action_uses_thumbnail) {
      if (const content::WebContents* web_contents =
              location_bar_->GetWebContents()) {
        content::RenderWidgetHostView* view =
            web_contents->GetPrimaryMainFrame()
                ->GetRenderViewHost()
                ->GetWidget()
                ->GetView();
        if (view && view->IsSurfaceAvailableForCopy()) {
          view->CopyFromSurface(
              /*src_rect=*/gfx::Rect(),
              /*output_size=*/gfx::Size(),
              base::BindPostTask(
                  base::SequencedTaskRunner::GetCurrentDefault(),
                  base::BindOnce(
                      [](const viz::CopyOutputBitmapWithMetadata& result) {
                        return result.bitmap;
                      })
                      .Then(base::BindOnce(on_bitmap_fetched, result_index,
                                           GURL()))));
        }
      }
    }
    if (!match.ImageUrl().is_empty()) {
      request_ids_.push_back(bitmap_fetcher_service->RequestImage(
          match.ImageUrl(),
          base::BindOnce(on_bitmap_fetched, result_index, GURL())));
    }
  }
}

gfx::Image ChromeOmniboxClient::GetFaviconForPageUrl(
    const GURL& page_url,
    FaviconFetchedCallback on_favicon_fetched) {
  return favicon_cache_.GetFaviconForPageUrl(page_url,
                                             std::move(on_favicon_fetched));
}

gfx::Image ChromeOmniboxClient::GetFaviconForDefaultSearchProvider(
    FaviconFetchedCallback on_favicon_fetched) {
  const TemplateURL* const default_provider =
      GetTemplateURLService()->GetDefaultSearchProvider();
  if (!default_provider) {
    return gfx::Image();
  }

  return favicon_cache_.GetFaviconForIconUrl(default_provider->favicon_url(),
                                             std::move(on_favicon_fetched));
}

gfx::Image ChromeOmniboxClient::GetFaviconForKeywordSearchProvider(
    const TemplateURL* template_url,
    FaviconFetchedCallback on_favicon_fetched) {
  if (!template_url) {
    return gfx::Image();
  }

  return favicon_cache_.GetFaviconForIconUrl(template_url->favicon_url(),
                                             std::move(on_favicon_fetched));
}

void ChromeOmniboxClient::OnTextChanged(const AutocompleteMatch& current_match,
                                        bool user_input_in_progress,
                                        const std::u16string& user_text,
                                        const AutocompleteResult& result,
                                        bool has_focus) {
  AutocompleteActionPredictor::Action recommended_action =
      AutocompleteActionPredictor::ACTION_NONE;
  if (user_input_in_progress) {
    content::WebContents* web_contents = location_bar_->GetWebContents();
    AutocompleteActionPredictor* action_predictor =
        predictors::AutocompleteActionPredictorFactory::GetForProfile(profile_);
    action_predictor->RegisterTransitionalMatches(user_text, result);
    // Confer with the AutocompleteActionPredictor to determine what action,
    // if any, we should take. Get the recommended action here even if we
    // don't need it so we can get stats for anyone who is opted in to UMA,
    // but only get it if the user has actually typed something to avoid
    // constructing it before it's needed. Note: This event is triggered as
    // part of startup when the initial tab transitions to the start page.
    recommended_action = action_predictor->RecommendAction(
        user_text, current_match, web_contents);
  }

  switch (recommended_action) {
    case AutocompleteActionPredictor::ACTION_PRERENDER:
      // It's possible that there is no current page, for instance if the tab
      // has been closed or on return from a sleep state.
      // (http://crbug.com/105689)
      if (!CurrentPageExists()) {
        break;
      }
      // Ask for prerendering if the destination URL is different than the
      // current URL.
      if (current_match.destination_url != GetURL()) {
        DoPrerender(current_match);
      }
      break;
    case AutocompleteActionPredictor::ACTION_PRECONNECT:
      DoPreconnect(current_match);
      break;
    case AutocompleteActionPredictor::ACTION_NONE:
      break;
  }

  location_bar_->OnChanged();
}

void ChromeOmniboxClient::OnRevert() {
  AutocompleteActionPredictor* action_predictor =
      predictors::AutocompleteActionPredictorFactory::GetForProfile(profile_);
  action_predictor->UpdateDatabaseFromTransitionalMatches(GURL());
}

void ChromeOmniboxClient::OnURLOpenedFromOmnibox(OmniboxLog* log) {
  // Record the value if prerender for search suggestion was not started. Other
  // values (kHitFinished, kUnused, kCancelled) are recorded in
  // PrerenderManager.
  content::WebContents* web_contents = location_bar_->GetWebContents();
  if (web_contents) {
    if (SearchPrefetchService* search_prefetch_service =
            SearchPrefetchServiceFactory::GetForProfile(profile_)) {
      search_prefetch_service->OnURLOpenedFromOmnibox(log);
    }

    auto* prerender_manager = PrerenderManager::FromWebContents(web_contents);
    if (!prerender_manager ||
        !prerender_manager->HasSearchResultPagePrerendered()) {
      base::UmaHistogramEnumeration(
          internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine,
          PrerenderPredictionStatus::kNotStarted);
    }
  }

  predictors::AutocompleteActionPredictorFactory::GetForProfile(profile_)
      ->OnOmniboxOpenedUrl(*log);
}

void ChromeOmniboxClient::OnBookmarkLaunched() {
  RecordBookmarkLaunch(BookmarkLaunchLocation::kOmnibox,
                       profile_metrics::GetBrowserProfileType(profile_));
}

void ChromeOmniboxClient::DiscardNonCommittedNavigations() {
  location_bar_->GetWebContents()->GetController().DiscardNonCommittedEntries();
}

void ChromeOmniboxClient::FocusWebContents() {
  if (location_bar_->GetWebContents()) {
    location_bar_->GetWebContents()->Focus();
  }
}

void ChromeOmniboxClient::OnNavigationLikely(
    size_t index,
    const AutocompleteMatch& match,
    omnibox::mojom::NavigationPredictor navigation_predictor) {
  if (SearchPrefetchService* search_prefetch_service =
          SearchPrefetchServiceFactory::GetForProfile(profile_)) {
    search_prefetch_service->OnNavigationLikely(
        index, match, navigation_predictor, location_bar_->GetWebContents());
  }
  if (auto* search_preload_service =
          SearchPreloadService::GetForProfile(profile_)) {
    search_preload_service->OnNavigationLikely(
        index, match, navigation_predictor, location_bar_->GetWebContents());
  }
}

void ChromeOmniboxClient::ShowFeedbackPage(const std::u16string& input_text,
                                           const GURL& destination_url) {
  base::Value::Dict ai_metadata;
  ai_metadata.Set("input", base::UTF16ToUTF8(input_text));
  ai_metadata.Set("destination_url", destination_url.spec());
  chrome::ShowFeedbackPage(
      browser_, feedback::kFeedbackSourceAI,
      /*description_template=*/std::string(),
      /*description_placeholder_text=*/
      l10n_util::GetStringUTF8(IDS_HISTORY_EMBEDDINGS_FEEDBACK_PLACEHOLDER),
      /*category_tag=*/"genai_history",
      /*extra_diagnostics=*/std::string(),
      /*autofill_metadata=*/base::Value::Dict(), std::move(ai_metadata));
}

void ChromeOmniboxClient::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,
    bool destination_url_entered_with_http_scheme,
    const std::u16string& text,
    const AutocompleteMatch& match,
    const AutocompleteMatch& alternative_nav_match) {
  TRACE_EVENT("omnibox", "ChromeOmniboxClient::OnAutocompleteAccept", "text",
              text, "match", match, "alternative_nav_match",
              alternative_nav_match);

  std::string extra_headers;
  for (const auto& header : match.extra_headers) {
    base::StrAppend(&extra_headers,
                    {header.first, ": ", header.second, "\r\n"});
  }

  // Store the details necessary to open the omnibox match via browser commands.
  location_bar_->set_navigation_params(LocationBar::NavigationParams(
      destination_url, disposition, transition, match_selection_timestamp,
      destination_url_entered_without_scheme,
      destination_url_entered_with_http_scheme, extra_headers));

  if (browser_) {
    auto navigation = chrome::OpenCurrentURL(browser_);
    ChromeOmniboxNavigationObserver::Create(navigation.get(), profile_, text,
                                            match, alternative_nav_match);
    search_engines::MaybeShowSearchEngineResetNotification(browser_,
                                                           match_type);
  }

#if BUILDFLAG(ENABLE_EXTENSIONS)
  extensions::MaybeShowExtensionControlledSearchNotification(
      location_bar_->GetWebContents(), match_type);

  if (AutocompleteMatch::IsSearchType(match_type)) {
    if (auto* telemetry_service =
            safe_browsing::ExtensionTelemetryService::Get(profile_)) {
      telemetry_service->OnOmniboxSearch(match);
    }
  }
#endif
}

void ChromeOmniboxClient::OnInputInProgress(bool in_progress) {
  location_bar_->UpdateWithoutTabRestore();
  content::WebContents* const web_contents = location_bar_->GetWebContents();
  if (web_contents) {
    auto* const helper =
        OmniboxTabHelper::FromWebContents(location_bar_->GetWebContents());
    CHECK(helper);
    helper->OnInputInProgress(in_progress);
  }
}

void ChromeOmniboxClient::OnPopupVisibilityChanged(bool popup_is_open) {
  content::WebContents* const web_contents = location_bar_->GetWebContents();
  if (web_contents) {
    auto* const helper =
        OmniboxTabHelper::FromWebContents(location_bar_->GetWebContents());
    CHECK(helper);
    helper->OnPopupVisibilityChanged(
        popup_is_open, GetPageClassification(/*is_prefetch=*/false));
  }
}

void ChromeOmniboxClient::OpenUrl(GURL gurl) {
  CHECK(browser_);
  NavigateParams params(browser_, gurl, ui::PAGE_TRANSITION_GENERATED);
  params.disposition = WindowOpenDisposition::CURRENT_TAB;
  Navigate(&params);
}

void ChromeOmniboxClient::OpenIphLink(GURL gurl) {
  ui::PageTransition transition = ui::PageTransitionFromInt(
      ui::PAGE_TRANSITION_LINK | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
  NavigateParams params(profile_, gurl, transition);
  params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
  Navigate(&params);
}

bool ChromeOmniboxClient::IsHistoryEmbeddingsEnabled() const {
  return history_embeddings::IsHistoryEmbeddingsEnabledForProfile(profile_);
}

bool ChromeOmniboxClient::IsAimPopupEnabled() const {
  return omnibox::IsAimPopupEnabled(profile_);
}

std::optional<lens::proto::LensOverlaySuggestInputs>
ChromeOmniboxClient::GetLensOverlaySuggestInputs() const {
  if (LensSearchController* lens_search_controller =
          GetLensSearchController(location_bar_->GetWebContents())) {
    return lens_search_controller->lens_searchbox_controller()
        ->GetLensSuggestInputs();
  }
  return std::nullopt;
}

void ChromeOmniboxClient::MaybePrewarmForDefaultSearchEngine() {
  if (!features::kPrewarmZeroSuggestTrigger.Get()) {
    // TODO(https://crbug.com/423465927): Consider to add a TriggerPlace enum
    // argument for this method on adding another triggers, and gate triggers
    // per the enum.
    return;
  }

  auto* prerender_manager = PrerenderManager::GetOrCreateForWebContents(
      location_bar_->GetWebContents());
  CHECK(prerender_manager);
  prerender_manager->MaybeStartPrewarmSearchResult();
}

base::WeakPtr<OmniboxClient> ChromeOmniboxClient::AsWeakPtr() {
  return weak_factory_.GetWeakPtr();
}

void ChromeOmniboxClient::DoPrerender(const AutocompleteMatch& match) {
  content::WebContents* web_contents = location_bar_->GetWebContents();

  // Don't prerender when DevTools is open in this tab.
  if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents)) {
    return;
  }

  // TODO(crbug.com/40208255): Refactor relevant code to reuse common
  // code, and ensure metrics are correctly recorded.
  DCHECK(!AutocompleteMatch::IsSearchType(match.type));

  predictors::AutocompleteActionPredictorFactory::GetForProfile(profile_)
      ->StartPrerendering(match.destination_url, *web_contents);
}

void ChromeOmniboxClient::DoPreconnect(const AutocompleteMatch& match) {
  if (match.destination_url.SchemeIs(extensions::kExtensionScheme)
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
      || match.destination_url.SchemeIs(extensions::kArkwebExtensionScheme)
#endif
  ) {
    return;
  }

  auto* loading_predictor =
      predictors::LoadingPredictorFactory::GetForProfile(profile_);
  if (loading_predictor) {
    bool is_preconnectable =
        predictors::AutocompleteActionPredictor::IsPreconnectable(match);
    loading_predictor->PrepareForPageLoad(
        /*initiator_origin=*/std::nullopt, match.destination_url,
        predictors::HintOrigin::OMNIBOX, is_preconnectable);
    base::UmaHistogramExactLinear(
        base::StrCat(
            {"Omnibox.LoadingPredictor.MatchType.",
             is_preconnectable ? "Preconnectable" : "NonPreconnectable"}),
        match.GetOmniboxEventResultType(),
        metrics::OmniboxEventProto::Suggestion::ResultType_MAX + 1);
  }
  // We could prefetch the alternate nav URL, if any, but because there
  // can be many of these as a user types an initial series of characters,
  // the OS DNS cache could suffer eviction problems for minimal gain.
}

// static
void ChromeOmniboxClient::OnSuccessfulNavigation(
    Profile* profile,
    const std::u16string& text,
    const AutocompleteMatch& match) {
  auto shortcuts_backend = ShortcutsBackendFactory::GetForProfile(profile);
  // Can be null in incognito.
  if (!shortcuts_backend) {
    return;
  }

  shortcuts_backend->AddOrUpdateShortcut(text, match);
}