blob: 22e73326da5da7143c29d8c7c3fc8ec08b551272 [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/query_tile_provider.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.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/omnibox_field_trial.h"
#include "components/omnibox/browser/page_classification_functions.h"
#include "components/query_tiles/tile_service.h"
#include "components/search/search.h"
#include "components/search_engines/template_url_service.h"
#include "third_party/metrics_proto/omnibox_event.pb.h"
#include "third_party/omnibox_proto/groups.pb.h"
// The relevance score for query tile match.
constexpr int kQueryTilesMatchRelevanceScore = 1600;
QueryTileProvider::QueryTileProvider(AutocompleteProviderClient* client,
AutocompleteProviderListener* listener)
: AutocompleteProvider(AutocompleteProvider::TYPE_QUERY_TILE),
client_(client) {
AddListener(listener);
}
QueryTileProvider::~QueryTileProvider() = default;
void QueryTileProvider::Start(const AutocompleteInput& input,
bool minimal_changes) {
Stop(true, false);
if (!IsAllowedInContext(input)) {
return;
}
done_ = false;
// Fetch data from server only if our caches are empty.
if (tiles_.empty() && !input.omit_asynchronous_matches()) {
client_->GetQueryTileService()->GetQueryTiles(
base::BindOnce(&QueryTileProvider::OnTilesFetched,
weak_ptr_factory_.GetWeakPtr(), /*is_prefetch=*/false));
} else {
// Serve results from cache synchronously.
BuildSuggestions();
}
}
void QueryTileProvider::StartPrefetch(const AutocompleteInput& input) {
if (!IsAllowedInContext(input)) {
return;
}
// Verify tiles age. Re-use previously cached response unless expired.
if (!tiles_.empty() &&
(base::TimeTicks::Now() - tiles_creation_timestamp_ <=
base::Hours(OmniboxFieldTrial::kQueryTilesCacheMaxAge.Get()))) {
return;
}
// Drop results, the contents will be served on Start().
client_->GetQueryTileService()->GetQueryTiles(
base::BindOnce(&QueryTileProvider::OnTilesFetched,
weak_ptr_factory_.GetWeakPtr(), /*is_prefetch=*/true));
}
void QueryTileProvider::Stop(bool clear_cached_results,
bool due_to_user_inactivity) {
AutocompleteProvider::Stop(clear_cached_results, due_to_user_inactivity);
// The request was stopped. Cancel any in-flight requests for fetching query
// tiles from TileService.
weak_ptr_factory_.InvalidateWeakPtrs();
}
bool QueryTileProvider::IsAllowedInContext(const AutocompleteInput& input) {
if (!client_->SearchSuggestEnabled())
return false;
if (client_->IsOffTheRecord())
return false;
if (!search::DefaultSearchProviderIsGoogle(
client_->GetTemplateURLService())) {
return false;
}
return (input.current_page_classification() ==
metrics::OmniboxEventProto::NTP_ZPS_PREFETCH ||
omnibox::IsNTPPage(input.current_page_classification())) &&
input.focus_type() == metrics::INTERACTION_FOCUS;
}
void QueryTileProvider::OnTilesFetched(bool is_prefetch,
std::vector<query_tiles::Tile> tiles) {
tiles_ = std::move(tiles);
tiles_creation_timestamp_ = base::TimeTicks::Now();
if (!is_prefetch) {
BuildSuggestions();
}
}
void QueryTileProvider::BuildSuggestions() {
// The following condition is guaranteed upon entering this function:
// - if `Stop()` is called before callback is executed, the callback will be
// invalidated, and this method won't be invoked.
// - `OnceCallback`s can only be executed once.
// No other mechanism re-sets `done_`.
DCHECK(!done_);
done_ = true;
// Note: we already know TemplateURL is valid, because we require DSE to be
// Google -- see |IsAllowedInContext|.
auto* template_url_service = client_->GetTemplateURLService();
auto* template_url = template_url_service->GetDefaultSearchProvider();
const auto& template_url_ref = template_url->url_ref();
const auto& search_terms_data = template_url_service->search_terms_data();
std::u16string keyword = template_url->keyword();
for (const auto& tile : tiles_) {
AutocompleteMatch match(this, kQueryTilesMatchRelevanceScore, false,
AutocompleteMatchType::TILE_SUGGESTION);
match.contents = base::ASCIIToUTF16(tile.display_text);
match.contents_class = ClassifyTermMatches({}, match.contents.size(),
ACMatchClassification::MATCH,
ACMatchClassification::URL);
match.fill_into_edit = base::ASCIIToUTF16(tile.query_text);
match.suggestion_group_id = omnibox::GROUP_MOBILE_QUERY_TILES;
match.keyword = keyword;
match.search_terms_args = std::make_unique<TemplateURLRef::SearchTermsArgs>(
base::ASCIIToUTF16(tile.query_text));
match.search_terms_args->additional_query_params =
base::JoinString(tile.search_params, "&");
if (!tile.image_metadatas.empty()) {
match.image_url = tile.image_metadatas[0].url;
}
match.destination_url = GURL(template_url_ref.ReplaceSearchTerms(
*match.search_terms_args, search_terms_data));
matches_.push_back(std::move(match));
}
NotifyListeners(!matches_.empty());
}