blob: 8bb0e48217456359f0532d1c3ca9b48dcfcb68c0 [file] [log] [blame]
// Copyright 2014 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/zero_suggest_provider.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_task_environment.h"
#include "components/history/core/browser/top_sites.h"
#include "components/metrics/proto/omnibox_event.pb.h"
#include "components/omnibox/browser/autocomplete_provider_listener.h"
#include "components/omnibox/browser/mock_autocomplete_provider_client.h"
#include "components/omnibox/browser/omnibox_field_trial.h"
#include "components/omnibox/browser/omnibox_pref_names.h"
#include "components/omnibox/browser/test_scheme_classifier.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_service.h"
#include "components/variations/entropy_provider.h"
#include "components/variations/variations_associated_data.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
class FakeEmptyTopSites : public history::TopSites {
public:
FakeEmptyTopSites() {
}
// history::TopSites:
bool SetPageThumbnail(const GURL& url, const gfx::Image& thumbnail,
const ThumbnailScore& score) override {
return false;
}
void GetMostVisitedURLs(const GetMostVisitedURLsCallback& callback,
bool include_forced_urls) override;
bool GetPageThumbnail(const GURL& url, bool prefix_match,
scoped_refptr<base::RefCountedMemory>* bytes) override {
return false;
}
bool GetPageThumbnailScore(const GURL& url, ThumbnailScore* score) override {
return false;
}
bool GetTemporaryPageThumbnailScore(const GURL& url, ThumbnailScore* score)
override {
return false;
}
void SyncWithHistory() override {}
bool HasBlacklistedItems() const override {
return false;
}
void AddBlacklistedURL(const GURL& url) override {}
void RemoveBlacklistedURL(const GURL& url) override {}
bool IsBlacklisted(const GURL& url) override {
return false;
}
void ClearBlacklistedURLs() override {}
bool IsKnownURL(const GURL& url) override {
return false;
}
bool IsNonForcedFull() override {
return false;
}
bool IsForcedFull() override {
return false;
}
bool loaded() const override {
return false;
}
history::PrepopulatedPageList GetPrepopulatedPages() override {
return history::PrepopulatedPageList();
}
bool AddForcedURL(const GURL& url, const base::Time& time) override {
return false;
}
void OnNavigationCommitted(const GURL& url) override {}
// RefcountedKeyedService:
void ShutdownOnUIThread() override {}
// A test-specific field for controlling when most visited callback is run
// after top sites have been requested.
GetMostVisitedURLsCallback mv_callback;
protected:
~FakeEmptyTopSites() override {}
DISALLOW_COPY_AND_ASSIGN(FakeEmptyTopSites);
};
void FakeEmptyTopSites::GetMostVisitedURLs(
const GetMostVisitedURLsCallback& callback,
bool include_forced_urls) {
mv_callback = callback;
}
class FakeAutocompleteProviderClient
: public testing::NiceMock<MockAutocompleteProviderClient> {
public:
FakeAutocompleteProviderClient()
: template_url_service_(new TemplateURLService(nullptr, 0)),
top_sites_(new FakeEmptyTopSites()) {
pref_service_.registry()->RegisterStringPref(
omnibox::kZeroSuggestCachedResults, std::string());
}
bool SearchSuggestEnabled() const override { return true; }
scoped_refptr<history::TopSites> GetTopSites() override { return top_sites_; }
TemplateURLService* GetTemplateURLService() override {
return template_url_service_.get();
}
TemplateURLService* GetTemplateURLService() const override {
return template_url_service_.get();
}
PrefService* GetPrefs() override { return &pref_service_; }
void Classify(
const base::string16& text,
bool prefer_keyword,
bool allow_exact_keyword_match,
metrics::OmniboxEventProto::PageClassification page_classification,
AutocompleteMatch* match,
GURL* alternate_nav_url) override {
// Populate enough of |match| to keep the ZeroSuggestProvider happy.
match->type = AutocompleteMatchType::URL_WHAT_YOU_TYPED;
match->destination_url = GURL(text);
}
const AutocompleteSchemeClassifier& GetSchemeClassifier() const override {
return scheme_classifier_;
}
private:
std::unique_ptr<TemplateURLService> template_url_service_;
scoped_refptr<history::TopSites> top_sites_;
TestingPrefServiceSimple pref_service_;
TestSchemeClassifier scheme_classifier_;
DISALLOW_COPY_AND_ASSIGN(FakeAutocompleteProviderClient);
};
} // namespace
class ZeroSuggestProviderTest : public testing::Test,
public AutocompleteProviderListener {
public:
ZeroSuggestProviderTest();
void SetUp() override;
protected:
// AutocompleteProviderListener:
void OnProviderUpdate(bool updated_matches) override;
void ResetFieldTrialList();
void CreatePersonalizedFieldTrial();
void CreateMostVisitedFieldTrial();
base::test::ScopedTaskEnvironment scoped_task_environment_;
// Needed for OmniboxFieldTrial::ActivateStaticTrials().
std::unique_ptr<base::FieldTrialList> field_trial_list_;
net::TestURLFetcherFactory test_factory_;
std::unique_ptr<FakeAutocompleteProviderClient> client_;
scoped_refptr<ZeroSuggestProvider> provider_;
TemplateURL* default_t_url_;
private:
DISALLOW_COPY_AND_ASSIGN(ZeroSuggestProviderTest);
};
ZeroSuggestProviderTest::ZeroSuggestProviderTest() {
ResetFieldTrialList();
}
void ZeroSuggestProviderTest::SetUp() {
// Make sure that fetchers are automatically unregistered upon destruction.
test_factory_.set_remove_fetcher_on_delete(true);
client_.reset(new FakeAutocompleteProviderClient());
TemplateURLService* turl_model = client_->GetTemplateURLService();
turl_model->Load();
TemplateURLData data;
data.SetShortName(base::ASCIIToUTF16("t"));
data.SetURL("https://www.google.com/?q={searchTerms}");
data.suggestions_url = "https://www.google.com/complete/?q={searchTerms}";
data.instant_url = "https://does/not/exist?strk=1";
data.search_terms_replacement_key = "strk";
default_t_url_ = turl_model->Add(base::MakeUnique<TemplateURL>(data));
turl_model->SetUserSelectedDefaultSearchProvider(default_t_url_);
provider_ = ZeroSuggestProvider::Create(client_.get(), nullptr, this);
}
void ZeroSuggestProviderTest::OnProviderUpdate(bool updated_matches) {
}
void ZeroSuggestProviderTest::ResetFieldTrialList() {
// Destroy the existing FieldTrialList before creating a new one to avoid
// a DCHECK.
field_trial_list_.reset();
field_trial_list_.reset(new base::FieldTrialList(
base::MakeUnique<metrics::SHA1EntropyProvider>("foo")));
variations::testing::ClearAllVariationParams();
}
void ZeroSuggestProviderTest::CreatePersonalizedFieldTrial() {
std::map<std::string, std::string> params;
params[std::string(OmniboxFieldTrial::kZeroSuggestRule)] = "true";
params[std::string(OmniboxFieldTrial::kZeroSuggestVariantRule)] =
"Personalized";
variations::AssociateVariationParams(
OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params);
base::FieldTrialList::CreateFieldTrial(
OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");
}
void ZeroSuggestProviderTest::CreateMostVisitedFieldTrial() {
std::map<std::string, std::string> params;
params[std::string(OmniboxFieldTrial::kZeroSuggestRule)] = "true";
params[std::string(OmniboxFieldTrial::kZeroSuggestVariantRule)] =
"MostVisitedWithoutSERP";
variations::AssociateVariationParams(
OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params);
base::FieldTrialList::CreateFieldTrial(
OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");
}
TEST_F(ZeroSuggestProviderTest, TestDoesNotReturnMatchesForPrefix) {
CreatePersonalizedFieldTrial();
std::string url("http://www.cnn.com/");
AutocompleteInput input(base::ASCIIToUTF16(url), base::string16::npos,
std::string(), GURL(url),
metrics::OmniboxEventProto::INVALID_SPEC, true, false,
true, true, false, TestSchemeClassifier());
// Set up the pref to cache the response from the previous run.
std::string json_response("[\"\",[\"search1\", \"search2\", \"search3\"],"
"[],[],{\"google:suggestrelevance\":[602, 601, 600],"
"\"google:verbatimrelevance\":1300}]");
PrefService* prefs = client_->GetPrefs();
prefs->SetString(omnibox::kZeroSuggestCachedResults, json_response);
provider_->Start(input, false);
// Expect that matches don't get populated out of cache because we are not
// in zero suggest mode.
EXPECT_TRUE(provider_->matches().empty());
// Expect that fetcher did not get created.
net::TestURLFetcher* fetcher = test_factory_.GetFetcherByID(1);
EXPECT_FALSE(fetcher);
}
TEST_F(ZeroSuggestProviderTest, TestMostVisitedCallback) {
CreateMostVisitedFieldTrial();
std::string current_url("http://www.foxnews.com/");
std::string input_url("http://www.cnn.com/");
AutocompleteInput input(base::ASCIIToUTF16(input_url), base::string16::npos,
std::string(), GURL(current_url),
metrics::OmniboxEventProto::OTHER, false, false, true,
true, true, TestSchemeClassifier());
history::MostVisitedURLList urls;
history::MostVisitedURL url(GURL("http://foo.com/"),
base::ASCIIToUTF16("Foo"));
urls.push_back(url);
provider_->Start(input, false);
EXPECT_TRUE(provider_->matches().empty());
scoped_refptr<history::TopSites> top_sites = client_->GetTopSites();
static_cast<FakeEmptyTopSites*>(top_sites.get())->mv_callback.Run(urls);
// Should have verbatim match + most visited url match.
EXPECT_EQ(2U, provider_->matches().size());
provider_->Stop(false, false);
provider_->Start(input, false);
provider_->Stop(false, false);
EXPECT_TRUE(provider_->matches().empty());
// Most visited results arriving after Stop() has been called, ensure they
// are not displayed.
static_cast<FakeEmptyTopSites*>(top_sites.get())->mv_callback.Run(urls);
EXPECT_TRUE(provider_->matches().empty());
}
TEST_F(ZeroSuggestProviderTest, TestMostVisitedNavigateToSearchPage) {
CreateMostVisitedFieldTrial();
std::string current_url("http://www.foxnews.com/");
std::string input_url("http://www.cnn.com/");
AutocompleteInput input(base::ASCIIToUTF16(input_url), base::string16::npos,
std::string(), GURL(current_url),
metrics::OmniboxEventProto::OTHER, false, false, true,
true, true, TestSchemeClassifier());
history::MostVisitedURLList urls;
history::MostVisitedURL url(GURL("http://foo.com/"),
base::ASCIIToUTF16("Foo"));
urls.push_back(url);
provider_->Start(input, false);
EXPECT_TRUE(provider_->matches().empty());
// Stop() doesn't always get called.
std::string search_url("https://www.google.com/?q=flowers");
AutocompleteInput srp_input(
base::ASCIIToUTF16(search_url), base::string16::npos, std::string(),
GURL(search_url),
metrics::OmniboxEventProto::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT,
false, false, true, true, true, TestSchemeClassifier());
provider_->Start(srp_input, false);
EXPECT_TRUE(provider_->matches().empty());
// Most visited results arriving after a new request has been started.
scoped_refptr<history::TopSites> top_sites = client_->GetTopSites();
static_cast<FakeEmptyTopSites*>(top_sites.get())->mv_callback.Run(urls);
EXPECT_TRUE(provider_->matches().empty());
}
TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestCachingFirstRun) {
CreatePersonalizedFieldTrial();
// Ensure the cache is empty.
PrefService* prefs = client_->GetPrefs();
prefs->SetString(omnibox::kZeroSuggestCachedResults, std::string());
std::string url("http://www.cnn.com/");
AutocompleteInput input(base::ASCIIToUTF16(url), base::string16::npos,
std::string(), GURL(url),
metrics::OmniboxEventProto::INVALID_SPEC, true, false,
true, true, true, TestSchemeClassifier());
provider_->Start(input, false);
EXPECT_TRUE(prefs->GetString(omnibox::kZeroSuggestCachedResults).empty());
EXPECT_TRUE(provider_->matches().empty());
net::TestURLFetcher* fetcher = test_factory_.GetFetcherByID(1);
ASSERT_TRUE(fetcher);
fetcher->set_response_code(200);
std::string json_response("[\"\",[\"search1\", \"search2\", \"search3\"],"
"[],[],{\"google:suggestrelevance\":[602, 601, 600],"
"\"google:verbatimrelevance\":1300}]");
fetcher->SetResponseString(json_response);
fetcher->delegate()->OnURLFetchComplete(fetcher);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(4U, provider_->matches().size()); // 3 results + verbatim
EXPECT_EQ(json_response,
prefs->GetString(omnibox::kZeroSuggestCachedResults));
}
TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestHasCachedResults) {
CreatePersonalizedFieldTrial();
std::string url("http://www.cnn.com/");
AutocompleteInput input(base::ASCIIToUTF16(url), base::string16::npos,
std::string(), GURL(url),
metrics::OmniboxEventProto::INVALID_SPEC, true, false,
true, true, true, TestSchemeClassifier());
// Set up the pref to cache the response from the previous run.
std::string json_response("[\"\",[\"search1\", \"search2\", \"search3\"],"
"[],[],{\"google:suggestrelevance\":[602, 601, 600],"
"\"google:verbatimrelevance\":1300}]");
PrefService* prefs = client_->GetPrefs();
prefs->SetString(omnibox::kZeroSuggestCachedResults, json_response);
provider_->Start(input, false);
// Expect that matches get populated synchronously out of the cache.
ASSERT_EQ(4U, provider_->matches().size());
EXPECT_EQ(base::ASCIIToUTF16("search1"), provider_->matches()[1].contents);
EXPECT_EQ(base::ASCIIToUTF16("search2"), provider_->matches()[2].contents);
EXPECT_EQ(base::ASCIIToUTF16("search3"), provider_->matches()[3].contents);
net::TestURLFetcher* fetcher = test_factory_.GetFetcherByID(1);
ASSERT_TRUE(fetcher);
fetcher->set_response_code(200);
std::string json_response2("[\"\",[\"search4\", \"search5\", \"search6\"],"
"[],[],{\"google:suggestrelevance\":[602, 601, 600],"
"\"google:verbatimrelevance\":1300}]");
fetcher->SetResponseString(json_response2);
fetcher->delegate()->OnURLFetchComplete(fetcher);
base::RunLoop().RunUntilIdle();
// Expect the same 4 results after the response has been handled.
ASSERT_EQ(4U, provider_->matches().size());
EXPECT_EQ(base::ASCIIToUTF16("search1"), provider_->matches()[1].contents);
EXPECT_EQ(base::ASCIIToUTF16("search2"), provider_->matches()[2].contents);
EXPECT_EQ(base::ASCIIToUTF16("search3"), provider_->matches()[3].contents);
// Expect the new results have been stored.
EXPECT_EQ(json_response2,
prefs->GetString(omnibox::kZeroSuggestCachedResults));
}
TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestReceivedEmptyResults) {
CreatePersonalizedFieldTrial();
std::string url("http://www.cnn.com/");
AutocompleteInput input(base::ASCIIToUTF16(url), base::string16::npos,
std::string(), GURL(url),
metrics::OmniboxEventProto::INVALID_SPEC, true, false,
true, true, true, TestSchemeClassifier());
// Set up the pref to cache the response from the previous run.
std::string json_response("[\"\",[\"search1\", \"search2\", \"search3\"],"
"[],[],{\"google:suggestrelevance\":[602, 601, 600],"
"\"google:verbatimrelevance\":1300}]");
PrefService* prefs = client_->GetPrefs();
prefs->SetString(omnibox::kZeroSuggestCachedResults, json_response);
provider_->Start(input, false);
// Expect that matches get populated synchronously out of the cache.
ASSERT_EQ(4U, provider_->matches().size());
EXPECT_EQ(base::ASCIIToUTF16("search1"), provider_->matches()[1].contents);
EXPECT_EQ(base::ASCIIToUTF16("search2"), provider_->matches()[2].contents);
EXPECT_EQ(base::ASCIIToUTF16("search3"), provider_->matches()[3].contents);
net::TestURLFetcher* fetcher = test_factory_.GetFetcherByID(1);
ASSERT_TRUE(fetcher);
fetcher->set_response_code(200);
std::string empty_response("[\"\",[],[],[],{}]");
fetcher->SetResponseString(empty_response);
fetcher->delegate()->OnURLFetchComplete(fetcher);
base::RunLoop().RunUntilIdle();
// Expect that the matches have been cleared.
ASSERT_TRUE(provider_->matches().empty());
// Expect the new results have been stored.
EXPECT_EQ(empty_response,
prefs->GetString(omnibox::kZeroSuggestCachedResults));
}