blob: 2c6166d2cc30e93db1a4494eae5870e8ff2a182b [file] [log] [blame]
// Copyright 2022 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/history_cluster_provider.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "components/history_clusters/core/config.h"
#include "components/history_clusters/core/history_clusters_service.h"
#include "components/omnibox/browser/actions/history_clusters_action.h"
#include "components/omnibox/browser/autocomplete_controller.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/autocomplete_match_classification.h"
#include "components/omnibox/browser/autocomplete_match_type.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "third_party/metrics_proto/omnibox_event.pb.h"
#include "components/omnibox/browser/autocomplete_provider_client.h"
#include "components/omnibox/browser/autocomplete_provider_listener.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"
HistoryClusterProvider::HistoryClusterProvider(
AutocompleteProviderClient* client,
AutocompleteProviderListener* listener,
AutocompleteProvider* search_provider,
AutocompleteProvider* history_url_provider,
AutocompleteProvider* history_quick_provider)
: AutocompleteProvider(AutocompleteProvider::TYPE_HISTORY_CLUSTER_PROVIDER),
client_(client),
search_provider_(search_provider),
history_url_provider_(history_url_provider),
history_quick_provider_(history_quick_provider) {
DCHECK(search_provider_);
DCHECK(history_url_provider_);
DCHECK(history_quick_provider_);
AddListener(listener);
search_provider_->AddListener(this);
history_url_provider_->AddListener(this);
history_quick_provider_->AddListener(this);
}
// static
void HistoryClusterProvider::CompleteHistoryClustersMatch(
const std::string& matching_text,
history::ClusterKeywordData matched_keyword_data,
AutocompleteMatch* match) {
DCHECK(match);
// It's fine to unconditionally attach this takeover action, as the action
// itself checks the flag to redirect the user to either the Side Panel or
// the traditional History/Journeys WebUI. As a side effect, it will also
// record the action-centric metrics.
DCHECK(match->actions.empty());
match->takeover_action =
base::MakeRefCounted<history_clusters::HistoryClustersAction>(
matching_text, std::move(matched_keyword_data));
}
void HistoryClusterProvider::Start(const AutocompleteInput& input,
bool minimal_changes) {
Stop(true, false);
if (input.omit_asynchronous_matches())
return;
if (!client_->GetHistoryClustersService() ||
!client_->GetHistoryClustersService()->IsJourneysEnabled()) {
return;
}
if (!history_clusters::GetConfig().omnibox_history_cluster_provider)
return;
done_ = false;
input_ = input;
if (AllProvidersDone())
CreateMatches();
}
void HistoryClusterProvider::OnProviderUpdate(
bool updated_matches,
const AutocompleteProvider* provider) {
if (done_ || !AllProvidersDone())
return;
NotifyListeners(CreateMatches());
}
bool HistoryClusterProvider::AllProvidersDone() {
return search_provider_->done() && history_url_provider_->done() &&
history_quick_provider_->done();
}
bool HistoryClusterProvider::CreateMatches() {
done_ = true;
// If there's a reasonably clear navigation intent, don't distract the user
// with a history cluster suggestion.
if (!history_clusters::GetConfig()
.omnibox_history_cluster_provider_on_navigation_intents) {
// Helper to get the top relevance score looking at both providers.
const auto top_relevance =
[&](history_clusters::TopRelevanceFilter filter) {
return std::max(
{history_clusters::TopRelevance(
search_provider_->matches().begin(),
search_provider_->matches().end(), filter),
history_clusters::TopRelevance(
history_url_provider_->matches().begin(),
history_url_provider_->matches().end(), filter),
history_clusters::TopRelevance(
history_quick_provider_->matches().begin(),
history_quick_provider_->matches().end(), filter)});
};
if (history_clusters::IsNavigationIntent(
top_relevance(history_clusters::TopRelevanceFilter::
FILTER_FOR_SEARCH_MATCHES),
top_relevance(history_clusters::TopRelevanceFilter::
FILTER_FOR_NON_SEARCH_MATCHES),
history_clusters::GetConfig()
.omnibox_history_cluster_provider_navigation_intent_score_threshold)) {
return false;
}
}
// Iterate search matches in their current order. This is usually highest to
// lowest relevance with an exception for search-what-you-typed search
// suggestions being ordered before others.
for (const auto& search_match : search_provider_->matches()) {
auto matched_keyword_data =
client_->GetHistoryClustersService()->DoesQueryMatchAnyCluster(
base::UTF16ToUTF8(search_match.contents));
if (matched_keyword_data) {
client_->GetOmniboxTriggeredFeatureService()->FeatureTriggered(
metrics::OmniboxEventProto_Feature_HISTORY_CLUSTER_SUGGESTION);
if (!history_clusters::GetConfig()
.omnibox_history_cluster_provider_counterfactual) {
matches_.push_back(
CreateMatch(search_match, std::move(matched_keyword_data.value())));
}
return true;
}
}
return false;
}
AutocompleteMatch HistoryClusterProvider::CreateMatch(
const AutocompleteMatch& search_match,
history::ClusterKeywordData matched_keyword_data) {
AutocompleteMatch match;
match.provider = this;
match.type = AutocompleteMatch::Type::HISTORY_CLUSTER;
// TODO(manukh): Currently, history cluster suggestions only display when the
// `text` is an exact match of a cluster keyword, and all cluster keywords
// are treated equal. Therefore, we're limited to using a static value.
// Ideally, relevance would depend on how many keywords matched, how
// significant the keywords were, how significant their clusters were etc.
match.relevance =
history_clusters::GetConfig()
.omnibox_history_cluster_provider_inherit_search_match_score
? search_match.relevance - 1
: history_clusters::GetConfig()
.omnibox_history_cluster_provider_score;
const auto& text = search_match.contents;
match.destination_url = GURL(base::UTF8ToUTF16(base::StringPrintf(
"chrome://history/journeys?q=%s",
base::EscapeQueryParamValue(base::UTF16ToUTF8(text), /*use_plus=*/false)
.c_str())));
match.fill_into_edit = text;
match.description = text;
match.description_class = ClassifyTermMatches(
FindTermMatches(input_.text(), text), text.length(),
ACMatchClassification::MATCH, ACMatchClassification::NONE);
match.contents = l10n_util::GetStringUTF16(
IDS_OMNIBOX_ACTION_HISTORY_CLUSTERS_SEARCH_HINT);
match.contents_class = {{0, ACMatchClassification::DIM}};
CompleteHistoryClustersMatch(base::UTF16ToUTF8(text),
std::move(matched_keyword_data), &match);
return match;
}