blob: eeb1d6ea66171fd514127f0b06e7c7378d973dfe [file] [log] [blame] [edit]
// 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 "components/omnibox/browser/omnibox_controller.h"
#include <memory>
#include <string>
#include "base/functional/bind.h"
#include "base/metrics/histogram.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "components/omnibox/browser/autocomplete_classifier.h"
#include "components/omnibox/browser/autocomplete_controller_emitter.h"
#include "components/omnibox/browser/autocomplete_enums.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/autocomplete_match_type.h"
#include "components/omnibox/browser/omnibox_client.h"
#include "components/omnibox/browser/omnibox_edit_model.h"
#include "components/omnibox/browser/omnibox_field_trial.h"
#include "components/omnibox/browser/omnibox_popup_selection.h"
#include "components/omnibox/browser/omnibox_popup_view.h"
#include "components/omnibox/browser/page_classification_functions.h"
#include "components/omnibox/common/omnibox_feature_configs.h"
#include "components/search_engines/template_url_starter_pack_data.h"
#include "ui/gfx/geometry/rect.h"
OmniboxController::OmniboxController(
OmniboxView* view,
std::unique_ptr<OmniboxClient> client,
base::TimeDelta autocomplete_stop_timer_duration)
: client_(std::move(client)),
autocomplete_controller_(std::make_unique<AutocompleteController>(
client_->CreateAutocompleteProviderClient(),
AutocompleteClassifier::DefaultOmniboxProviders(),
autocomplete_stop_timer_duration)),
edit_model_(std::make_unique<OmniboxEditModel>(
/*omnibox_controller=*/this,
view)) {
// Directly observe omnibox's `AutocompleteController` instance - i.e., when
// `view` is provided in the constructor. In the case of realbox - i.e., when
// `view` is not provided in the constructor - `RealboxHandler` directly
// observes the `AutocompleteController` instance itself.
if (view) {
autocomplete_controller_->AddObserver(this);
}
// Register the `AutocompleteController` with `AutocompleteControllerEmitter`.
if (auto* emitter = client_->GetAutocompleteControllerEmitter()) {
autocomplete_controller_->AddObserver(emitter);
}
}
constexpr bool is_ios = !!BUILDFLAG(IS_IOS);
OmniboxController::~OmniboxController() = default;
void OmniboxController::StartAutocomplete(
const AutocompleteInput& input) const {
TRACE_EVENT0("omnibox", "OmniboxController::StartAutocomplete");
ClearPopupKeywordMode();
// We don't explicitly clear OmniboxPopupModel::manually_selected_match, as
// Start ends up invoking OmniboxPopupModel::OnResultChanged which clears it.
autocomplete_controller_->Start(input);
}
void OmniboxController::StopAutocomplete(bool clear_result) const {
TRACE_EVENT0("omnibox", "OmniboxController::StopAutocomplete");
autocomplete_controller_->Stop(clear_result
? AutocompleteStopReason::kClobbered
: AutocompleteStopReason::kInteraction);
}
void OmniboxController::StartZeroSuggestPrefetch() {
TRACE_EVENT0("omnibox", "OmniboxController::StartZeroSuggestPrefetch");
client_->MaybePrewarmForDefaultSearchEngine();
auto page_classification =
client_->GetPageClassification(/*is_prefetch=*/true);
GURL current_url = client_->GetURL();
std::u16string text = base::UTF8ToUTF16(current_url.spec());
if (omnibox::IsNTPPage(page_classification) || !is_ios) {
text.clear();
}
AutocompleteInput input(text, page_classification,
client_->GetSchemeClassifier());
input.set_current_url(current_url);
input.set_current_title(client_->GetTitle());
input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS);
autocomplete_controller_->StartPrefetch(input);
}
void OmniboxController::OnResultChanged(AutocompleteController* controller,
bool default_match_changed) {
TRACE_EVENT0("omnibox", "OmniboxController::OnResultChanged");
DCHECK(controller == autocomplete_controller_.get());
const bool popup_was_open = edit_model_->PopupIsOpen();
if (default_match_changed) {
// The default match has changed, we need to let the OmniboxEditModel know
// about new inline autocomplete text (blue highlight).
if (autocomplete_controller_->result().default_match()) {
edit_model_->OnCurrentMatchChanged();
} else {
edit_model_->OnPopupResultChanged();
edit_model_->OnPopupDataChanged(
std::u16string(),
/*is_temporary_text=*/false, std::u16string(), std::u16string(),
std::u16string(), false, std::u16string(), AutocompleteMatch());
}
} else {
edit_model_->OnPopupResultChanged();
}
const bool popup_is_open = edit_model_->PopupIsOpen();
if (popup_was_open != popup_is_open) {
client_->OnPopupVisibilityChanged(popup_is_open);
}
if (popup_was_open && !popup_is_open) {
// Accept the temporary text as the user text, because it makes little sense
// to have temporary text when the popup is closed.
edit_model_->AcceptTemporaryTextAsUserText();
// Closing the popup can change the default suggestion. This usually occurs
// when it's unclear whether the input represents a search or URL; e.g.,
// 'a.com/b c' or when title autocompleting. Clear the additional text to
// avoid suggesting the omnibox contains a URL suggestion when that may no
// longer be the case; i.e. when the default suggestion changed from a URL
// to a search suggestion upon closing the popup.
edit_model_->ClearAdditionalText();
}
// Note: The client outlives |this|, so bind a weak pointer to the callback
// passed in to eliminate the potential for crashes on shutdown.
// `should_preload` is set to `controller->done()` as prerender may only want
// to start preloading a result after all Autocomplete results are ready.
client_->OnResultChanged(
autocomplete_controller_->result(), default_match_changed,
/*should_preload=*/controller->done(),
base::BindRepeating(&OmniboxController::SetRichSuggestionBitmap,
weak_ptr_factory_.GetWeakPtr()));
}
void OmniboxController::ClearPopupKeywordMode() const {
TRACE_EVENT0("omnibox", "OmniboxController::ClearPopupKeywordMode");
if (edit_model_->PopupIsOpen()) {
OmniboxPopupSelection selection = edit_model_->GetPopupSelection();
if (selection.state == OmniboxPopupSelection::KEYWORD_MODE) {
selection.state = OmniboxPopupSelection::NORMAL;
edit_model_->SetPopupSelection(selection);
}
}
}
std::u16string OmniboxController::GetHeaderForSuggestionGroup(
omnibox::GroupId suggestion_group_id) const {
return autocomplete_controller_->result().GetHeaderForSuggestionGroup(
suggestion_group_id);
}
bool OmniboxController::IsSuggestionHidden(
const AutocompleteMatch& match) const {
if (OmniboxFieldTrial::IsStarterPackExpansionEnabled() &&
match.from_keyword) {
const TemplateURL* turl =
match.GetTemplateURL(client_->GetTemplateURLService(), false);
if (turl &&
turl->starter_pack_id() == template_url_starter_pack_data::kGemini) {
return true;
}
}
return false;
}
void OmniboxController::SetRichSuggestionBitmap(int result_index,
const GURL& icon_url,
const SkBitmap& bitmap) {
if (!icon_url.is_empty()) {
edit_model_->SetIconBitmap(icon_url, bitmap);
} else {
edit_model_->SetPopupRichSuggestionBitmap(result_index, bitmap);
}
}