blob: 645a9872ff33745485fb42ea55266d476e8d2442 [file] [log] [blame]
// Copyright 2013 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 "chrome/browser/ui/app_list/search/omnibox_provider.h"
#include <iterator>
#include <string>
#include <utility>
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/app_list/app_list_features.h"
#include "base/containers/flat_set.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/autocomplete/chrome_autocomplete_provider_client.h"
#include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h"
#include "chrome/browser/favicon/favicon_service_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
#include "chrome/browser/ui/app_list/search/common/types_util.h"
#include "chrome/browser/ui/app_list/search/omnibox_answer_result.h"
#include "chrome/browser/ui/app_list/search/omnibox_result.h"
#include "chrome/browser/ui/app_list/search/open_tab_result.h"
#include "chrome/browser/ui/app_list/search/ranking/util.h"
#include "components/favicon/core/favicon_service.h"
#include "components/omnibox/browser/autocomplete_classifier.h"
#include "components/omnibox/browser/autocomplete_input.h"
#include "components/search_engines/omnibox_focus_type.h"
#include "third_party/metrics_proto/omnibox_event.pb.h"
#include "url/gurl.h"
namespace app_list {
namespace {
using chromeos::string_matching::TokenizedString;
// Some omnibox answers overtrigger on short queries. This controls the minimum
// query length before they are displayed.
constexpr size_t kMinQueryLengthForCommonAnswers = 4u;
bool IsDriveUrl(const GURL& url) {
// Returns true if the |url| points to a Drive Web host.
const std::string& host = url.host();
return host == "drive.google.com" || host == "docs.google.com";
}
// Returns true if the match is an answer, including calculator answers.
bool IsAnswer(const AutocompleteMatch& match) {
return match.answer.has_value() ||
match.type == AutocompleteMatchType::CALCULATOR;
}
// Some answer result types overtrigger on short queries. Returns true if an
// answer should be filtered.
bool ShouldFilterAnswer(const AutocompleteMatch& match,
const std::u16string& query) {
// TODO(crbug.com/1258415): Move this to the filtering ranker once more
// detailed result subtype info is exposed by ChromeSearchResult.
if (query.size() >= kMinQueryLengthForCommonAnswers || !match.answer) {
return false;
}
switch (match.answer.value().type()) {
case SuggestionAnswer::ANSWER_TYPE_DICTIONARY:
case SuggestionAnswer::ANSWER_TYPE_TRANSLATION:
return true;
default:
return false;
}
}
int ProviderTypes() {
// We use all the default providers except for the document provider, which
// suggests Drive files on enterprise devices. This is disabled to avoid
// duplication with search results from DriveFS.
int providers = AutocompleteClassifier::DefaultOmniboxProviders() &
~AutocompleteProvider::TYPE_DOCUMENT;
if (ash::features::IsProductivityLauncherEnabled() &&
base::GetFieldTrialParamByFeatureAsBool(
ash::features::kProductivityLauncher, "enable_open_tab", true)) {
providers |= AutocompleteProvider::TYPE_OPEN_TAB;
}
return providers;
}
void RemoveDuplicates(std::vector<std::unique_ptr<OmniboxResult>>& results) {
// Sort the results by deduplication priority and then filter from left to
// right. This ensures that higher priority results are retained.
sort(results.begin(), results.end(),
[](const std::unique_ptr<OmniboxResult>& a,
const std::unique_ptr<OmniboxResult>& b) {
return a->dedup_priority() > b->dedup_priority();
});
base::flat_set<std::string> seen_ids;
for (auto iter = results.begin(); iter != results.end();) {
bool inserted = seen_ids.insert((*iter)->id()).second;
if (!inserted) {
// C++11:: The return value of erase(iter) is an iterator pointing to the
// next element in the container.
iter = results.erase(iter);
} else {
++iter;
}
}
}
} // namespace
OmniboxProvider::OmniboxProvider(Profile* profile,
AppListControllerDelegate* list_controller)
: profile_(profile),
list_controller_(list_controller),
controller_(std::make_unique<AutocompleteController>(
std::make_unique<ChromeAutocompleteProviderClient>(profile),
ProviderTypes())),
favicon_cache_(FaviconServiceFactory::GetForProfile(
profile,
ServiceAccessType::EXPLICIT_ACCESS),
HistoryServiceFactory::GetForProfile(
profile,
ServiceAccessType::EXPLICIT_ACCESS)) {
controller_->AddObserver(this);
}
OmniboxProvider::~OmniboxProvider() {}
void OmniboxProvider::Start(const std::u16string& query) {
ClearResultsSilently();
last_query_ = query;
last_tokenized_query_.emplace(query, TokenizedString::Mode::kCamelCase);
controller_->Stop(false);
query_finished_ = false;
// The new page classification value(CHROMEOS_APP_LIST) is introduced
// to differentiate the suggest requests initiated by ChromeOS app_list from
// the ones by Chrome omnibox.
input_ =
AutocompleteInput(query, metrics::OmniboxEventProto::CHROMEOS_APP_LIST,
ChromeAutocompleteSchemeClassifier(profile_));
// Sets the |from_omnibox_focus| flag to enable ZeroSuggestProvider to process
// the requests from app_list.
if (input_.text().empty()) {
input_.set_focus_type(OmniboxFocusType::ON_FOCUS);
is_zero_state_input_ = true;
} else {
is_zero_state_input_ = false;
}
query_start_time_ = base::TimeTicks::Now();
controller_->Start(input_);
}
void OmniboxProvider::StartZeroState() {
// Do not perform zero-state queries in the productivity launcher, because
// Omnibox is not shown in zero-state.
if (!app_list_features::IsCategoricalSearchEnabled()) {
Start(std::u16string());
}
}
ash::AppListSearchResultType OmniboxProvider::ResultType() const {
return ash::AppListSearchResultType::kOmnibox;
}
void OmniboxProvider::PopulateFromACResult(const AutocompleteResult& result) {
SearchProvider::Results new_results;
new_results.reserve(result.size());
std::vector<std::unique_ptr<OmniboxResult>> list_results;
list_results.reserve(result.size());
for (const AutocompleteMatch& match : result) {
// Do not return a match in any of these cases:
// - The URL is invalid.
// - The URL points to Drive Web and is not an open tab. The Drive search
// and zero-state providers surface Drive results.
// - The URL points to a local file. The Local file search and zero-state
// providers handle local file results, even if they've been opened in the
// browser.
const bool is_drive = IsDriveUrl(match.destination_url) &&
match.type != AutocompleteMatchType::OPEN_TAB;
if (!match.destination_url.is_valid() || is_drive ||
match.destination_url.SchemeIsFile()) {
continue;
}
if (!is_zero_state_input_ && IsAnswer(match) &&
!ShouldFilterAnswer(match, last_query_)) {
new_results.emplace_back(std::make_unique<OmniboxAnswerResult>(
profile_, list_controller_, controller_.get(), match, last_query_));
} else if (match.type == AutocompleteMatchType::OPEN_TAB) {
DCHECK(last_tokenized_query_.has_value());
new_results.emplace_back(std::make_unique<OpenTabResult>(
profile_, list_controller_, &favicon_cache_,
last_tokenized_query_.value(), match));
} else {
list_results.emplace_back(std::make_unique<OmniboxResult>(
profile_, list_controller_, controller_.get(), &favicon_cache_,
input_, match, is_zero_state_input_));
}
}
// Deduplicate the list results and then move-concatenate it into new_results.
RemoveDuplicates(list_results);
std::move(list_results.begin(), list_results.end(),
std::back_inserter(new_results));
if (controller_->done() && !query_finished_) {
query_finished_ = true;
SwapResults(&new_results);
}
}
void OmniboxProvider::OnResultChanged(AutocompleteController* controller,
bool default_match_changed) {
DCHECK(controller == controller_.get());
// Record the query latency.
RecordQueryLatencyHistogram();
PopulateFromACResult(controller_->result());
}
void OmniboxProvider::RecordQueryLatencyHistogram() {
base::TimeDelta query_latency = base::TimeTicks::Now() - query_start_time_;
if (is_zero_state_input_) {
UMA_HISTOGRAM_TIMES("Apps.AppList.OmniboxProvider.ZeroStateLatency",
query_latency);
} else {
UMA_HISTOGRAM_TIMES("Apps.AppList.OmniboxProvider.QueryTime",
query_latency);
}
}
} // namespace app_list