blob: 250469a464e7221f09207fd2129d52488cdcc9c2 [file] [log] [blame]
// Copyright 2012 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/autocomplete_provider.h"
#include <stddef.h>
#include <memory>
#include <optional>
#include <string>
#include "base/base64url.h"
#include "base/command_line.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/values_test_util.h"
#include "components/omnibox/browser/autocomplete_controller.h"
#include "components/omnibox/browser/autocomplete_input.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/autocomplete_match_classification.h"
#include "components/omnibox/browser/autocomplete_provider_listener.h"
#include "components/omnibox/browser/fake_autocomplete_provider_client.h"
#include "components/omnibox/browser/keyword_provider.h"
#include "components/omnibox/browser/mock_autocomplete_provider_client.h"
#include "components/omnibox/browser/omnibox_prefs.h"
#include "components/omnibox/browser/omnibox_triggered_feature_service.h"
#include "components/omnibox/browser/search_provider.h"
#include "components/omnibox/browser/suggestion_group_util.h"
#include "components/omnibox/browser/zero_suggest_provider.h"
#include "components/omnibox/common/omnibox_feature_configs.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/open_from_clipboard/fake_clipboard_recent_content.h"
#include "components/prefs/testing_pref_service.h"
#include "components/search_engines/search_engines_switches.h"
#include "components/search_engines/search_engines_test_environment.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_service.h"
#include "components/search_engines/template_url_service_client.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/metrics_proto/omnibox_event.pb.h"
#include "third_party/metrics_proto/omnibox_focus_type.pb.h"
#include "third_party/omnibox_proto/types.pb.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/gfx/image/image_util.h"
#include "url/url_constants.h"
namespace {
const size_t kResultsPerProvider = 3;
const char16_t kTestTemplateURLKeyword[] = u"t";
class TestingSchemeClassifier : public AutocompleteSchemeClassifier {
public:
TestingSchemeClassifier() = default;
TestingSchemeClassifier(const TestingSchemeClassifier&) = delete;
TestingSchemeClassifier& operator=(const TestingSchemeClassifier&) = delete;
metrics::OmniboxInputType GetInputTypeForScheme(
const std::string& scheme) const override {
DCHECK_EQ(scheme, base::ToLowerASCII(scheme));
return (scheme == url::kHttpScheme || scheme == url::kHttpsScheme)
? metrics::OmniboxInputType::URL
: metrics::OmniboxInputType::EMPTY;
}
};
// AutocompleteController::Observer implementation that runs the provided
// closure, when the controller is done.
class TestAutocompleteControllerObserver
: public AutocompleteController::Observer {
public:
TestAutocompleteControllerObserver() = default;
~TestAutocompleteControllerObserver() override = default;
TestAutocompleteControllerObserver(
const TestAutocompleteControllerObserver&) = delete;
TestAutocompleteControllerObserver& operator=(
const TestAutocompleteControllerObserver&) = delete;
void set_closure(const base::RepeatingClosure& closure) {
closure_ = closure;
}
void set_is_observing() { is_observing_ = true; }
bool is_observing() const { return is_observing_; }
private:
// AutocompleteController::Observer implementation.
void OnResultChanged(AutocompleteController* controller,
bool default_match_changed) override {
if (controller->done() && closure_) {
closure_.Run();
}
}
base::RepeatingClosure closure_;
bool is_observing_ = false;
};
// AutocompleteProviderListener implementation that runs the provided closure,
// when the provider is done. Informs the controller for non-prefetch requests.
class TestAutocompleteProviderListener : public AutocompleteProviderListener {
public:
explicit TestAutocompleteProviderListener(AutocompleteController* controller)
: controller_(controller) {}
~TestAutocompleteProviderListener() override = default;
TestAutocompleteProviderListener(const TestAutocompleteProviderListener&) =
delete;
TestAutocompleteProviderListener& operator=(
const TestAutocompleteProviderListener&) = delete;
void set_closure(const base::RepeatingClosure& closure) {
closure_ = closure;
}
// TestAutocompleteProviderListener:
void OnProviderUpdate(bool updated_matches,
const AutocompleteProvider* provider) override {
controller_->OnProviderUpdate(updated_matches, provider);
if (closure_)
closure_.Run();
}
// Used by TestProvider to notify it is done with a prefetch request.
void OnProviderFinishedPrefetch() {
if (closure_)
closure_.Run();
}
private:
raw_ptr<AutocompleteController> controller_;
base::RepeatingClosure closure_;
};
} // namespace
// Autocomplete provider that provides known results. Note that this is
// refcounted so that it can also be a task on the message loop.
class TestProvider : public AutocompleteProvider {
public:
TestProvider(int relevance,
const std::u16string& prefix,
const std::u16string& match_keyword,
AutocompleteProviderClient* client)
: AutocompleteProvider(AutocompleteProvider::TYPE_SEARCH),
relevance_(relevance),
prefix_(prefix),
match_keyword_(match_keyword),
client_(client) {}
TestProvider(const TestProvider&) = delete;
TestProvider& operator=(const TestProvider&) = delete;
// AutocompleteProvider:
void StartPrefetch(const AutocompleteInput& input) override;
void Start(const AutocompleteInput& input, bool minimal_changes) override;
void ResizeMatches(size_t max_matches, bool ml_scoring_enabled);
void set_supports_prefetch(const bool supports_prefetch) {
supports_prefetch_ = supports_prefetch;
}
bool prefetch_done() { return prefetch_done_; }
void set_closure(const base::RepeatingClosure& closure) {
closure_ = closure;
}
void set_matches(const ACMatches& matches) { matches_ = matches; }
const ACMatches& get_matches() { return matches_; }
protected:
~TestProvider() override = default;
void OnNonPrefetchRequestDone();
void OnPrefetchRequestDone();
void AddResults(int start_at, int num);
void AddResultsWithSearchTermsArgs(
int start_at,
int num,
AutocompleteMatch::Type type,
const TemplateURLRef::SearchTermsArgs& search_terms_args);
int relevance_;
const std::u16string prefix_;
const std::u16string match_keyword_;
raw_ptr<AutocompleteProviderClient> client_;
bool supports_prefetch_{false};
bool prefetch_done_{true};
base::RepeatingClosure closure_;
};
void TestProvider::StartPrefetch(const AutocompleteInput& input) {
if (!supports_prefetch_) {
return;
}
matches_.clear();
prefetch_done_ = false;
if (closure_) {
closure_.Run();
}
if (!input.omit_asynchronous_matches()) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&TestProvider::OnPrefetchRequestDone,
base::Unretained(this)));
} else {
OnPrefetchRequestDone();
}
}
void TestProvider::OnPrefetchRequestDone() {
AddResults(0, kResultsPerProvider);
prefetch_done_ = true;
for (AutocompleteProviderListener* listener : listeners_) {
static_cast<TestAutocompleteProviderListener*>(listener)
->OnProviderFinishedPrefetch();
}
}
void TestProvider::Start(const AutocompleteInput& input, bool minimal_changes) {
if (minimal_changes)
return;
matches_.clear();
if (input.IsZeroSuggest()) {
return;
}
// Generate 4 results synchronously, the rest later.
AddResults(0, 1);
AddResultsWithSearchTermsArgs(1, 1,
AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED,
TemplateURLRef::SearchTermsArgs(u"echo"));
AddResultsWithSearchTermsArgs(2, 1, AutocompleteMatchType::NAVSUGGEST,
TemplateURLRef::SearchTermsArgs(u"nav"));
AddResultsWithSearchTermsArgs(3, 1, AutocompleteMatchType::SEARCH_SUGGEST,
TemplateURLRef::SearchTermsArgs(u"query"));
if (!input.omit_asynchronous_matches()) {
done_ = false;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&TestProvider::OnNonPrefetchRequestDone,
base::Unretained(this)));
}
}
void TestProvider::ResizeMatches(size_t max_matches, bool ml_scoring_enabled) {
AutocompleteProvider::ResizeMatches(max_matches, ml_scoring_enabled);
}
void TestProvider::OnNonPrefetchRequestDone() {
AddResults(1, kResultsPerProvider);
done_ = true;
NotifyListeners(true);
}
void TestProvider::AddResults(int start_at, int num) {
AddResultsWithSearchTermsArgs(
start_at, num, AutocompleteMatchType::URL_WHAT_YOU_TYPED,
TemplateURLRef::SearchTermsArgs(std::u16string()));
}
void TestProvider::AddResultsWithSearchTermsArgs(
int start_at,
int num,
AutocompleteMatch::Type type,
const TemplateURLRef::SearchTermsArgs& search_terms_args) {
for (int i = start_at; i < num; i++) {
AutocompleteMatch match(this, relevance_ - i, false, type);
match.fill_into_edit = prefix_ + base::UTF8ToUTF16(base::NumberToString(i));
match.destination_url = GURL(base::UTF16ToUTF8(match.fill_into_edit));
match.allowed_to_be_default_match = true;
match.contents = match.fill_into_edit;
match.contents_class.push_back(
ACMatchClassification(0, ACMatchClassification::NONE));
match.description = match.fill_into_edit;
match.description_class.push_back(
ACMatchClassification(0, ACMatchClassification::NONE));
match.search_terms_args =
std::make_unique<TemplateURLRef::SearchTermsArgs>(search_terms_args);
if (!match_keyword_.empty()) {
match.keyword = match_keyword_;
ASSERT_NE(nullptr,
match.GetTemplateURL(client_->GetTemplateURLService(), false));
}
matches_.push_back(match);
}
}
// Helper class to make running tests of ClassifyAllMatchesInString() more
// convenient.
class ClassifyTest {
public:
ClassifyTest(const std::u16string& text,
const bool text_is_query,
ACMatchClassifications matches);
~ClassifyTest();
ACMatchClassifications RunTest(const std::u16string& find_text);
private:
const std::u16string text_;
const bool text_is_query_;
const ACMatchClassifications matches_;
};
ClassifyTest::ClassifyTest(const std::u16string& text,
const bool text_is_query,
ACMatchClassifications matches)
: text_(text), text_is_query_(text_is_query), matches_(matches) {}
ClassifyTest::~ClassifyTest() = default;
ACMatchClassifications ClassifyTest::RunTest(const std::u16string& find_text) {
return ClassifyAllMatchesInString(find_text, text_, text_is_query_, matches_);
}
class AutocompleteProviderTest : public testing::Test {
public:
AutocompleteProviderTest();
~AutocompleteProviderTest() override;
AutocompleteProviderTest(const AutocompleteProviderTest&) = delete;
AutocompleteProviderTest& operator=(const AutocompleteProviderTest&) = delete;
void CopyResults();
protected:
struct KeywordTestData {
const std::u16string fill_into_edit;
const std::u16string keyword;
const std::u16string expected_associated_keyword;
};
struct SuggestionGroupsTestData {
omnibox::GroupConfigMap suggestion_groups_map;
std::vector<std::optional<omnibox::GroupId>> suggestion_group_ids;
};
struct SearchboxStatsTestData {
const AutocompleteMatch::Type match_type;
std::optional<omnibox::GroupId> group_id;
const omnibox::metrics::ChromeSearchboxStats expected_searchbox_stats;
omnibox::SuggestType type;
base::flat_set<omnibox::SuggestSubtype> subtypes;
};
void SetUp() override {
omnibox::RegisterProfilePrefs(
static_cast<sync_preferences::TestingPrefServiceSyncable*>(
client_->GetPrefs())
->registry());
}
// Registers a test TemplateURL under the given keyword.
void RegisterTemplateURL(const std::u16string& keyword,
const std::string& template_url,
const std::string& image_url,
const std::string& image_url_post_params);
// Resets |controller_| with two TestProviders. |provider1_ptr| and
// |provider2_ptr| are updated to point to the new providers if non-NULL.
void ResetControllerWithTestProviders(bool same_destinations,
TestProvider** provider1_ptr,
TestProvider** provider2_ptr);
// Runs a query on the input "a", and makes sure both providers' input is
// properly collected.
void RunTest();
// Constructs an AutocompleteResult from |match_data|, sets the |controller_|
// to pretend it was running against input |input|, calls the |controller_|'s
// UpdateAssociatedKeywords, and checks that the matches have associated
// keywords as expected.
void RunKeywordTest(const std::u16string& input,
base::span<const KeywordTestData> match_data);
void UpdateResultsWithSuggestionGroupsTestData(
const SuggestionGroupsTestData& test_data);
void RunSearchboxStatsTest(
base::span<const SearchboxStatsTestData> sbs_test_data,
bool input_is_zero_suggest);
void RunQuery(const std::string& query, bool allow_exact_keyword_match);
void ResetControllerWithKeywordAndSearchProviders();
void ResetControllerWithKeywordProvider();
void RunExactKeymatchTest(bool allow_exact_keyword_match);
// Returns match.destination_url as it would be set by
// AutocompleteController::UpdateMatchDestinationURL().
GURL GetDestinationURL(AutocompleteMatch& match,
base::TimeDelta query_formulation_time) const;
void set_remote_search_feature_triggered_in_session(bool value) {
client_->GetOmniboxTriggeredFeatureService()->ResetSession();
if (value) {
client_->GetOmniboxTriggeredFeatureService()->FeatureTriggered(
metrics::OmniboxEventProto_Feature_REMOTE_SEARCH_FEATURE);
}
}
void set_current_page_classification(
metrics::OmniboxEventProto::PageClassification classification) {
controller_->input_.current_page_classification_ = classification;
}
void add_zero_suggest_provider_experiment_stats_v2(
const omnibox::metrics::ChromeSearchboxStats::ExperimentStatsV2&
experiment_stat_v2) {
auto& experiment_stats_v2s =
const_cast<SearchSuggestionParser::ExperimentStatsV2s&>(
controller_->zero_suggest_provider_->experiment_stats_v2s());
experiment_stats_v2s.push_back(experiment_stat_v2);
}
// Resets the controller with the given |type|. |type| is a bitmap containing
// AutocompleteProvider::Type values that will (potentially, depending on
// platform, flags, etc.) be instantiated.
void ResetControllerWithType(int type);
base::test::TaskEnvironment task_environment_;
TestAutocompleteControllerObserver autocomplete_controller_observer_;
search_engines::SearchEnginesTestEnvironment search_engines_test_environment_;
std::unique_ptr<AutocompleteController> controller_;
// Owned by |controller_|.
raw_ptr<MockAutocompleteProviderClient> client_;
// `result_` may contain a `raw_ptr` (e.g. `AutocompleteMatch::provider) to
// the `controller_`. This means that (per //docs/dangling_ptr_guide.md) the
// `result_` field needs to be declared *after* the `controller_` field.
AutocompleteResult result_;
// Used to ensure that |client_| ownership has been passed to |controller_|
// exactly once.
bool client_owned_{};
};
AutocompleteProviderTest::AutocompleteProviderTest()
: client_(new FakeAutocompleteProviderClient()) {}
AutocompleteProviderTest::~AutocompleteProviderTest() {
EXPECT_TRUE(client_owned_);
}
void AutocompleteProviderTest::RegisterTemplateURL(
const std::u16string& keyword,
const std::string& template_url,
const std::string& image_url = "",
const std::string& image_url_post_params = "") {
TemplateURLData data;
data.SetURL(template_url);
data.SetShortName(keyword);
data.SetKeyword(keyword);
data.image_url = image_url;
data.image_url_post_params = image_url_post_params;
TemplateURLService* turl_model = client_->GetTemplateURLService();
TemplateURL* default_turl =
turl_model->Add(std::make_unique<TemplateURL>(data));
turl_model->SetUserSelectedDefaultSearchProvider(default_turl);
turl_model->Load();
TemplateURLID default_provider_id = default_turl->id();
ASSERT_NE(0, default_provider_id);
}
void AutocompleteProviderTest::ResetControllerWithTestProviders(
bool same_destinations,
TestProvider** provider1_ptr,
TestProvider** provider2_ptr) {
// TODO: Move it outside this method, after refactoring the existing
// unit tests. Specifically:
// (1) Make sure that AutocompleteMatch.keyword is set iff there is
// a corresponding call to RegisterTemplateURL; otherwise the
// controller flow will crash; this practically means that
// RunTests/ResetControllerXXX/RegisterTemplateURL should
// be coordinated with each other.
// (2) Inject test arguments rather than rely on the hardcoded values, e.g.
// don't rely on kResultsPerProvided and default relevance ordering
// (B > A).
RegisterTemplateURL(kTestTemplateURLKeyword,
"http://foo/{searchTerms}/{google:assistedQueryStats}");
AutocompleteController::Providers providers;
// Construct two new providers, with either the same or different prefixes.
TestProvider* provider1 = new TestProvider(kResultsPerProvider, u"http://a",
kTestTemplateURLKeyword, client_);
providers.push_back(provider1);
TestProvider* provider2 = new TestProvider(
kResultsPerProvider * 2, same_destinations ? u"http://a" : u"http://b",
std::u16string(), client_);
providers.push_back(provider2);
// Reset the controller to contain our new providers.
ResetControllerWithType(0);
// We're going to swap the providers vector, but the old vector should be
// empty so no elements need to be freed at this point.
EXPECT_TRUE(controller_->providers_.empty());
controller_->providers_.swap(providers);
provider1->AddListener(controller_.get());
provider2->AddListener(controller_.get());
if (provider1_ptr)
*provider1_ptr = provider1;
if (provider2_ptr)
*provider2_ptr = provider2;
}
void AutocompleteProviderTest::ResetControllerWithKeywordAndSearchProviders() {
// Reset the default TemplateURL.
TemplateURLData data;
data.SetShortName(u"default");
data.SetKeyword(u"default");
data.SetURL("http://defaultturl/{searchTerms}");
TemplateURLService* turl_model = client_->GetTemplateURLService();
TemplateURL* default_turl =
turl_model->Add(std::make_unique<TemplateURL>(data));
turl_model->SetUserSelectedDefaultSearchProvider(default_turl);
TemplateURLID default_provider_id = default_turl->id();
ASSERT_NE(0, default_provider_id);
// Create another TemplateURL for KeywordProvider.
TemplateURLData data2;
data2.SetShortName(u"k");
data2.SetKeyword(u"k");
data2.SetURL("http://keyword/{searchTerms}");
TemplateURL* keyword_turl =
turl_model->Add(std::make_unique<TemplateURL>(data2));
ASSERT_NE(0, keyword_turl->id());
ResetControllerWithType(AutocompleteProvider::TYPE_KEYWORD |
AutocompleteProvider::TYPE_SEARCH |
AutocompleteProvider::TYPE_ZERO_SUGGEST);
}
void AutocompleteProviderTest::ResetControllerWithKeywordProvider() {
TemplateURLService* turl_model = client_->GetTemplateURLService();
// Create a TemplateURL for KeywordProvider.
TemplateURLData data;
data.SetShortName(u"foo.com");
data.SetKeyword(u"foo.com");
data.SetURL("http://foo.com/{searchTerms}");
data.is_active = TemplateURLData::ActiveStatus::kTrue;
TemplateURL* keyword_turl =
turl_model->Add(std::make_unique<TemplateURL>(data));
ASSERT_NE(0, keyword_turl->id());
// Make a TemplateURL for KeywordProvider that a shorter version of the
// first.
data.SetShortName(u"f");
data.SetKeyword(u"f");
data.SetURL("http://f.com/{searchTerms}");
data.is_active = TemplateURLData::ActiveStatus::kTrue;
keyword_turl = turl_model->Add(std::make_unique<TemplateURL>(data));
ASSERT_NE(0, keyword_turl->id());
// Create another TemplateURL for KeywordProvider.
data.SetShortName(u"bar.com");
data.SetKeyword(u"bar.com");
data.SetURL("http://bar.com/{searchTerms}");
data.is_active = TemplateURLData::ActiveStatus::kTrue;
keyword_turl = turl_model->Add(std::make_unique<TemplateURL>(data));
ASSERT_NE(0, keyword_turl->id());
ResetControllerWithType(AutocompleteProvider::TYPE_KEYWORD);
}
void AutocompleteProviderTest::ResetControllerWithType(int type) {
EXPECT_FALSE(client_owned_);
controller_ = std::make_unique<AutocompleteController>(
base::WrapUnique(client_.get()), type);
client_owned_ = true;
}
void AutocompleteProviderTest::RunTest() {
RunQuery("a", true);
}
void AutocompleteProviderTest::RunKeywordTest(
const std::u16string& input,
base::span<const KeywordTestData> match_data) {
ACMatches matches;
for (size_t i = 0; i < match_data.size(); ++i) {
AutocompleteMatch match;
match.relevance = 1000; // Arbitrary non-zero value.
match.allowed_to_be_default_match = true;
match.fill_into_edit = match_data[i].fill_into_edit;
match.transition = ui::PAGE_TRANSITION_KEYWORD;
match.keyword = match_data[i].keyword;
matches.push_back(match);
}
AutocompleteInput autocomplete_input(
input,
metrics::OmniboxEventProto::INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS,
TestingSchemeClassifier());
autocomplete_input.set_prefer_keyword(true);
controller_->input_ = autocomplete_input;
AutocompleteResult result;
result.AppendMatches(matches);
controller_->UpdateAssociatedKeywords(&result);
for (size_t j = 0; j < result.size(); ++j) {
EXPECT_EQ(match_data[j].expected_associated_keyword,
result.match_at(j)->associated_keyword
? result.match_at(j)->associated_keyword->keyword
: std::u16string());
}
}
void AutocompleteProviderTest::UpdateResultsWithSuggestionGroupsTestData(
const SuggestionGroupsTestData& test_data) {
// Create new matches and add to the result.
size_t relevance = 1000;
ACMatches matches;
for (auto suggestion_group_id : test_data.suggestion_group_ids) {
AutocompleteMatch match(nullptr, relevance--, false,
AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED);
if (suggestion_group_id.has_value()) {
match.suggestion_group_id = suggestion_group_id.value();
}
matches.push_back(match);
}
result_.ClearMatches();
result_.AppendMatches(matches);
// Update the result with the suggestion groups information.
result_.MergeSuggestionGroupsMap(test_data.suggestion_groups_map);
// Group matches with group IDs and move them to the bottom of the result set.
result_.GroupAndDemoteMatchesInGroups();
}
void AutocompleteProviderTest::RunSearchboxStatsTest(
base::span<const SearchboxStatsTestData> sbs_test_data,
bool input_is_zero_suggest) {
if (input_is_zero_suggest) {
// Prepare the input.
AutocompleteInput input(u"", metrics::OmniboxEventProto::OTHER,
TestingSchemeClassifier());
input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS);
controller_->input_ = input;
}
// Prepare the results.
const size_t kMaxRelevance = 1000;
ACMatches matches;
for (size_t i = 0; i < sbs_test_data.size(); ++i) {
AutocompleteMatch match(nullptr, kMaxRelevance - i, false,
sbs_test_data[i].match_type);
match.suggestion_group_id = sbs_test_data[i].group_id;
match.allowed_to_be_default_match = true;
match.keyword = kTestTemplateURLKeyword;
match.search_terms_args =
std::make_unique<TemplateURLRef::SearchTermsArgs>(std::u16string());
match.suggest_type = sbs_test_data[i].type;
match.subtypes = sbs_test_data[i].subtypes;
matches.push_back(match);
}
result_.Reset();
result_.AppendMatches(matches);
result_.MergeSuggestionGroupsMap(
omnibox::BuildDefaultGroupsForInput(AutocompleteInput()));
result_.set_zero_prefix_enabled_in_session(input_is_zero_suggest);
// Update Searchbox stats.
controller_->UpdateSearchboxStats(&result_);
// Verify data.
for (size_t i = 0; i < sbs_test_data.size(); ++i) {
std::string serialized_searchbox_stats;
result_.match_at(i)->search_terms_args->searchbox_stats.SerializeToString(
&serialized_searchbox_stats);
std::string encoded_searchbox_stats;
base::Base64UrlEncode(serialized_searchbox_stats,
base::Base64UrlEncodePolicy::OMIT_PADDING,
&encoded_searchbox_stats);
std::string expected_serialized_searchbox_stats;
sbs_test_data[i].expected_searchbox_stats.SerializeToString(
&expected_serialized_searchbox_stats);
std::string expected_encoded_searchbox_stats;
base::Base64UrlEncode(expected_serialized_searchbox_stats,
base::Base64UrlEncodePolicy::OMIT_PADDING,
&expected_encoded_searchbox_stats);
EXPECT_EQ(expected_encoded_searchbox_stats, encoded_searchbox_stats);
}
}
void AutocompleteProviderTest::RunQuery(const std::string& query,
bool allow_exact_keyword_match) {
result_.ClearMatches();
AutocompleteInput input(base::ASCIIToUTF16(query),
metrics::OmniboxEventProto::OTHER,
TestingSchemeClassifier());
input.set_prevent_inline_autocomplete(true);
input.set_allow_exact_keyword_match(allow_exact_keyword_match);
base::RunLoop run_loop;
autocomplete_controller_observer_.set_closure(
run_loop.QuitClosure().Then(base::BindRepeating(
&AutocompleteProviderTest::CopyResults, base::Unretained(this))));
if (!autocomplete_controller_observer_.is_observing()) {
controller_->AddObserver(&autocomplete_controller_observer_);
autocomplete_controller_observer_.set_is_observing();
}
controller_->Start(input);
if (!controller_->done())
run_loop.Run();
}
void AutocompleteProviderTest::RunExactKeymatchTest(
bool allow_exact_keyword_match) {
// Send the controller input which exactly matches the keyword provider we
// created in ResetControllerWithKeywordAndSearchProviders(). The default
// match should thus be a search-other-engine match iff
// |allow_exact_keyword_match| is true. Regardless, the match should
// be from SearchProvider. (It provides all verbatim search matches,
// keyword or not.)
RunQuery("k test", allow_exact_keyword_match);
EXPECT_EQ(AutocompleteProvider::TYPE_SEARCH,
controller_->result().default_match()->provider->type());
EXPECT_EQ(allow_exact_keyword_match
? AutocompleteMatchType::SEARCH_OTHER_ENGINE
: AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED,
controller_->result().default_match()->type);
}
void AutocompleteProviderTest::CopyResults() {
result_.CopyMatchesFrom(controller_->result());
}
GURL AutocompleteProviderTest::GetDestinationURL(
AutocompleteMatch& match,
base::TimeDelta query_formulation_time) const {
controller_->UpdateMatchDestinationURLWithAdditionalSearchboxStats(
query_formulation_time, &match);
return match.destination_url;
}
// Tests that the default selection is set properly when updating results.
TEST_F(AutocompleteProviderTest, Query) {
TestProvider* provider1 = nullptr;
TestProvider* provider2 = nullptr;
ResetControllerWithTestProviders(false, &provider1, &provider2);
RunTest();
// Make sure the default match gets set to the highest relevance match. The
// highest relevance matches should come from the second provider.
EXPECT_EQ(
std::min(AutocompleteResult::GetMaxMatches(), kResultsPerProvider * 2),
result_.size());
ASSERT_TRUE(result_.default_match());
EXPECT_EQ(provider2, result_.default_match()->provider);
}
// Tests searchbox stats.
TEST_F(AutocompleteProviderTest, SearchboxStats) {
ResetControllerWithTestProviders(false, nullptr, nullptr);
RunTest();
ASSERT_EQ(
std::min(AutocompleteResult::GetMaxMatches(), kResultsPerProvider * 2),
result_.size());
// Now, check the results from the second provider, as they should not have
// searchbox stats set.
for (size_t i = 0; i < kResultsPerProvider; ++i) {
EXPECT_EQ(
0u,
result_.match_at(i)->search_terms_args->searchbox_stats.ByteSizeLong());
}
// The first provider has a test keyword, so the searchbox stats should be
// non-empty.
for (size_t i = kResultsPerProvider; i < result_.size(); ++i) {
EXPECT_NE(
0u,
result_.match_at(i)->search_terms_args->searchbox_stats.ByteSizeLong());
}
}
TEST_F(AutocompleteProviderTest, RemoveDuplicates) {
TestProvider* provider1 = nullptr;
TestProvider* provider2 = nullptr;
ResetControllerWithTestProviders(true, &provider1, &provider2);
RunTest();
// Make sure all the first provider's results were eliminated by the second
// provider's.
EXPECT_EQ(kResultsPerProvider, result_.size());
for (AutocompleteResult::const_iterator i(result_.begin());
i != result_.end(); ++i)
EXPECT_EQ(provider2, i->provider);
}
TEST_F(AutocompleteProviderTest, AllowExactKeywordMatch) {
ResetControllerWithKeywordAndSearchProviders();
RunExactKeymatchTest(true);
RunExactKeymatchTest(false);
}
// Ensures matches from (only) the default search provider respect any extra
// query params set on the command line.
TEST_F(AutocompleteProviderTest, ExtraQueryParams) {
ResetControllerWithKeywordAndSearchProviders();
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kExtraSearchQueryParams, "a=b");
RunExactKeymatchTest(true);
CopyResults();
ASSERT_EQ(1U, result_.size());
EXPECT_EQ("http://keyword/test",
result_.match_at(0)->destination_url.possibly_invalid_spec());
}
// Ensures matches from (only) the default search provider are curbed when in
// keyword mode.
TEST_F(AutocompleteProviderTest, CurbDefaultSuggestions) {
ResetControllerWithKeywordAndSearchProviders();
RunExactKeymatchTest(true);
CopyResults();
// DSE suggestions are curbed in keyword mode, so the default turl suggestion
// should not be present in the res=ults.
ASSERT_EQ(1U, result_.size());
EXPECT_EQ("http://keyword/test",
result_.match_at(0)->destination_url.possibly_invalid_spec());
}
// Test that redundant associated keywords are removed.
TEST_F(AutocompleteProviderTest, RedundantKeywordsIgnoredInResult) {
ResetControllerWithKeywordProvider();
{
KeywordTestData duplicate_url[] = {
{u"fo", std::u16string(), std::u16string()},
{u"foo.com", std::u16string(), u"foo.com"},
{u"foo.com", std::u16string(), std::u16string()}};
SCOPED_TRACE("Duplicate url");
RunKeywordTest(u"fo", duplicate_url);
}
{
KeywordTestData keyword_match[] = {
{u"foo.com", u"foo.com", std::u16string()},
{u"foo.com", std::u16string(), std::u16string()}};
SCOPED_TRACE("Duplicate url with keyword match");
RunKeywordTest(u"fo", keyword_match);
}
{
KeywordTestData multiple_keyword[] = {
{u"fo", std::u16string(), std::u16string()},
{u"foo.com", std::u16string(), u"foo.com"},
{u"foo.com", std::u16string(), std::u16string()},
{u"bar.com", std::u16string(), u"bar.com"},
};
SCOPED_TRACE("Duplicate url with multiple keywords");
RunKeywordTest(u"fo", multiple_keyword);
}
}
// Test that exact match keywords trump keywords associated with
// the match.
TEST_F(AutocompleteProviderTest, ExactMatchKeywords) {
ResetControllerWithKeywordProvider();
{
KeywordTestData keyword_match[] = {
{u"foo.com", std::u16string(), u"foo.com"}};
SCOPED_TRACE("keyword match as usual");
RunKeywordTest(u"fo", keyword_match);
}
// The same result set with an input of "f" (versus "fo") should get
// a different associated keyword because "f" is an exact match for
// a keyword and that should trump the keyword normally associated with
// this match.
{
KeywordTestData keyword_match[] = {{u"foo.com", std::u16string(), u"f"}};
SCOPED_TRACE("keyword exact match");
RunKeywordTest(u"f", keyword_match);
}
}
// Tests that the AutocompleteResult is updated with the suggestion group
// information and matches with group IDs are grouped and demoted correctly.
// Also verifies that:
// 1) headers are optional for suggestion groups.
// 2) suggestion groups are ordered based on their priories.
// 3) suggestion group IDs without associated suggestion group information are
// stripped away.
TEST_F(AutocompleteProviderTest, SuggestionGroups) {
ResetControllerWithKeywordAndSearchProviders();
const auto kRecommendedGroupId = omnibox::GROUP_PREVIOUS_SEARCH_RELATED;
const std::string kRecommended = "Recommended for you";
const auto kRecentSearchesGroupId =
omnibox::GROUP_PREVIOUS_SEARCH_RELATED_ENTITY_CHIPS;
const std::string kRecentSearches = "Recent Searches";
const auto kBadGroupId = omnibox::GROUP_INVALID;
{
// AutocompleteResult::GetHeaderForSuggestionGroup() returns an empty string
// for unknown suggestion group IDs.
EXPECT_EQ(u"", result_.GetHeaderForSuggestionGroup(kBadGroupId));
// AutocompleteResult::GetSectionForSuggestionGroup() returns
// omnibox::SECTION_DEFAULT for unknown suggestion group IDs.
EXPECT_EQ(omnibox::SECTION_DEFAULT,
result_.GetSectionForSuggestionGroup(kBadGroupId));
}
{
// Headers are optional for suggestion groups.
omnibox::GroupConfigMap suggestion_groups_map;
suggestion_groups_map[kRecommendedGroupId].set_header_text(kRecommended);
suggestion_groups_map[kRecommendedGroupId].set_section(
omnibox::SECTION_REMOTE_ZPS_4);
suggestion_groups_map[kRecentSearchesGroupId].set_section(
omnibox::SECTION_REMOTE_ZPS_3);
UpdateResultsWithSuggestionGroupsTestData({std::move(suggestion_groups_map),
{
{},
{},
{},
{kRecentSearchesGroupId},
{kRecommendedGroupId},
}});
EXPECT_FALSE(result_.match_at(0)->suggestion_group_id.has_value());
EXPECT_FALSE(result_.match_at(1)->suggestion_group_id.has_value());
EXPECT_FALSE(result_.match_at(2)->suggestion_group_id.has_value());
EXPECT_EQ(kRecentSearchesGroupId,
result_.match_at(3)->suggestion_group_id.value());
EXPECT_EQ(u"", result_.GetHeaderForSuggestionGroup(kRecentSearchesGroupId));
EXPECT_EQ(kRecommendedGroupId,
result_.match_at(4)->suggestion_group_id.value());
EXPECT_EQ(base::UTF8ToUTF16(kRecommended),
result_.GetHeaderForSuggestionGroup(kRecommendedGroupId));
}
{
// Suggestion groups are ordered based on their priories.
omnibox::GroupConfigMap suggestion_groups_map;
suggestion_groups_map[kRecommendedGroupId].set_header_text(kRecommended);
suggestion_groups_map[kRecommendedGroupId].set_section(
omnibox::SECTION_REMOTE_ZPS_3);
suggestion_groups_map[kRecentSearchesGroupId].set_header_text(
kRecentSearches);
suggestion_groups_map[kRecentSearchesGroupId].set_section(
omnibox::SECTION_REMOTE_ZPS_4);
UpdateResultsWithSuggestionGroupsTestData({std::move(suggestion_groups_map),
{
{},
{kRecentSearchesGroupId},
{},
{kRecommendedGroupId},
{kRecentSearchesGroupId},
}});
EXPECT_FALSE(result_.match_at(0)->suggestion_group_id.has_value());
EXPECT_FALSE(result_.match_at(1)->suggestion_group_id.has_value());
EXPECT_EQ(kRecommendedGroupId,
result_.match_at(2)->suggestion_group_id.value());
EXPECT_EQ(base::UTF8ToUTF16(kRecommended),
result_.GetHeaderForSuggestionGroup(kRecommendedGroupId));
EXPECT_EQ(kRecentSearchesGroupId,
result_.match_at(3)->suggestion_group_id.value());
EXPECT_EQ(base::UTF8ToUTF16(kRecentSearches),
result_.GetHeaderForSuggestionGroup(kRecentSearchesGroupId));
EXPECT_EQ(kRecentSearchesGroupId,
result_.match_at(4)->suggestion_group_id.value());
EXPECT_EQ(base::UTF8ToUTF16(kRecentSearches),
result_.GetHeaderForSuggestionGroup(kRecentSearchesGroupId));
}
{
// suggestion group IDs without associated suggestion group information are
// stripped away.
omnibox::GroupConfigMap suggestion_groups_map;
suggestion_groups_map[kRecommendedGroupId].set_header_text(kRecommended);
suggestion_groups_map[kRecentSearchesGroupId].set_header_text(
kRecentSearches);
UpdateResultsWithSuggestionGroupsTestData({std::move(suggestion_groups_map),
{
{kBadGroupId},
{kRecentSearchesGroupId},
{kRecommendedGroupId},
{kBadGroupId},
{kBadGroupId},
}});
EXPECT_FALSE(result_.match_at(0)->suggestion_group_id.has_value());
EXPECT_FALSE(result_.match_at(1)->suggestion_group_id.has_value());
EXPECT_FALSE(result_.match_at(2)->suggestion_group_id.has_value());
EXPECT_EQ(kRecentSearchesGroupId,
result_.match_at(3)->suggestion_group_id.value());
EXPECT_EQ(base::UTF8ToUTF16(kRecentSearches),
result_.GetHeaderForSuggestionGroup(kRecentSearchesGroupId));
EXPECT_EQ(kRecommendedGroupId,
result_.match_at(4)->suggestion_group_id.value());
EXPECT_EQ(base::UTF8ToUTF16(kRecommended),
result_.GetHeaderForSuggestionGroup(kRecommendedGroupId));
}
}
TEST_F(AutocompleteProviderTest, UpdateSearchboxStats) {
ResetControllerWithTestProviders(false, nullptr, nullptr);
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(omnibox::kCategoricalSuggestions);
{
omnibox::metrics::ChromeSearchboxStats searchbox_stats;
SCOPED_TRACE("No matches");
RunSearchboxStatsTest({}, /*input_is_zero_suggest=*/false);
}
// Note: See suggest.proto for the types and subtypes referenced below.
{
omnibox::metrics::ChromeSearchboxStats searchbox_stats;
searchbox_stats.set_client_name("chrome");
searchbox_stats.set_num_zero_prefix_suggestions_shown(0);
searchbox_stats.set_zero_prefix_enabled(false);
auto* available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(0);
available_suggestion->set_type(omnibox::TYPE_NATIVE_CHROME);
available_suggestion->add_subtypes(omnibox::SUBTYPE_OMNIBOX_ECHO_SEARCH);
SearchboxStatsTestData test_data[] = {
{AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED,
{/* GroupID */},
searchbox_stats,
omnibox::TYPE_NATIVE_CHROME}};
SCOPED_TRACE("One match");
RunSearchboxStatsTest(test_data, /*input_is_zero_suggest=*/false);
}
{
omnibox::metrics::ChromeSearchboxStats searchbox_stats;
searchbox_stats.set_client_name("chrome");
searchbox_stats.set_num_zero_prefix_suggestions_shown(0);
searchbox_stats.set_zero_prefix_enabled(false);
auto* available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(0);
available_suggestion->set_type(omnibox::TYPE_ENTITY);
available_suggestion->add_subtypes(omnibox::SUBTYPE_PERSONAL);
auto* assisted_query_info = searchbox_stats.mutable_assisted_query_info();
assisted_query_info->MergeFrom(searchbox_stats.available_suggestions(0));
SearchboxStatsTestData test_data[] = {
{AutocompleteMatchType::SEARCH_SUGGEST_ENTITY,
{/* GroupID */},
searchbox_stats,
omnibox::TYPE_ENTITY,
{omnibox::SUBTYPE_PERSONAL}}};
SCOPED_TRACE("One match with provider populated subtypes");
RunSearchboxStatsTest(test_data,
/*input_is_zero_suggest=*/false);
}
{
base::test::ScopedFeatureList features;
features.InitAndEnableFeature(
omnibox::kMostVisitedTilesHorizontalRenderGroup);
omnibox::ResetDefaultGroupsForTest();
omnibox::metrics::ChromeSearchboxStats searchbox_stats;
searchbox_stats.set_client_name("chrome");
searchbox_stats.set_num_zero_prefix_suggestions_shown(1);
searchbox_stats.set_zero_prefix_enabled(true);
auto* available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(0);
available_suggestion->set_type(omnibox::TYPE_QUERY);
available_suggestion->add_subtypes(
omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_QUERIES);
auto* assisted_query_info = searchbox_stats.mutable_assisted_query_info();
assisted_query_info->MergeFrom(searchbox_stats.available_suggestions(0));
SearchboxStatsTestData test_data[] = {
{AutocompleteMatchType::SEARCH_SUGGEST,
{omnibox::GROUP_MOBILE_MOST_VISITED},
searchbox_stats,
omnibox::TYPE_QUERY,
{omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_QUERIES}},
{AutocompleteMatchType::SEARCH_SUGGEST,
{omnibox::GROUP_MOBILE_MOST_VISITED},
searchbox_stats,
omnibox::TYPE_QUERY,
{omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_QUERIES}},
{AutocompleteMatchType::SEARCH_SUGGEST,
{omnibox::GROUP_MOBILE_MOST_VISITED},
searchbox_stats,
omnibox::TYPE_QUERY,
{omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_QUERIES}},
};
SCOPED_TRACE("Multiple matches in horizontal render group");
RunSearchboxStatsTest(test_data, /*input_is_zero_suggest=*/true);
}
{
base::test::ScopedFeatureList features;
features.InitAndEnableFeature(
omnibox::kMostVisitedTilesHorizontalRenderGroup);
omnibox::ResetDefaultGroupsForTest();
omnibox::metrics::ChromeSearchboxStats searchbox_stats;
searchbox_stats.set_client_name("chrome");
searchbox_stats.set_num_zero_prefix_suggestions_shown(3);
searchbox_stats.set_zero_prefix_enabled(true);
auto* available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(0);
available_suggestion->set_type(omnibox::TYPE_ENTITY);
available_suggestion->add_subtypes(omnibox::SUBTYPE_ZERO_PREFIX);
available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(1);
available_suggestion->set_type(omnibox::TYPE_QUERY);
available_suggestion->add_subtypes(
omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_QUERIES);
available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(2);
available_suggestion->set_type(omnibox::TYPE_ENTITY);
available_suggestion->add_subtypes(omnibox::SUBTYPE_ZERO_PREFIX);
auto stats_0 = searchbox_stats;
auto* assisted_query_info = stats_0.mutable_assisted_query_info();
assisted_query_info->MergeFrom(searchbox_stats.available_suggestions(0));
auto stats_1 = searchbox_stats;
assisted_query_info = stats_1.mutable_assisted_query_info();
assisted_query_info->MergeFrom(searchbox_stats.available_suggestions(1));
auto stats_2 = searchbox_stats;
assisted_query_info = stats_2.mutable_assisted_query_info();
assisted_query_info->MergeFrom(searchbox_stats.available_suggestions(2));
SearchboxStatsTestData test_data[] = {
// Entity Suggestion
{AutocompleteMatchType::SEARCH_SUGGEST_ENTITY,
{/* GroupID */},
stats_0,
omnibox::TYPE_ENTITY,
{omnibox::SUBTYPE_ZERO_PREFIX}},
// Three horizontally rendered tiles
{AutocompleteMatchType::SEARCH_SUGGEST,
{omnibox::GROUP_MOBILE_MOST_VISITED},
stats_1,
omnibox::TYPE_QUERY,
{omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_QUERIES}},
{AutocompleteMatchType::SEARCH_SUGGEST,
{omnibox::GROUP_MOBILE_MOST_VISITED},
stats_1,
omnibox::TYPE_QUERY,
{omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_URLS}},
{AutocompleteMatchType::SEARCH_SUGGEST,
{omnibox::GROUP_MOBILE_MOST_VISITED},
stats_1,
omnibox::TYPE_QUERY,
{omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_HISTORY}},
// Entity suggestion.
{AutocompleteMatchType::SEARCH_SUGGEST_ENTITY,
{/* GroupID */},
stats_2,
omnibox::TYPE_ENTITY,
{omnibox::SUBTYPE_ZERO_PREFIX}},
};
SCOPED_TRACE("Multiple matches with horizontal render group");
RunSearchboxStatsTest(test_data, /*input_is_zero_suggest=*/true);
}
{
omnibox::metrics::ChromeSearchboxStats searchbox_stats;
searchbox_stats.set_client_name("chrome");
searchbox_stats.set_num_zero_prefix_suggestions_shown(2);
searchbox_stats.set_zero_prefix_enabled(true);
auto* available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(0);
available_suggestion->set_type(omnibox::TYPE_QUERY);
available_suggestion->add_subtypes(omnibox::SUBTYPE_PERSONAL);
available_suggestion->add_subtypes(omnibox::SUBTYPE_TRENDS);
available_suggestion->add_subtypes(omnibox::SUBTYPE_ZERO_PREFIX);
available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(1);
available_suggestion->set_type(omnibox::TYPE_ENTITY);
available_suggestion->add_subtypes(omnibox::SUBTYPE_PERSONAL);
available_suggestion->add_subtypes(omnibox::SUBTYPE_TRENDS);
available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(2);
available_suggestion->set_type(omnibox::TYPE_CATEGORICAL_QUERY);
available_suggestion->add_subtypes(omnibox::SUBTYPE_PERSONAL);
available_suggestion->add_subtypes(omnibox::SUBTYPE_TRENDS);
available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(3);
available_suggestion->set_type(omnibox::TYPE_ENTITY);
available_suggestion->add_subtypes(omnibox::SUBTYPE_PERSONAL);
available_suggestion->add_subtypes(omnibox::SUBTYPE_TRENDS);
available_suggestion->add_subtypes(omnibox::SUBTYPE_ZERO_PREFIX);
available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(4);
available_suggestion->set_type(omnibox::TYPE_ENTITY);
available_suggestion->add_subtypes(omnibox::SUBTYPE_PERSONAL);
available_suggestion->add_subtypes(omnibox::SUBTYPE_TRENDS);
omnibox::metrics::ChromeSearchboxStats searchbox_stats_0;
searchbox_stats_0.MergeFrom(searchbox_stats);
auto* assisted_query_info = searchbox_stats_0.mutable_assisted_query_info();
assisted_query_info->MergeFrom(searchbox_stats.available_suggestions(0));
omnibox::metrics::ChromeSearchboxStats searchbox_stats_1;
searchbox_stats_1.MergeFrom(searchbox_stats);
assisted_query_info = searchbox_stats_1.mutable_assisted_query_info();
assisted_query_info->MergeFrom(searchbox_stats.available_suggestions(1));
omnibox::metrics::ChromeSearchboxStats searchbox_stats_2;
searchbox_stats_2.MergeFrom(searchbox_stats);
assisted_query_info = searchbox_stats_2.mutable_assisted_query_info();
assisted_query_info->MergeFrom(searchbox_stats.available_suggestions(2));
omnibox::metrics::ChromeSearchboxStats searchbox_stats_3;
searchbox_stats_3.MergeFrom(searchbox_stats);
assisted_query_info = searchbox_stats_3.mutable_assisted_query_info();
assisted_query_info->MergeFrom(searchbox_stats.available_suggestions(3));
omnibox::metrics::ChromeSearchboxStats searchbox_stats_4;
searchbox_stats_4.MergeFrom(searchbox_stats);
assisted_query_info = searchbox_stats_4.mutable_assisted_query_info();
assisted_query_info->MergeFrom(searchbox_stats.available_suggestions(4));
// This test confirms that repetitive subtype information is being
// properly handled and reported as the same suggestion type.
SearchboxStatsTestData test_data[] = {
{AutocompleteMatchType::SEARCH_SUGGEST,
{/* GroupID */},
searchbox_stats_0,
omnibox::TYPE_QUERY,
{omnibox::SUBTYPE_PERSONAL, omnibox::SUBTYPE_TRENDS,
omnibox::SUBTYPE_ZERO_PREFIX, omnibox::SUBTYPE_TRENDS}},
// The next two matches should be detected as the same type, despite
// repeated subtype match.
{AutocompleteMatchType::SEARCH_SUGGEST_ENTITY,
{/* GroupID */},
searchbox_stats_1,
omnibox::TYPE_ENTITY,
{omnibox::SUBTYPE_PERSONAL, omnibox::SUBTYPE_TRENDS}},
{AutocompleteMatchType::SEARCH_SUGGEST_ENTITY,
{/* GroupID */},
searchbox_stats_2,
omnibox::TYPE_CATEGORICAL_QUERY,
{omnibox::SUBTYPE_PERSONAL, omnibox::SUBTYPE_TRENDS,
omnibox::SUBTYPE_PERSONAL}},
// This match should not be bundled together with previous two, because
// it comes with additional subtype information (42).
{AutocompleteMatchType::SEARCH_SUGGEST_ENTITY,
{/* GroupID */},
searchbox_stats_3,
omnibox::TYPE_ENTITY,
{omnibox::SUBTYPE_PERSONAL, omnibox::SUBTYPE_TRENDS,
omnibox::SUBTYPE_ZERO_PREFIX}},
// This match should not be bundled together with the group before,
// because these items are not adjacent.
{AutocompleteMatchType::SEARCH_SUGGEST_ENTITY,
{/* GroupID */},
searchbox_stats_4,
omnibox::TYPE_ENTITY,
{omnibox::SUBTYPE_PERSONAL, omnibox::SUBTYPE_TRENDS}},
};
SCOPED_TRACE("Complex set of matches with repetitive subtypes");
RunSearchboxStatsTest(test_data, /*input_is_zero_suggest=*/true);
}
// This test confirms that selection of trivial suggestions does not get
// reported in `assisted_query_info`. And that the count of zero-prefix
// matches coming from the suggest server or the local device are recorded.
{
omnibox::metrics::ChromeSearchboxStats searchbox_stats;
searchbox_stats.set_client_name("chrome");
searchbox_stats.set_num_zero_prefix_suggestions_shown(3);
searchbox_stats.set_zero_prefix_enabled(true);
auto* available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(0);
available_suggestion->set_type(omnibox::TYPE_NATIVE_CHROME);
available_suggestion->add_subtypes(omnibox::SUBTYPE_OMNIBOX_ECHO_SEARCH);
available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(1);
available_suggestion->set_type(omnibox::TYPE_NATIVE_CHROME);
available_suggestion->add_subtypes(omnibox::SUBTYPE_OMNIBOX_ECHO_URL);
available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(2);
available_suggestion->set_type(omnibox::TYPE_NAVIGATION);
available_suggestion->add_subtypes(omnibox::SUBTYPE_OMNIBOX_OTHER);
available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(3);
available_suggestion->set_type(omnibox::TYPE_NAVIGATION);
available_suggestion->add_subtypes(omnibox::SUBTYPE_OMNIBOX_OTHER);
available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(4);
available_suggestion->set_type(omnibox::TYPE_QUERY);
available_suggestion->add_subtypes(omnibox::SUBTYPE_ZERO_PREFIX);
available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(5);
available_suggestion->set_type(omnibox::TYPE_QUERY);
available_suggestion->add_subtypes(omnibox::SUBTYPE_ZERO_PREFIX);
available_suggestion->add_subtypes(
omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_HISTORY);
available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(6);
available_suggestion->set_type(omnibox::TYPE_QUERY);
available_suggestion->add_subtypes(omnibox::SUBTYPE_ZERO_PREFIX);
available_suggestion->add_subtypes(
omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_URLS);
available_suggestion = searchbox_stats.add_available_suggestions();
available_suggestion->set_index(7);
available_suggestion->set_type(omnibox::TYPE_NATIVE_CHROME);
available_suggestion->add_subtypes(omnibox::SUBTYPE_OMNIBOX_HISTORY_SEARCH);
omnibox::metrics::ChromeSearchboxStats searchbox_stats_0;
searchbox_stats_0.MergeFrom(searchbox_stats);
omnibox::metrics::ChromeSearchboxStats searchbox_stats_1;
searchbox_stats_1.MergeFrom(searchbox_stats);
omnibox::metrics::ChromeSearchboxStats searchbox_stats_2;
searchbox_stats_2.MergeFrom(searchbox_stats);
auto* assisted_query_info = searchbox_stats_2.mutable_assisted_query_info();
assisted_query_info->MergeFrom(searchbox_stats.available_suggestions(2));
omnibox::metrics::ChromeSearchboxStats searchbox_stats_3;
searchbox_stats_3.MergeFrom(searchbox_stats);
assisted_query_info = searchbox_stats_3.mutable_assisted_query_info();
assisted_query_info->MergeFrom(searchbox_stats.available_suggestions(3));
omnibox::metrics::ChromeSearchboxStats searchbox_stats_4;
searchbox_stats_4.MergeFrom(searchbox_stats);
assisted_query_info = searchbox_stats_4.mutable_assisted_query_info();
assisted_query_info->MergeFrom(searchbox_stats.available_suggestions(4));
omnibox::metrics::ChromeSearchboxStats searchbox_stats_5;
searchbox_stats_5.MergeFrom(searchbox_stats);
assisted_query_info = searchbox_stats_5.mutable_assisted_query_info();
assisted_query_info->MergeFrom(searchbox_stats.available_suggestions(5));
omnibox::metrics::ChromeSearchboxStats searchbox_stats_6;
searchbox_stats_6.MergeFrom(searchbox_stats);
assisted_query_info = searchbox_stats_6.mutable_assisted_query_info();
assisted_query_info->MergeFrom(searchbox_stats.available_suggestions(6));
omnibox::metrics::ChromeSearchboxStats searchbox_stats_7;
searchbox_stats_7.MergeFrom(searchbox_stats);
assisted_query_info = searchbox_stats_7.mutable_assisted_query_info();
assisted_query_info->MergeFrom(searchbox_stats.available_suggestions(7));
SearchboxStatsTestData test_data[] = {
{AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED,
{/* GroupID */},
searchbox_stats_0,
omnibox::TYPE_NATIVE_CHROME},
{AutocompleteMatchType::URL_WHAT_YOU_TYPED,
{/* GroupID */},
searchbox_stats_1,
omnibox::TYPE_NATIVE_CHROME},
{AutocompleteMatchType::NAVSUGGEST,
{/* GroupID */},
searchbox_stats_2,
omnibox::TYPE_NAVIGATION},
{AutocompleteMatchType::NAVSUGGEST,
{/* GroupID */},
searchbox_stats_3,
omnibox::TYPE_NAVIGATION},
{AutocompleteMatchType::SEARCH_SUGGEST,
{/* GroupID */},
searchbox_stats_4,
omnibox::TYPE_QUERY,
{omnibox::SUBTYPE_ZERO_PREFIX}},
{AutocompleteMatchType::SEARCH_SUGGEST,
{/* GroupID */},
searchbox_stats_5,
omnibox::TYPE_QUERY,
{omnibox::SUBTYPE_ZERO_PREFIX,
omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_HISTORY}},
{AutocompleteMatchType::SEARCH_SUGGEST,
{/* GroupID */},
searchbox_stats_6,
omnibox::TYPE_QUERY,
{omnibox::SUBTYPE_ZERO_PREFIX,
omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_URLS}},
{AutocompleteMatchType::SEARCH_HISTORY,
{/* GroupID */},
searchbox_stats_7,
omnibox::TYPE_NATIVE_CHROME},
};
SCOPED_TRACE("Trivial and zero-prefix matches");
RunSearchboxStatsTest(test_data, /*input_is_zero_suggest=*/true);
}
}
TEST_F(AutocompleteProviderTest, GetDestinationURL) {
ResetControllerWithKeywordAndSearchProviders();
// For the destination URL to have searchbox stats parameters for query
// formulation time and the field trial triggered bit, many conditions need
// to be satisfied.
AutocompleteMatch match(nullptr, 1100, false,
AutocompleteMatchType::SEARCH_SUGGEST);
GURL url(GetDestinationURL(match, base::Milliseconds(2456)));
EXPECT_TRUE(url.path().empty());
// The protocol needs to be https.
RegisterTemplateURL(kTestTemplateURLKeyword,
"https://foo/{searchTerms}/{google:assistedQueryStats}");
url = GetDestinationURL(match, base::Milliseconds(2456));
EXPECT_TRUE(url.path().empty());
// There needs to be a keyword provider.
match.keyword = kTestTemplateURLKeyword;
url = GetDestinationURL(match, base::Milliseconds(2456));
EXPECT_TRUE(url.path().empty());
// search_terms_args needs to be set.
match.search_terms_args =
std::make_unique<TemplateURLRef::SearchTermsArgs>(std::u16string());
url = GetDestinationURL(match, base::Milliseconds(2456));
EXPECT_TRUE(url.path().empty());
// searchbox_stats need to have been set.
match.search_terms_args->searchbox_stats.set_client_name("chrome");
url = GetDestinationURL(match, base::Milliseconds(2456));
EXPECT_EQ("//gs_lcrp=EgZjaHJvbWXSAQgyNDU2ajBqMA&", url.path());
// Make sure searchbox_stats is serialized and encoded correctly.
{
std::string serialized_proto;
EXPECT_TRUE(base::Base64UrlDecode(
"EgZjaHJvbWXSAQgyNDU2ajBqMA",
base::Base64UrlDecodePolicy::DISALLOW_PADDING, &serialized_proto));
omnibox::metrics::ChromeSearchboxStats expected;
expected.ParseFromString(serialized_proto);
EXPECT_EQ("chrome", expected.client_name());
}
// Test field trial triggered bit set.
set_remote_search_feature_triggered_in_session(true);
url = GetDestinationURL(match, base::Milliseconds(2456));
EXPECT_EQ("//gs_lcrp=EgZjaHJvbWXSAQgyNDU2ajFqMA&", url.path());
// Make sure searchbox_stats is serialized and encoded correctly.
{
std::string serialized_proto;
EXPECT_TRUE(base::Base64UrlDecode(
"EgZjaHJvbWXSAQgyNDU2ajFqMA",
base::Base64UrlDecodePolicy::DISALLOW_PADDING, &serialized_proto));
omnibox::metrics::ChromeSearchboxStats expected;
expected.ParseFromString(serialized_proto);
EXPECT_EQ("2456j1j0", expected.experiment_stats());
}
// Test page classification set.
set_remote_search_feature_triggered_in_session(false);
set_current_page_classification(metrics::OmniboxEventProto::OTHER);
url = GetDestinationURL(match, base::Milliseconds(2456));
EXPECT_EQ("//gs_lcrp=EgZjaHJvbWXSAQgyNDU2ajBqNA&", url.path());
// Make sure searchbox_stats is serialized and encoded correctly.
{
std::string serialized_proto;
EXPECT_TRUE(base::Base64UrlDecode(
"EgZjaHJvbWXSAQgyNDU2ajBqNA",
base::Base64UrlDecodePolicy::DISALLOW_PADDING, &serialized_proto));
omnibox::metrics::ChromeSearchboxStats expected;
expected.ParseFromString(serialized_proto);
EXPECT_EQ("2456j0j4", expected.experiment_stats());
}
// Test page classification and field trial triggered set.
set_remote_search_feature_triggered_in_session(true);
set_current_page_classification(metrics::OmniboxEventProto::OTHER);
url = GetDestinationURL(match, base::Milliseconds(2456));
EXPECT_EQ("//gs_lcrp=EgZjaHJvbWXSAQgyNDU2ajFqNA&", url.path());
// Make sure searchbox_stats is serialized and encoded correctly.
{
std::string serialized_proto;
EXPECT_TRUE(base::Base64UrlDecode(
"EgZjaHJvbWXSAQgyNDU2ajFqNA",
base::Base64UrlDecodePolicy::DISALLOW_PADDING, &serialized_proto));
omnibox::metrics::ChromeSearchboxStats expected;
expected.ParseFromString(serialized_proto);
EXPECT_EQ("2456j1j4", expected.experiment_stats());
}
#if BUILDFLAG(IS_IOS)
{ // Test top omnibox position in experiment stats v2.
AutocompleteMatch match_copy = match;
controller_->SetSteadyStateOmniboxPosition(
metrics::OmniboxEventProto::TOP_POSITION);
url = GetDestinationURL(match_copy, base::Milliseconds(2456));
EXPECT_EQ("//gs_lcrp=EgZjaHJvbWXSAQgyNDU2ajFqNOIDBBgBIF8&", url.path());
// Make sure searchbox_stats is serialized and encoded correctly.
std::string serialized_proto;
EXPECT_TRUE(base::Base64UrlDecode(
"EgZjaHJvbWXSAQgyNDU2ajFqNOIDBBgBIF8",
base::Base64UrlDecodePolicy::DISALLOW_PADDING, &serialized_proto));
omnibox::metrics::ChromeSearchboxStats expected;
expected.ParseFromString(serialized_proto);
EXPECT_EQ(1, expected.experiment_stats_v2_size());
EXPECT_EQ(95, expected.experiment_stats_v2(0).type_int());
EXPECT_EQ(1, expected.experiment_stats_v2(0).int_value());
}
{ // Test bottom omnibox position in experiment stats v2.
AutocompleteMatch match_copy = match;
controller_->SetSteadyStateOmniboxPosition(
metrics::OmniboxEventProto::BOTTOM_POSITION);
url = GetDestinationURL(match_copy, base::Milliseconds(2456));
EXPECT_EQ("//gs_lcrp=EgZjaHJvbWXSAQgyNDU2ajFqNOIDBBgCIF8&", url.path());
// Make sure searchbox_stats is serialized and encoded correctly.
std::string serialized_proto;
EXPECT_TRUE(base::Base64UrlDecode(
"EgZjaHJvbWXSAQgyNDU2ajFqNOIDBBgCIF8",
base::Base64UrlDecodePolicy::DISALLOW_PADDING, &serialized_proto));
omnibox::metrics::ChromeSearchboxStats expected;
expected.ParseFromString(serialized_proto);
EXPECT_EQ(1, expected.experiment_stats_v2_size());
EXPECT_EQ(95, expected.experiment_stats_v2(0).type_int());
EXPECT_EQ(2, expected.experiment_stats_v2(0).int_value());
}
controller_->SetSteadyStateOmniboxPosition(
metrics::OmniboxEventProto::UNKNOWN_POSITION);
#endif
// Test experiment stats v2 set.
omnibox::metrics::ChromeSearchboxStats::ExperimentStatsV2 experiment_stats_v2;
experiment_stats_v2.set_type_int(10001);
experiment_stats_v2.set_string_value("0:67");
add_zero_suggest_provider_experiment_stats_v2(experiment_stats_v2);
url = GetDestinationURL(match, base::Milliseconds(2456));
EXPECT_EQ("//gs_lcrp=EgZjaHJvbWXSAQgyNDU2ajFqNOIDCRIEMCw2NyCRTg&",
url.path());
// Make sure searchbox_stats is serialized and encoded correctly.
{
std::string serialized_proto;
EXPECT_TRUE(base::Base64UrlDecode(
"EgZjaHJvbWXSAQgyNDU2ajFqNOIDCRIEMCw2NyCRTg",
base::Base64UrlDecodePolicy::DISALLOW_PADDING, &serialized_proto));
omnibox::metrics::ChromeSearchboxStats expected;
expected.ParseFromString(serialized_proto);
EXPECT_EQ(1, expected.experiment_stats_v2_size());
EXPECT_EQ(10001, expected.experiment_stats_v2(0).type_int());
EXPECT_EQ("0,67", expected.experiment_stats_v2(0).string_value());
}
}
TEST_F(AutocompleteProviderTest, ClassifyAllMatchesInString) {
ResetControllerWithKeywordAndSearchProviders();
ACMatchClassifications matches =
AutocompleteMatch::ClassificationsFromString("0,0");
ClassifyTest classify_test(u"A man, a plan, a canal Panama",
/*text_is_query=*/false, matches);
ACMatchClassifications spans;
spans = classify_test.RunTest(u"man");
// A man, a plan, a canal Panama
// ACMatch spans should be: '--MMM------------------------'
EXPECT_EQ("0,0,2,2,5,0", AutocompleteMatch::ClassificationsToString(spans));
spans = classify_test.RunTest(u"man p");
// A man, a plan, a canal Panama
// ACMatch spans should be: '--MMM----M-------------M-----'
EXPECT_EQ("0,0,2,2,5,0,9,2,10,0,23,2,24,0",
AutocompleteMatch::ClassificationsToString(spans));
// Comparisons should be case insensitive.
spans = classify_test.RunTest(u"mAn pLAn panAMa");
// A man, a plan, a canal Panama
// ACMatch spans should be: '--MMM----MMMM----------MMMMMM'
EXPECT_EQ("0,0,2,2,5,0,9,2,13,0,23,2",
AutocompleteMatch::ClassificationsToString(spans));
// When the user input is a prefix of the suggest text, subsequent occurrences
// of the user words should be matched.
spans = classify_test.RunTest(u"a man,");
// A man, a plan, a canal Panama
// ACMatch spans should be: 'MMMMMM-----------------------'
EXPECT_EQ("0,2,6,0", AutocompleteMatch::ClassificationsToString(spans));
// Matches must begin at word-starts in the suggest text. E.g. 'a' in 'plan'
// should not match.
spans = classify_test.RunTest(u"a man, p");
// A man, a plan, a canal Panama
// ACMatch spans should be: 'M-MMM--M-M-----M-------M-----'
EXPECT_EQ("0,2,1,0,2,2,5,0,7,2,8,0,9,2,10,0,15,2,16,0,23,2,24,0",
AutocompleteMatch::ClassificationsToString(spans));
ClassifyTest classify_test2(
u"Yahoo! Sports - Sports News, "
u"Scores, Rumors, Fantasy Games, and more",
/*text_is_query=*/false, matches);
spans = classify_test2.RunTest(u"ne");
// Yahoo! Sports - Sports News, Scores, Rumors, Fantasy Games, and more
// -----------------------MM-------------------------------------------
EXPECT_EQ("0,0,23,2,25,0", AutocompleteMatch::ClassificationsToString(spans));
spans = classify_test2.RunTest(u"neWs R");
// Yahoo! Sports - Sports News, Scores, Rumors, Fantasy Games, and more
// -----------------------MMMM----------M------------------------------
EXPECT_EQ("0,0,23,2,27,0,37,2,38,0",
AutocompleteMatch::ClassificationsToString(spans));
matches = AutocompleteMatch::ClassificationsFromString("0,1");
ClassifyTest classify_test3(u"livescore.goal.com",
/*text_is_query=*/false, matches);
// Matches should be merged with existing classifications.
// Matches can begin after symbols in the suggest text.
spans = classify_test3.RunTest(u"go");
// livescore.goal.com
// ----------MM------
// ACMatch spans should match first two letters of the "goal".
EXPECT_EQ("0,1,10,3,12,1", AutocompleteMatch::ClassificationsToString(spans));
matches = AutocompleteMatch::ClassificationsFromString("0,0,13,1");
ClassifyTest classify_test4(u"Email login: mail.somecorp.com",
/*text_is_query=*/false, matches);
// Matches must begin at word-starts in the suggest text.
spans = classify_test4.RunTest(u"ail");
// Email login: mail.somecorp.com
// 000000000000011111111111111111
EXPECT_EQ("0,0,13,1", AutocompleteMatch::ClassificationsToString(spans));
// The longest matches should take precedence (e.g. 'log' instead of 'lo').
spans = classify_test4.RunTest(u"lo log mail em");
// Email login: mail.somecorp.com
// 220000222000033331111111111111
EXPECT_EQ("0,2,2,0,6,2,9,0,13,3,17,1",
AutocompleteMatch::ClassificationsToString(spans));
// Some web sites do not have a description. If the string being searched is
// empty, the classifications must also be empty: http://crbug.com/148647
// Extra parens in the next line hack around C++03's "most vexing parse".
class ClassifyTest classify_test5((std::u16string()), /*text_is_query=*/false,
ACMatchClassifications());
spans = classify_test5.RunTest(u"man");
ASSERT_EQ(0U, spans.size());
// Matches which end at beginning of classification merge properly.
matches = AutocompleteMatch::ClassificationsFromString("0,4,9,0");
ClassifyTest classify_test6(u"html password example",
/*text_is_query=*/false, matches);
// Extra space in the next string avoids having the string be a prefix of the
// text above, which would allow for two different valid classification sets,
// one of which uses two spans (the first of which would mark all of "html
// pass" as a match) and one which uses four (which marks the individual words
// as matches but not the space between them). This way only the latter is
// valid.
spans = classify_test6.RunTest(u"html pass");
EXPECT_EQ("0,6,4,4,5,6,9,0",
AutocompleteMatch::ClassificationsToString(spans));
// Multiple matches with both beginning and end at beginning of
// classifications merge properly.
matches = AutocompleteMatch::ClassificationsFromString("0,1,11,0");
ClassifyTest classify_test7(u"http://a.co is great",
/*text_is_query=*/false, matches);
spans = classify_test7.RunTest(u"ht co");
EXPECT_EQ("0,3,2,1,9,3,11,0",
AutocompleteMatch::ClassificationsToString(spans));
// Search queries should be bold non-matches and unbold matches.
matches = AutocompleteMatch::ClassificationsFromString("0,0");
ClassifyTest classify_test8(u"panama canal",
/*text_is_query=*/true, matches);
spans = classify_test8.RunTest(u"pan");
// panama canal
// ACMatch spans should be: "---MMMMMMMMM";
EXPECT_EQ("0,0,3,2", AutocompleteMatch::ClassificationsToString(spans));
spans = classify_test8.RunTest(u"canal");
// panama canal
// ACMatch spans should be: "MMMMMMM-----";
EXPECT_EQ("0,2,7,0", AutocompleteMatch::ClassificationsToString(spans));
// Search autocomplete suggestion.
ClassifyTest classify_test9(u"comcast webmail login",
/*text_is_query=*/true, ACMatchClassifications());
// Matches first and first part of middle word and the last word.
spans = classify_test9.RunTest(u"comcast web login");
// comcast webmail login
// ACMatch spans should be: "-------M---MMMMM-----";
EXPECT_EQ("0,0,7,2,8,0,11,2,16,0",
AutocompleteMatch::ClassificationsToString(spans));
// Matches partial word in the middle of suggestion.
spans = classify_test9.RunTest(u"web");
// comcast webmail login
// ACMatch spans should be: "MMMMMMMM---MMMMMMMMMM";
EXPECT_EQ("0,2,8,0,11,2", AutocompleteMatch::ClassificationsToString(spans));
ClassifyTest classify_test10(u"comcast.net web mail login",
/*text_is_query=*/true,
ACMatchClassifications());
spans = classify_test10.RunTest(u"comcast web login");
// comcast.net web mail login
// ACMatch spans should be: "-------MMMMM---MMMMMM-----";
EXPECT_EQ("0,0,7,2,12,0,15,2,21,0",
AutocompleteMatch::ClassificationsToString(spans));
// Same with |classify_test10| except using characters in
// base::kWhitespaceASCIIAs16 instead of white space.
ClassifyTest classify_test11(u"comcast.net\x0aweb\x0dmail login",
/*text_is_query=*/true,
ACMatchClassifications());
spans = classify_test11.RunTest(u"comcast web login");
// comcast.net web mail login
// ACMatch spans should be: "-------MMMMM---MMMMMM-----";
EXPECT_EQ("0,0,7,2,12,0,15,2,21,0",
AutocompleteMatch::ClassificationsToString(spans));
}
TEST_F(AutocompleteProviderTest, ResizeMatches) {
TestProvider* provider = nullptr;
ResetControllerWithTestProviders(false, &provider, nullptr);
// Populate 'matches_` with test data.
ACMatches matches = {
AutocompleteMatch(nullptr, 100, false,
AutocompleteMatchType::BOOKMARK_TITLE),
AutocompleteMatch(nullptr, 110, false,
AutocompleteMatchType::BOOKMARK_TITLE),
AutocompleteMatch(nullptr, 120, false,
AutocompleteMatchType::BOOKMARK_TITLE),
AutocompleteMatch(nullptr, 130, false,
AutocompleteMatchType::BOOKMARK_TITLE),
AutocompleteMatch(nullptr, 140, false,
AutocompleteMatchType::BOOKMARK_TITLE),
AutocompleteMatch(nullptr, 150, false,
AutocompleteMatchType::BOOKMARK_TITLE),
};
provider->set_matches(matches);
EXPECT_EQ(provider->get_matches().size(), matches.size());
// When ML Scoring is enabled, calling resize matches should not actually
// resize the match list. Instead, it should mark any matches over the
// `max_matches` with a relevance score of zero and `culled_by_provider`.
const size_t kMaxMatches = 3;
provider->ResizeMatches(kMaxMatches, true);
EXPECT_EQ(provider->get_matches().size(), matches.size());
// Check to see if `relevance` and `culled_by_provider` are set correctly.
// The first `max_matches` matches should keep their relevance score and have
// `culled_by_provider` set to false.
ACMatches provider_matches = provider->get_matches();
std::ranges::for_each(provider_matches.begin(),
std::next(provider_matches.begin(), kMaxMatches),
[&](auto match) {
EXPECT_NE(match.relevance, 0);
EXPECT_FALSE(match.culled_by_provider);
});
// Any match beyond that should have their relevance score zeroed and
// `culled_by_provider` set.
std::ranges::for_each(std::next(provider_matches.begin(), kMaxMatches),
provider_matches.end(), [&](auto match) {
EXPECT_EQ(match.relevance, 0);
EXPECT_TRUE(match.culled_by_provider);
});
// Now disable the flag. With ML Scoring disabled, `matches_` should actually
// be resized and `relevance` and `culled_by_provider` should be untouched.
provider->set_matches(matches);
EXPECT_EQ(provider->get_matches().size(), matches.size());
provider->ResizeMatches(kMaxMatches, false);
EXPECT_EQ(provider->get_matches().size(), kMaxMatches);
std::ranges::for_each(provider->get_matches(), [&](auto match) {
EXPECT_NE(match.relevance, 0);
EXPECT_FALSE(match.culled_by_provider);
});
}
class AutocompleteProviderPrefetchTest : public AutocompleteProviderTest {
public:
AutocompleteProviderPrefetchTest() {
RegisterTemplateURL(kTestTemplateURLKeyword,
"http://foo/{searchTerms}/{google:assistedQueryStats}");
// Create an empty controller.
ResetControllerWithType(0);
provider_listener_ =
std::make_unique<TestAutocompleteProviderListener>(controller_.get());
}
~AutocompleteProviderPrefetchTest() override = default;
AutocompleteProviderPrefetchTest(const AutocompleteProviderPrefetchTest&) =
delete;
AutocompleteProviderPrefetchTest& operator=(
const AutocompleteProviderPrefetchTest&) = delete;
protected:
std::unique_ptr<TestAutocompleteProviderListener> provider_listener_;
};
TEST_F(AutocompleteProviderPrefetchTest, SupportedProvider_NonPrefetch) {
// Add a test provider that supports prefetch requests.
TestProvider* provider = new TestProvider(kResultsPerProvider, u"http://a",
kTestTemplateURLKeyword, client_);
provider->set_supports_prefetch(true);
controller_->providers_.push_back(provider);
base::RunLoop listener_run_loop;
provider_listener_->set_closure(
listener_run_loop.QuitClosure().Then(base::BindRepeating(
&AutocompleteProviderTest::CopyResults, base::Unretained(this))));
provider->AddListener(provider_listener_.get());
AutocompleteInput input(u"foo", metrics::OmniboxEventProto::OTHER,
TestingSchemeClassifier());
controller_->Start(input);
ASSERT_FALSE(provider->done());
ASSERT_FALSE(controller_->done());
// Wait for the provider to finish asynchronously.
listener_run_loop.Run();
ASSERT_TRUE(provider->done());
ASSERT_TRUE(controller_->done());
// The results are expected to be non-empty as the provider did notify the
// controller of the non-prefetch request results.
EXPECT_EQ(kResultsPerProvider, result_.size());
}
TEST_F(AutocompleteProviderPrefetchTest, SupportedProvider_Prefetch) {
// Add a test provider that supports prefetch requests.
base::test::ScopedFeatureList features;
features.InitWithFeatures(
/*enabled_features=*/{omnibox::kZeroSuggestPrefetching},
/*disabled_features=*/{});
TestProvider* provider = new TestProvider(kResultsPerProvider, u"http://a",
kTestTemplateURLKeyword, client_);
provider->set_supports_prefetch(true);
controller_->providers_.push_back(provider);
base::RunLoop provider_run_loop;
provider->set_closure(provider_run_loop.QuitClosure());
base::RunLoop listener_run_loop;
provider_listener_->set_closure(
listener_run_loop.QuitClosure().Then(base::BindRepeating(
&AutocompleteProviderTest::CopyResults, base::Unretained(this))));
provider->AddListener(provider_listener_.get());
AutocompleteInput input(u"", metrics::OmniboxEventProto::NTP_ZPS_PREFETCH,
TestingSchemeClassifier());
controller_->StartPrefetch(input);
// Wait for StartPrefetch() to be called on the provider.
provider_run_loop.Run();
// StartPrefetch() doesn't affect the state of the provider or the controller.
ASSERT_FALSE(provider->prefetch_done());
ASSERT_TRUE(provider->done());
ASSERT_TRUE(controller_->done());
// Wait for the provider to finish asynchronously.
listener_run_loop.Run();
ASSERT_TRUE(provider->prefetch_done());
ASSERT_TRUE(controller_->done());
// The results are expected to be empty as the provider did not notify the
// controller of the prefetch request results.
EXPECT_TRUE(result_.empty());
}
TEST_F(AutocompleteProviderPrefetchTest, SupportedProvider_OngoingNonPrefetch) {
base::test::ScopedFeatureList features;
features.InitWithFeatures(
/*enabled_features=*/{omnibox::kZeroSuggestPrefetching},
/*disabled_features=*/{});
// Add a test provider that supports prefetch requests.
TestProvider* provider = new TestProvider(kResultsPerProvider, u"http://a",
kTestTemplateURLKeyword, client_);
provider->set_supports_prefetch(true);
controller_->providers_.push_back(provider);
base::RunLoop provider_run_loop;
provider->set_closure(provider_run_loop.QuitClosure());
base::RunLoop listener_run_loop;
provider_listener_->set_closure(
listener_run_loop.QuitClosure().Then(base::BindRepeating(
&AutocompleteProviderTest::CopyResults, base::Unretained(this))));
provider->AddListener(provider_listener_.get());
AutocompleteInput input(u"bar", metrics::OmniboxEventProto::NTP_ZPS_PREFETCH,
TestingSchemeClassifier());
controller_->Start(input);
ASSERT_TRUE(provider->prefetch_done());
ASSERT_FALSE(provider->done());
ASSERT_FALSE(controller_->done());
// Try to start a prefetch request while a non-prefetch request is still in
// progress. We expect this not to call StartPrefetch() on the provider.
// We test this by requesting synchronous matches from TestPrefetchProvider
// which notifies the provider listener synchronously and calls the quit
// closure of `run_loop` before `run_loop` is run. This prevents the provider
// from being able to notify the controller of finishing the non-prefetch
// request resulting in the controller to remain in an invalid state.
input.set_omit_asynchronous_matches(true);
controller_->StartPrefetch(input);
ASSERT_FALSE(provider_run_loop.running());
ASSERT_TRUE(provider->prefetch_done());
// Wait for the provider to finish the non-prefetch request asynchronously.
listener_run_loop.Run();
ASSERT_TRUE(provider->done());
ASSERT_TRUE(controller_->done());
// The results are expected to be non-empty as the provider did notify the
// controller of the non-prefetch request results.
EXPECT_EQ(kResultsPerProvider, result_.size());
}
TEST_F(AutocompleteProviderPrefetchTest, UnsupportedProvider_Prefetch) {
base::test::ScopedFeatureList features;
features.InitWithFeatures(
/*enabled_features=*/{omnibox::kZeroSuggestPrefetching},
/*disabled_features=*/{});
// Add a test provider that does not support prefetch requests.
TestProvider* provider = new TestProvider(kResultsPerProvider, u"http://a",
kTestTemplateURLKeyword, client_);
controller_->providers_.push_back(provider);
base::RunLoop provider_run_loop;
provider->set_closure(provider_run_loop.QuitClosure());
AutocompleteInput input(u"", metrics::OmniboxEventProto::NTP_ZPS_PREFETCH,
TestingSchemeClassifier());
controller_->StartPrefetch(input);
// We expect this not to call StartPrefetch() on the provider.
ASSERT_FALSE(provider_run_loop.running());
ASSERT_TRUE(provider->prefetch_done());
ASSERT_TRUE(provider->done());
ASSERT_TRUE(controller_->done());
}