blob: 05bae05c8af691e43d4d4b52024bd1bce53fee05 [file] [log] [blame]
// Copyright 2020 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/zero_suggest_verbatim_match_provider.h"
#include <string>
#include "base/strings/escape.h"
#include "base/strings/utf_string_conversions.h"
#include "components/dom_distiller/core/url_utils.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/history_types.h"
#include "components/history/core/browser/url_database.h"
#include "components/history/core/browser/url_row.h"
#include "components/omnibox/browser/autocomplete_enums.h"
#include "components/omnibox/browser/autocomplete_match_classification.h"
#include "components/omnibox/browser/autocomplete_provider_client.h"
#include "components/omnibox/browser/autocomplete_provider_listener.h"
#include "components/omnibox/browser/suggestion_group_util.h"
#include "components/omnibox/browser/verbatim_match.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/search_engines/template_url_service.h"
#include "components/url_formatter/url_formatter.h"
namespace {
constexpr bool is_android = !!BUILDFLAG(IS_ANDROID);
// Returns whether specific context is eligible for a verbatim match.
// Only offer verbatim match on a site visit and SRP (no NTP etc).
bool IsVerbatimMatchEligible(
metrics::OmniboxEventProto::PageClassification context) {
using OEP = metrics::OmniboxEventProto;
// Only offer verbatim match on a site visit and SRP (no NTP etc).
return context == OEP::SEARCH_RESULT_PAGE_DOING_SEARCH_TERM_REPLACEMENT ||
context == OEP::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT ||
context == OEP::SEARCH_RESULT_PAGE_ON_CCT ||
context == OEP::ANDROID_SEARCH_WIDGET ||
context == OEP::ANDROID_SHORTCUTS_WIDGET ||
context == OEP::OTHER_ON_CCT || context == OEP::OTHER;
}
} // namespace
ZeroSuggestVerbatimMatchProvider::ZeroSuggestVerbatimMatchProvider(
AutocompleteProviderClient* client)
: AutocompleteProvider(TYPE_VERBATIM_MATCH), client_(client) {}
ZeroSuggestVerbatimMatchProvider::~ZeroSuggestVerbatimMatchProvider() = default;
void ZeroSuggestVerbatimMatchProvider::Start(const AutocompleteInput& input,
bool minimal_changes) {
Stop(AutocompleteStopReason::kClobbered);
if (!IsVerbatimMatchEligible(input.current_page_classification()))
return;
// Only offer verbatim match after the user just focused the Omnibox on NTP,
// SRP, or existing website view, or if the input field is empty.
if (!input.IsZeroSuggest()) {
return;
}
// For consistency with other zero-prefix providers.
const auto& page_url = input.current_url();
if (input.type() != metrics::OmniboxInputType::EMPTY &&
!(page_url.is_valid() &&
((page_url.scheme() == url::kHttpScheme) ||
(page_url.scheme() == url::kHttpsScheme) ||
(page_url.scheme() == url::kAboutScheme) ||
(page_url.scheme() ==
client_->GetEmbedderRepresentationOfAboutScheme())))) {
return;
}
CreateVerbatimMatch(input, input.current_title());
// It is possible for `title` to be empty if the page is currently loading.
// If title is empty and async matches are permitted, make an effort to
// retrieve page title from history database.
if (!matches_.back().description.empty() ||
input.omit_asynchronous_matches()) {
return;
}
history::HistoryService* const history_service = client_->GetHistoryService();
if (!history_service) {
return;
}
// Attempt to retrieve `title` from historical records.
done_ = false;
history_service->QueryURL(
input.current_url(), false,
base::BindOnce(&ZeroSuggestVerbatimMatchProvider::OnPageTitleRetrieved,
request_weak_ptr_factory_.GetWeakPtr(), input),
&task_tracker_);
}
void ZeroSuggestVerbatimMatchProvider::Stop(
AutocompleteStopReason stop_reason) {
AutocompleteProvider::Stop(stop_reason);
request_weak_ptr_factory_.InvalidateWeakPtrs();
}
void ZeroSuggestVerbatimMatchProvider::OnPageTitleRetrieved(
const AutocompleteInput& input,
history::QueryURLResult result) {
done_ = true;
// Re-create the item with a title collected from History service.
matches_.clear();
CreateVerbatimMatch(input, result.row.title());
}
void ZeroSuggestVerbatimMatchProvider::CreateVerbatimMatch(
const AutocompleteInput& input,
std::u16string page_title) {
AutocompleteInput verbatim_input = input;
verbatim_input.set_prevent_inline_autocomplete(true);
verbatim_input.set_allow_exact_keyword_match(false);
AutocompleteMatch match = VerbatimMatchForURL(
this, client_, verbatim_input, input.current_url(), std::move(page_title),
omnibox::kVerbatimMatchZeroSuggestRelevance);
// Make sure the URL is formatted the same was as most visited sites.
auto format_types = AutocompleteMatch::GetFormatTypes(false, false);
match.suggestion_group_id = omnibox::GROUP_MOBILE_SEARCH_READY_OMNIBOX;
match.contents = url_formatter::FormatUrl(input.current_url(), format_types,
base::UnescapeRule::SPACES, nullptr,
nullptr, nullptr);
TermMatches term_matches;
if (input.text().length() > 0) {
term_matches = {{0, 0, input.text().length()}};
}
match.contents_class = ClassifyTermMatches(
term_matches, match.contents.size(),
ACMatchClassification::MATCH | ACMatchClassification::URL,
ACMatchClassification::URL);
// If the URL suggestion comes from the default search engine, extract the
// original search query and place it in fill_into_edit, to permit re-use of
// the query for manual refinement.
if constexpr (is_android) {
auto* const url_service = client_->GetTemplateURLService();
if (url_service->IsSearchResultsPageFromDefaultSearchProvider(
match.destination_url)) {
auto* const dse = url_service->GetDefaultSearchProvider();
if (dse->url_ref().SupportsReplacement(
url_service->search_terms_data())) {
dse->ExtractSearchTermsFromURL(match.destination_url,
url_service->search_terms_data(),
&match.contents);
// Upgrade Verbatim Match to a SEARCH_WHAT_YOU_TYPED.
match.type = AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED;
match.keyword = dse->keyword();
match.fill_into_edit = match.contents;
if (match.description.empty() ||
match.description ==
base::UTF8ToUTF16(match.destination_url.spec())) {
match.description = match.fill_into_edit;
if (match.description_class.empty()) {
match.description_class.push_back({0, ACMatchClassification::NONE});
}
}
}
} else {
// URL suggestion here does not come from the default search engine.
// Ensure that distilled URL is transformed to original URL for
// fill_into_edit and contents fields of the suggestion.
if (dom_distiller::url_utils::IsDistilledPage(match.destination_url)) {
GURL original_url =
dom_distiller::url_utils::GetOriginalUrlFromDistillerUrl(
match.destination_url);
match.fill_into_edit = base::UTF8ToUTF16(original_url.spec());
match.contents = url_formatter::FormatUrl(
original_url,
url_formatter::kFormatUrlOmitDefaults |
url_formatter::kFormatUrlOmitHTTPS |
url_formatter::kFormatUrlOmitTrivialSubdomains,
base::UnescapeRule::SPACES, nullptr, nullptr, nullptr);
}
}
}
match.provider = this;
matches_.push_back(std::move(match));
}