#include "chrome/browser/autocomplete/unscoped_extension_provider_delegate_impl.h"
#include <cstddef>
#include <memory>
#include <string>
#include "base/containers/fixed_flat_map.h"
#include "base/functional/bind.h"
#include "base/i18n/case_conversion.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/api/omnibox/omnibox_api.h"
#include "chrome/browser/omnibox/omnibox_input_watcher_factory.h"
#include "chrome/browser/omnibox/omnibox_suggestions_watcher_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "components/omnibox/browser/actions/omnibox_extension_action.h"
#include "components/omnibox/browser/autocomplete_input.h"
#include "components/omnibox/browser/autocomplete_match_classification.h"
#include "components/omnibox/browser/suggestion_group_util.h"
#include "components/omnibox/browser/unscoped_extension_provider.h"
#include "components/omnibox/browser/vector_icons.h"
#include "extensions/browser/extension_util.h"
namespace {
constexpr size_t kMaxSuggestionsPerExtension = 4;
constexpr int kUnscopedExtensionRelevance = 2000;
constexpr auto kReservedGroupIdMap =
base::MakeFixedFlatMap<size_t, omnibox::GroupId>(
{{0, omnibox::GROUP_UNSCOPED_EXTENSION_1},
{1, omnibox::GROUP_UNSCOPED_EXTENSION_2}});
constexpr auto kReservedSectionMap =
base::MakeFixedFlatMap<size_t, omnibox::GroupSection>(
{{0, omnibox::SECTION_UNSCOPED_EXTENSION_1},
{1, omnibox::SECTION_UNSCOPED_EXTENSION_2}});
}
UnscopedExtensionProviderDelegateImpl::UnscopedExtensionProviderDelegateImpl(
Profile* profile,
UnscopedExtensionProvider* provider)
: profile_(profile), provider_(provider) {
CHECK(provider_);
omnibox_input_observation_.Observe(
OmniboxInputWatcherFactory::GetForBrowserContext(profile_));
omnibox_suggestions_observation_.Observe(
OmniboxSuggestionsWatcherFactory::GetForBrowserContext(
profile_->GetOriginalProfile()));
}
UnscopedExtensionProviderDelegateImpl::
~UnscopedExtensionProviderDelegateImpl() = default;
void UnscopedExtensionProviderDelegateImpl::Start(
const AutocompleteInput& input,
bool minimal_changes,
std::set<std::string> unscoped_mode_extension_ids) {
CHECK(extension_suggest_matches_.empty());
CHECK(extension_id_to_group_id_map_.empty());
first_suggestion_relevance_ =
input.IsZeroSuggest() ? omnibox::kUnscopedExtensionZeroSuggestRelevance
: kUnscopedExtensionRelevance;
for (const std::string& extension_id : unscoped_mode_extension_ids) {
if (!IsEnabledExtension(extension_id)) {
continue;
}
provider_->set_done(false);
extensions::ExtensionOmniboxEventRouter::OnInputChanged(
profile_, extension_id, base::UTF16ToUTF8(input.text()),
current_request_id_);
}
}
void UnscopedExtensionProviderDelegateImpl::Stop(bool clear_cached_results) {
current_request_id_++;
if (clear_cached_results) {
ClearSuggestions();
}
provider_->set_done(true);
}
void UnscopedExtensionProviderDelegateImpl::DeleteSuggestion(
const TemplateURL* template_url,
const std::u16string& suggestion_text) {
if (!IsEnabledExtension(template_url->GetExtensionId())) {
return;
}
extensions::ExtensionOmniboxEventRouter::OnDeleteSuggestion(
profile_, template_url->GetExtensionId(),
base::UTF16ToUTF8(suggestion_text));
}
void UnscopedExtensionProviderDelegateImpl::OnOmniboxSuggestionsReady(
const std::vector<ExtensionSuggestion>& suggestions,
const int request_id,
const std::string& extension_id) {
if (request_id != current_request_id_ ||
base::Contains(extension_id_to_group_id_map_, extension_id) ||
provider_->done() || suggestions.empty()) {
return;
}
TemplateURLService* turl_service = provider_->GetTemplateURLService();
const TemplateURL* template_url = turl_service->FindTemplateURLForExtension(
extension_id, TemplateURL::OMNIBOX_API_EXTENSION);
const omnibox::GroupId current_group_id =
kReservedGroupIdMap.at(next_available_group_index_++);
extension_id_to_group_id_map_[extension_id] = current_group_id;
CHECK_LT(next_available_section_index_, kReservedSectionMap.size());
const omnibox::GroupSection current_section =
kReservedSectionMap.at(next_available_section_index_++);
omnibox::GroupConfig group;
group.set_section(current_section);
group.set_render_type(omnibox::GroupConfig_RenderType_DEFAULT_VERTICAL);
group.set_header_text(base::UTF16ToUTF8(template_url->keyword()));
provider_->AddToSuggestionGroupsMap(current_group_id, std::move(group));
for (const auto& suggestion : suggestions) {
CHECK_GE(first_suggestion_relevance_, 0);
extension_suggest_matches_.push_back(CreateAutocompleteMatch(
suggestion, first_suggestion_relevance_--, extension_id));
}
ACMatches* matches = provider_->matches();
matches->insert(matches->end(), extension_suggest_matches_.begin(),
std::min(extension_suggest_matches_.end(),
extension_suggest_matches_.begin() +
kMaxSuggestionsPerExtension));
if (next_available_group_index_ == kReservedGroupIdMap.size() ||
provider_->GetTemplateURLService()
->GetUnscopedModeExtensionIds()
.size() == 1) {
provider_->set_done(true);
}
provider_->NotifyListeners(!extension_suggest_matches_.empty());
}
void UnscopedExtensionProviderDelegateImpl::OnOmniboxInputEntered() {
Stop(true);
}
AutocompleteMatch
UnscopedExtensionProviderDelegateImpl::CreateAutocompleteMatch(
const ExtensionSuggestion& suggestion,
int relevance,
const std::string& extension_id) {
AutocompleteMatch match(provider_.get(), relevance, suggestion.deletable,
AutocompleteMatchType::SEARCH_OTHER_ENGINE);
std::u16string trimmed_suggestion_content;
base::TrimWhitespace(base::UTF8ToUTF16(suggestion.content),
base::TRIM_LEADING, &trimmed_suggestion_content);
match.fill_into_edit = trimmed_suggestion_content;
match.contents = base::UTF8ToUTF16(suggestion.description);
match.contents_class.emplace_back(0, ACMatchClassification::DIM);
match.transition = ui::PAGE_TRANSITION_GENERATED;
TemplateURLService* turl_service = provider_->GetTemplateURLService();
const TemplateURL* template_url = turl_service->FindTemplateURLForExtension(
extension_id, TemplateURL::OMNIBOX_API_EXTENSION);
match.keyword = template_url->keyword();
const TemplateURLRef& element_ref = template_url->url_ref();
CHECK(element_ref.SupportsReplacement(
provider_->GetTemplateURLService()->search_terms_data()));
TemplateURLRef::SearchTermsArgs search_terms_args(
base::UTF8ToUTF16(suggestion.content));
match.destination_url = GURL(element_ref.ReplaceSearchTerms(
search_terms_args,
provider_->GetTemplateURLService()->search_terms_data()));
CHECK(!suggestion.match_classifications.empty());
match.contents_class = suggestion.match_classifications;
match.suggestion_group_id = extension_id_to_group_id_map_[extension_id];
if (suggestion.actions) {
for (const auto& action : *suggestion.actions) {
match.actions.push_back(base::MakeRefCounted<OmniboxExtensionAction>(
base::UTF8ToUTF16(action.label),
base::UTF8ToUTF16(action.tooltip_text),
base::BindRepeating(
&UnscopedExtensionProviderDelegateImpl::OnActionExecuted,
weak_factory_.GetWeakPtr(), extension_id, action.name,
suggestion.content),
action.icon));
}
}
if (suggestion.icon_url.has_value()) {
GURL icon_url = GURL(suggestion.icon_url.value());
match.image_url = icon_url.is_valid() ? icon_url : GURL();
}
return match;
}
void UnscopedExtensionProviderDelegateImpl::ClearSuggestions() {
extension_suggest_matches_.clear();
extension_id_to_group_id_map_.clear();
next_available_group_index_ = 0;
next_available_section_index_ = 0;
}
void UnscopedExtensionProviderDelegateImpl::OnActionExecuted(
const std::string& extension_id,
const std::string& action_name,
const std::string& contents) {
if (!IsEnabledExtension(extension_id)) {
return;
}
extensions::ExtensionOmniboxEventRouter::OnActionExecuted(
profile_.get(), extension_id, action_name, contents);
Stop(true);
}
bool UnscopedExtensionProviderDelegateImpl::IsEnabledExtension(
const std::string& extension_id) {
const extensions::Extension* extension =
extensions::ExtensionRegistry::Get(profile_)
->enabled_extensions()
.GetByID(extension_id);
return extension;
}