blob: fa375dfdaab2148d9a2749dd1baec7d7876e4156 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/omnibox/browser/on_device_head_provider.h"
#include <limits>
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/i18n/case_conversion.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_macros.h"
#include "base/path_service.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/thread_pool.h"
#include "base/task_runner_util.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
#include "components/omnibox/browser/autocomplete_provider_listener.h"
#include "components/omnibox/browser/base_search_provider.h"
#include "components/omnibox/browser/omnibox_field_trial.h"
#include "components/omnibox/browser/on_device_head_provider.h"
#include "components/omnibox/browser/on_device_model_update_listener.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/search_engines/omnibox_focus_type.h"
#include "components/search_engines/search_terms_data.h"
#include "components/search_engines/template_url_service.h"
#include "third_party/metrics_proto/omnibox_input_type.pb.h"
namespace {
const int kBaseRelevanceForUrlInput = 99;
const size_t kMaxRequestId = std::numeric_limits<size_t>::max() - 1;
bool IsDefaultSearchProviderGoogle(
const TemplateURLService* template_url_service) {
if (!template_url_service)
return false;
const TemplateURL* default_provider =
template_url_service->GetDefaultSearchProvider();
return default_provider &&
default_provider->GetEngineType(
template_url_service->search_terms_data()) == SEARCH_ENGINE_GOOGLE;
}
} // namespace
struct OnDeviceHeadProvider::OnDeviceHeadProviderParams {
// The id assigned during request creation, which is used to trace this
// request and determine whether it is current or obsolete.
const size_t request_id;
// AutocompleteInput provided by OnDeviceHeadProvider::Start.
AutocompleteInput input;
// The suggestions fetched from the on device model which matches the input.
std::vector<std::string> suggestions;
// Indicates whether this request failed or not.
bool failed = false;
// The time when this request is created.
base::TimeTicks creation_time;
OnDeviceHeadProviderParams(size_t request_id, const AutocompleteInput& input)
: request_id(request_id), input(input) {}
~OnDeviceHeadProviderParams() = default;
OnDeviceHeadProviderParams(const OnDeviceHeadProviderParams&) = delete;
OnDeviceHeadProviderParams& operator=(const OnDeviceHeadProviderParams&) =
delete;
};
// static
OnDeviceHeadProvider* OnDeviceHeadProvider::Create(
AutocompleteProviderClient* client,
AutocompleteProviderListener* listener) {
DCHECK(client);
DCHECK(listener);
return new OnDeviceHeadProvider(client, listener);
}
OnDeviceHeadProvider::OnDeviceHeadProvider(
AutocompleteProviderClient* client,
AutocompleteProviderListener* listener)
: AutocompleteProvider(AutocompleteProvider::TYPE_ON_DEVICE_HEAD),
client_(client),
listener_(listener),
worker_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, base::MayBlock()})),
on_device_search_request_id_(0) {}
OnDeviceHeadProvider::~OnDeviceHeadProvider() {}
bool OnDeviceHeadProvider::IsOnDeviceHeadProviderAllowed(
const AutocompleteInput& input) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
// Only accept asynchronous request.
if (!input.want_asynchronous_matches() ||
input.type() == metrics::OmniboxInputType::EMPTY)
return false;
// Check whether search suggest is enabled.
if (!client()->SearchSuggestEnabled())
return false;
// Check if provider is allowed in incognito / non-incognito.
if (client()->IsOffTheRecord() &&
!OmniboxFieldTrial::IsOnDeviceHeadSuggestEnabledForIncognito())
return false;
if (!client()->IsOffTheRecord() &&
!OmniboxFieldTrial::IsOnDeviceHeadSuggestEnabledForNonIncognito())
return false;
// Reject on focus request.
if (input.focus_type() != OmniboxFocusType::DEFAULT)
return false;
// Do not proceed if default search provider is not Google.
return IsDefaultSearchProviderGoogle(client()->GetTemplateURLService());
}
void OnDeviceHeadProvider::Start(const AutocompleteInput& input,
bool minimal_changes) {
TRACE_EVENT0("omnibox", "OnDeviceHeadProvider::Start");
// Cancel any in-progress request.
Stop(!minimal_changes, false);
if (!IsOnDeviceHeadProviderAllowed(input)) {
matches_.clear();
return;
}
// If the input text has not changed, the result can be reused.
if (minimal_changes)
return;
matches_.clear();
if (input.text().empty() || GetOnDeviceHeadModelFilename().empty())
return;
// Note |on_device_search_request_id_| has already been changed in |Stop| so
// we don't need to change it again here to get a new id for this request.
std::unique_ptr<OnDeviceHeadProviderParams> params = base::WrapUnique(
new OnDeviceHeadProviderParams(on_device_search_request_id_, input));
done_ = false;
// Since the On Device provider usually runs much faster than online
// providers, it will be very likely users will see on device suggestions
// first and then the Omnibox UI gets refreshed to show suggestions fetched
// from server, if we issue both requests simultaneously.
// Therefore, we might want to delay the On Device suggest requests (and also
// apply a timeout to search default loader) to mitigate this issue. Note this
// delay is not needed for incognito where server suggestion is not served.
int delay = OmniboxFieldTrial::OnDeviceHeadSuggestDelaySuggestRequestMs(
client()->IsOffTheRecord());
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&OnDeviceHeadProvider::DoSearch,
weak_ptr_factory_.GetWeakPtr(), std::move(params)),
delay > 0 ? base::Milliseconds(delay) : base::TimeDelta());
}
void OnDeviceHeadProvider::Stop(bool clear_cached_results,
bool due_to_user_inactivity) {
// Increase the request_id so that any in-progress requests will become
// obsolete.
on_device_search_request_id_ =
(on_device_search_request_id_ + 1) % kMaxRequestId;
weak_ptr_factory_.InvalidateWeakPtrs();
if (clear_cached_results)
matches_.clear();
done_ = true;
}
// TODO(crbug.com/925072): post OnDeviceHeadModel::GetSuggestionsForPrefix
// directly and remove this function.
// static
std::unique_ptr<OnDeviceHeadProvider::OnDeviceHeadProviderParams>
OnDeviceHeadProvider::GetSuggestionsFromModel(
const std::string& model_filename,
const size_t provider_max_matches,
std::unique_ptr<OnDeviceHeadProviderParams> params) {
if (model_filename.empty() || !params) {
if (params) {
params->failed = true;
}
return params;
}
params->creation_time = base::TimeTicks::Now();
std::u16string trimmed_input;
base::TrimWhitespace(params->input.text(), base::TRIM_ALL, &trimmed_input);
auto results = OnDeviceHeadModel::GetSuggestionsForPrefix(
model_filename, provider_max_matches,
base::UTF16ToUTF8(base::i18n::ToLower(trimmed_input)));
params->suggestions.clear();
for (const auto& item : results) {
// The second member is the score which is not useful for provider.
params->suggestions.push_back(item.first);
}
return params;
}
void OnDeviceHeadProvider::AddProviderInfo(ProvidersInfo* provider_info) const {
provider_info->push_back(metrics::OmniboxEventProto_ProviderInfo());
metrics::OmniboxEventProto_ProviderInfo& new_entry = provider_info->back();
new_entry.set_provider(metrics::OmniboxEventProto::ON_DEVICE_HEAD);
new_entry.set_provider_done(done_);
}
void OnDeviceHeadProvider::DoSearch(
std::unique_ptr<OnDeviceHeadProviderParams> params) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
if (!params || params->request_id != on_device_search_request_id_) {
SearchDone(std::move(params));
return;
}
base::PostTaskAndReplyWithResult(
worker_task_runner_.get(), FROM_HERE,
base::BindOnce(&OnDeviceHeadProvider::GetSuggestionsFromModel,
GetOnDeviceHeadModelFilename(), provider_max_matches_,
std::move(params)),
base::BindOnce(&OnDeviceHeadProvider::SearchDone,
weak_ptr_factory_.GetWeakPtr()));
}
void OnDeviceHeadProvider::SearchDone(
std::unique_ptr<OnDeviceHeadProviderParams> params) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
TRACE_EVENT0("omnibox", "OnDeviceHeadProvider::SearchDone");
// Ignore this request if it has been stopped or a new one has already been
// created.
if (!params || params->request_id != on_device_search_request_id_)
return;
if (params->failed) {
done_ = true;
return;
}
const TemplateURLService* template_url_service =
client()->GetTemplateURLService();
if (IsDefaultSearchProviderGoogle(template_url_service)) {
UMA_HISTOGRAM_CUSTOM_COUNTS("Omnibox.OnDeviceHeadSuggest.ResultCount",
params->suggestions.size(), 1, 5, 6);
matches_.clear();
int relevance =
params->input.type() == metrics::OmniboxInputType::URL
? kBaseRelevanceForUrlInput
: OmniboxFieldTrial::OnDeviceHeadSuggestMaxScoreForNonUrlInput(
client()->IsOffTheRecord());
for (const auto& item : params->suggestions) {
matches_.push_back(BaseSearchProvider::CreateOnDeviceSearchSuggestion(
/*autocomplete_provider=*/this, /*input=*/params->input,
/*suggestion=*/base::UTF8ToUTF16(item), /*relevance=*/relevance--,
/*template_url=*/
template_url_service->GetDefaultSearchProvider(),
/*search_terms_data=*/
template_url_service->search_terms_data(),
/*accepted_suggestion=*/TemplateURLRef::NO_SUGGESTION_CHOSEN));
}
UMA_HISTOGRAM_TIMES("Omnibox.OnDeviceHeadSuggest.AsyncQueryTime",
base::TimeTicks::Now() - params->creation_time);
}
done_ = true;
listener_->OnProviderUpdate(true);
}
std::string OnDeviceHeadProvider::GetOnDeviceHeadModelFilename() const {
auto* model_update_listener = OnDeviceModelUpdateListener::GetInstance();
return model_update_listener != nullptr
? model_update_listener->model_filename()
: "";
}