blob: bb051ad8aaa1a4941639fbbda49357ae4bbaa454 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/omnibox/browser/most_visited_sites_provider.h"
#include <list>
#include <memory>
#include <string>
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "components/history/core/browser/features.h"
#include "components/history/core/browser/top_sites.h"
#include "components/history/core/browser/top_sites_impl.h"
#include "components/omnibox/browser/autocomplete_enums.h"
#include "components/omnibox/browser/autocomplete_provider_listener.h"
#include "components/omnibox/browser/fake_autocomplete_provider_client.h"
#include "components/omnibox/browser/suggestion_group_util.h"
#include "components/omnibox/browser/tab_matcher.h"
#include "components/omnibox/browser/test_scheme_classifier.h"
#include "components/omnibox/common/omnibox_feature_configs.h"
#include "components/omnibox/common/omnibox_features.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 "ui/base/device_form_factor.h"
namespace {
struct TestData {
bool is_search;
history::MostVisitedURL entry;
};
using OEP = metrics::OmniboxEventProto;
using OFT = metrics::OmniboxFocusType;
using testing::_;
class FakeTopSites : public history::TopSites {
public:
FakeTopSites() = default;
// history::TopSites:
void GetMostVisitedURLs(GetMostVisitedURLsCallback callback) override {
callbacks_.push_back(std::move(callback));
}
void SyncWithHistory() override {}
bool HasBlockedUrls() const override { return !blocked_urls_.empty(); }
void AddBlockedUrl(const GURL& url) override {
blocked_urls_.insert(url.spec());
}
void RemoveBlockedUrl(const GURL& url) override {
blocked_urls_.erase(url.spec());
}
bool IsBlocked(const GURL& url) override {
return blocked_urls_.count(url.spec()) > 0;
}
void ClearBlockedUrls() override { blocked_urls_.clear(); }
bool IsFull() override { return false; }
bool loaded() const override { return false; }
history::PrepopulatedPageList GetPrepopulatedPages() override {
return history::PrepopulatedPageList();
}
void OnNavigationCommitted(const GURL& url) override {}
int NumBlockedSites() const override { return blocked_urls_.size(); }
// RefcountedKeyedService:
void ShutdownOnUIThread() override {}
// Only runs a single callback, so that the test can specify a different
// set per call.
// Returns true if there was a recipient to receive the URLs and the list was
// emitted, otherwise returns false.
bool EmitURLs(const std::vector<TestData>& data) {
if (callbacks_.empty())
return false;
history::MostVisitedURLList urls;
for (const auto& test_element : data) {
urls.push_back(test_element.entry);
}
std::move(callbacks_.front()).Run(std::move(urls));
callbacks_.pop_front();
return true;
}
const std::set<std::string>& blocked_urls() const { return blocked_urls_; }
protected:
// A test-specific field for controlling when most visited callback is run
// after top sites have been requested.
std::list<GetMostVisitedURLsCallback> callbacks_;
std::set<std::string> blocked_urls_;
~FakeTopSites() override = default;
};
constexpr const auto* WEB_URL = u"https://example.com/";
enum class ExpectedUiType { kAggregateMatch, kIndividualTiles };
const std::vector<TestData> DefaultTestData() {
return {{false, {GURL("http://www.a.art/"), u"A art"}},
{false, {GURL("http://www.b.biz/"), u"B biz"}},
{false, {GURL("http://www.c.com/"), u"C com"}},
{false, {GURL("http://www.d.de/"), u"D de"}},
{true, {GURL("http://www.google.com/search?q=abc"), u"abc"}}};
}
class MockHistoryService : public history::HistoryService {
public:
MockHistoryService() = default;
~MockHistoryService() override = default;
MOCK_METHOD(base::CancelableTaskTracker::TaskId,
QueryMostVisitedURLs,
(int result_count,
history::HistoryService::QueryMostVisitedURLsCallback callback,
base::CancelableTaskTracker* tracker,
const std::optional<std::string>& recency_factor_name,
std::optional<size_t> recency_window_days,
bool check_visual_deduplication_flag),
(override));
MOCK_METHOD(void, DeleteURLs, (const std::vector<GURL>& urls), (override));
};
} // namespace
class MostVisitedSitesProviderTest : public testing::Test,
public AutocompleteProviderListener {
public:
void SetUp() override;
protected:
// Construct AutocompleteInput object a hypothetical Omnibox session context.
// Does not run any validation on the supplied values, allowing any
// combination (including invalid ones) to be used to create AutocompleteInput
// context object.
AutocompleteInput BuildAutocompleteInput(
const std::u16string& input_url,
const std::u16string& current_url,
metrics::OmniboxEventProto::PageClassification page_class,
metrics::OmniboxFocusType focus_type) {
AutocompleteInput input(input_url, page_class, TestSchemeClassifier());
input.set_focus_type(focus_type);
input.set_current_url(GURL(current_url));
return input;
}
// Helper method, constructing a valid AutocompleteInput object for a website
// visit.
AutocompleteInput BuildAutocompleteInputForWebOnFocus() {
return BuildAutocompleteInput(WEB_URL, WEB_URL,
metrics::OmniboxEventProto::OTHER,
metrics::OmniboxFocusType::INTERACTION_FOCUS);
}
// A prefetch AutocompleteInput on Web.
AutocompleteInput BuildAutocompletePrefetchInputForWeb() {
return BuildAutocompleteInput(
WEB_URL, WEB_URL, metrics::OmniboxEventProto::OTHER_ZPS_PREFETCH,
metrics::OmniboxFocusType::INTERACTION_FOCUS);
}
// Iterate over all matches offered by the Provider and verify these against
// the supplied list of History URLs.
void CheckMatchesEquivalentTo(const std::vector<TestData>& data,
ExpectedUiType ui_type);
// Iterate over all matches offered by the Provider and verify these against
// the supplied list of History URLs for desktop.
void CheckDesktopMatchesEquivalentTo(const std::vector<TestData>& data,
size_t url_limit);
// Returns total number of all NAVSUGGEST and TILE_NAVSUGGEST elements.
size_t NumMostVisitedMatches();
// Returns the N-th match of a particular type, skipping over all matches of
// other types. If match of that type does not exist, or there are not enough
// elements of that type, this call returns null.
const AutocompleteMatch* GetMatch(AutocompleteMatchType::Type type,
size_t index);
// AutocompleteProviderListener:
void OnProviderUpdate(bool updated_matches,
const AutocompleteProvider* provider) override;
base::HistogramTester histogram_;
std::unique_ptr<base::test::SingleThreadTaskEnvironment> task_environment_;
FakeAutocompleteProviderClient client_;
scoped_refptr<FakeTopSites> top_sites_;
scoped_refptr<MostVisitedSitesProvider> provider_;
int provider_update_count_{};
};
size_t MostVisitedSitesProviderTest::NumMostVisitedMatches() {
const auto& result = provider_->matches();
size_t count = 0;
for (const auto& match : result) {
if ((match.type == AutocompleteMatchType::TILE_NAVSUGGEST) ||
(match.type == AutocompleteMatchType::NAVSUGGEST) ||
(match.type == AutocompleteMatchType::TILE_MOST_VISITED_SITE) ||
(match.type == AutocompleteMatchType::TILE_REPEATABLE_QUERY)) {
++count;
}
}
return count;
}
const AutocompleteMatch* MostVisitedSitesProviderTest::GetMatch(
AutocompleteMatchType::Type type,
size_t index) {
const auto& result = provider_->matches();
for (const auto& match : result) {
if (match.type == type) {
if (!index)
return &match;
--index;
}
}
return nullptr;
}
void MostVisitedSitesProviderTest::CheckMatchesEquivalentTo(
const std::vector<TestData>& data,
ExpectedUiType ui_type) {
// Compare the AutocompleteResult against a set of URLs that we expect to see.
// Note that additional matches may be offered if other providers are also
// registered in the same category as MostVisitedSitesProvider.
// We ignore all matches that are not ours.
const auto& result = provider_->matches();
size_t match_index = 0;
if (ui_type == ExpectedUiType::kAggregateMatch) {
ASSERT_EQ(1ul, NumMostVisitedMatches())
<< "Expected only one TILE_NAVSUGGEST match";
for (const auto& match : result) {
if (match.type != AutocompleteMatchType::TILE_NAVSUGGEST)
continue;
EXPECT_TRUE(match.subtypes.contains(
omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_URLS));
EXPECT_TRUE(match.subtypes.contains(omnibox::SUBTYPE_URL_BASED));
const auto& tiles = match.suggest_tiles;
ASSERT_EQ(data.size(), tiles.size()) << "Wrong number of tiles reported";
for (size_t index = 0u; index < data.size(); index++) {
EXPECT_EQ(data[index].entry.url, tiles[index].url)
<< "Invalid Tile URL at position " << index;
EXPECT_EQ(data[index].entry.title, tiles[index].title)
<< "Invalid Tile Title at position " << index;
}
break;
}
} else if (ui_type == ExpectedUiType::kIndividualTiles) {
ASSERT_EQ(data.size(), NumMostVisitedMatches())
<< "Unexpected number of TILE matches";
int expected_relevance = omnibox::kMostVisitedTilesZeroSuggestHighRelevance;
for (const auto& match : result) {
if (data[match_index].is_search) {
EXPECT_EQ(match.type, AutocompleteMatchType::TILE_REPEATABLE_QUERY);
EXPECT_TRUE(match.subtypes.contains(
omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_QUERIES));
} else {
EXPECT_EQ(match.type, AutocompleteMatchType::TILE_MOST_VISITED_SITE);
EXPECT_TRUE(match.subtypes.contains(
omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_URLS));
EXPECT_TRUE(match.subtypes.contains(omnibox::SUBTYPE_URL_BASED));
}
EXPECT_EQ(data[match_index].entry.url, match.destination_url)
<< "Invalid Match URL at position " << match_index;
EXPECT_EQ(data[match_index].entry.title, match.description)
<< "Invalid Match Title at position " << match_index;
EXPECT_EQ(expected_relevance, match.relevance)
<< "Invalid Match Relevance at position " << match_index;
++match_index;
--expected_relevance;
}
}
}
void MostVisitedSitesProviderTest::CheckDesktopMatchesEquivalentTo(
const std::vector<TestData>& data,
size_t url_limit) {
// Compare the AutocompleteResult against a set of URLs that we expect to see.
// Note that additional matches may be offered if other providers are also
// registered in the same category as MostVisitedSitesProvider.
// We ignore all matches that are not ours.
const auto& result = provider_->matches();
size_t match_index = 0;
ASSERT_EQ(url_limit, NumMostVisitedMatches())
<< "Unexpected number of TILE matches";
int expected_relevance = omnibox::kMostVisitedTilesZeroSuggestHighRelevance;
for (const auto& match : result) {
EXPECT_EQ(match.type, AutocompleteMatchType::TILE_MOST_VISITED_SITE);
EXPECT_TRUE(match.subtypes.contains(
omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_URLS));
EXPECT_TRUE(match.subtypes.contains(omnibox::SUBTYPE_URL_BASED));
EXPECT_EQ(data[match_index].entry.url, match.destination_url)
<< "Invalid Match URL at position " << match_index;
EXPECT_EQ(data[match_index].entry.title, match.description)
<< "Invalid Match Title at position " << match_index;
EXPECT_EQ(expected_relevance, match.relevance)
<< "Invalid Match Relevance at position " << match_index;
EXPECT_EQ(omnibox::GROUP_MOST_VISITED, match.suggestion_group_id);
++match_index;
--expected_relevance;
}
}
void MostVisitedSitesProviderTest::SetUp() {
task_environment_ =
std::make_unique<base::test::SingleThreadTaskEnvironment>();
top_sites_ = new FakeTopSites();
client_.set_top_sites(top_sites_);
// For tests requiring direct interaction with the Provider.
provider_ = new MostVisitedSitesProvider(&client_, this);
}
void MostVisitedSitesProviderTest::OnProviderUpdate(
bool updated_matches,
const AutocompleteProvider* provider) {
provider_update_count_++;
}
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
constexpr const auto* SRP_URL = u"https://www.google.com/?q=flowers";
constexpr const auto* FTP_URL = u"ftp://just.for.filtering.com";
TEST_F(MostVisitedSitesProviderTest, TestMostVisitedCallback) {
base::test::ScopedFeatureList features;
features.InitAndDisableFeature(
omnibox::kMostVisitedTilesHorizontalRenderGroup);
auto input = BuildAutocompleteInputForWebOnFocus();
provider_->Start(input, true);
EXPECT_EQ(0u, NumMostVisitedMatches());
auto test_data = DefaultTestData();
EXPECT_TRUE(top_sites_->EmitURLs(test_data));
CheckMatchesEquivalentTo(test_data, ExpectedUiType::kAggregateMatch);
EXPECT_EQ(1, provider_update_count_);
provider_->Stop(AutocompleteStopReason::kClobbered);
// Observe that subsequent request does not return stale data.
provider_->Start(input, true);
provider_->Stop(AutocompleteStopReason::kClobbered);
// Since this provider's async logic is still in-flight (`EmitURLs()` has not
// been called yet), we should not be reporting anything from past runs.
EXPECT_EQ(0ul, NumMostVisitedMatches());
EXPECT_EQ(1, provider_update_count_);
// Most visited results arriving after Stop() has been called, ensure they
// are not displayed.
std::vector<TestData> new_urls{{
false,
{GURL("http://www.g.gov/"), u"G gov"},
}};
EXPECT_TRUE(top_sites_->EmitURLs(new_urls));
EXPECT_EQ(0ul, NumMostVisitedMatches());
EXPECT_EQ(1, provider_update_count_);
provider_->Start(input, true);
provider_->Stop(AutocompleteStopReason::kClobbered);
provider_->Start(input, true);
// Stale results (reported for the first of the two Start() requests) should
// be rejected.
EXPECT_TRUE(top_sites_->EmitURLs(DefaultTestData()));
EXPECT_EQ(0ul, NumMostVisitedMatches());
EXPECT_EQ(1, provider_update_count_);
// Results for the second Start() action should be recorded.
EXPECT_TRUE(top_sites_->EmitURLs(test_data));
CheckMatchesEquivalentTo(test_data, ExpectedUiType::kAggregateMatch);
EXPECT_EQ(2, provider_update_count_);
provider_->Stop(AutocompleteStopReason::kClobbered);
}
TEST_F(MostVisitedSitesProviderTest, TestMostVisitedNavigateToSearchPage) {
provider_->Start(BuildAutocompleteInputForWebOnFocus(), true);
EXPECT_EQ(0u, NumMostVisitedMatches());
// Stop() doesn't always get called.
auto srp_input = BuildAutocompleteInput(
SRP_URL, SRP_URL,
metrics::OmniboxEventProto::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT,
metrics::OmniboxFocusType::INTERACTION_FOCUS);
provider_->Start(srp_input, true);
EXPECT_EQ(0u, NumMostVisitedMatches());
// Most visited results arriving after a new request has been started.
EXPECT_TRUE(top_sites_->EmitURLs(DefaultTestData()));
EXPECT_EQ(0u, NumMostVisitedMatches());
}
TEST_F(MostVisitedSitesProviderTest, AllowMostVisitedSitesSuggestions) {
// MostVisited should never deal with prefix suggestions.
EXPECT_FALSE(provider_->AllowMostVisitedSitesSuggestions(
&client_, BuildAutocompleteInput(WEB_URL, WEB_URL, OEP::OTHER,
OFT::INTERACTION_DEFAULT)));
// This should always be true, as otherwise we will break MostVisited.
EXPECT_TRUE(provider_->AllowMostVisitedSitesSuggestions(
&client_, BuildAutocompleteInput(WEB_URL, WEB_URL, OEP::OTHER,
OFT::INTERACTION_FOCUS)));
// Verifies that non-permitted schemes are rejected.
EXPECT_FALSE(provider_->AllowMostVisitedSitesSuggestions(
&client_, BuildAutocompleteInput(FTP_URL, FTP_URL, OEP::OTHER,
OFT::INTERACTION_FOCUS)));
// Offer MV sites when the User is visiting a website and deletes text.
EXPECT_TRUE(provider_->AllowMostVisitedSitesSuggestions(
&client_, BuildAutocompleteInput(WEB_URL, WEB_URL, OEP::OTHER,
OFT::INTERACTION_FOCUS)));
}
TEST_F(MostVisitedSitesProviderTest, NoSRPCoverage) {
EXPECT_FALSE(provider_->AllowMostVisitedSitesSuggestions(
&client_,
BuildAutocompleteInput(WEB_URL, WEB_URL,
OEP::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT,
OFT::INTERACTION_FOCUS)));
}
TEST_F(MostVisitedSitesProviderTest, TestCreateMostVisitedMatch) {
base::test::ScopedFeatureList features;
features.InitAndDisableFeature(
omnibox::kMostVisitedTilesHorizontalRenderGroup);
provider_->Start(BuildAutocompleteInputForWebOnFocus(), true);
EXPECT_EQ(0u, NumMostVisitedMatches());
// Accept only direct TopSites data.
auto test_data = DefaultTestData();
EXPECT_TRUE(top_sites_->EmitURLs(test_data));
CheckMatchesEquivalentTo(test_data, ExpectedUiType::kAggregateMatch);
}
TEST_F(MostVisitedSitesProviderTest, NoMatchesWhenNoMostVisitedSites) {
// Start with no URLs.
provider_->Start(BuildAutocompleteInputForWebOnFocus(), true);
EXPECT_EQ(0u, NumMostVisitedMatches());
// Accept only direct TopSites data, confirm no matches are built.
EXPECT_TRUE(top_sites_->EmitURLs({}));
EXPECT_EQ(0u, NumMostVisitedMatches());
}
TEST_F(MostVisitedSitesProviderTest,
NoMatchesWhenTopSitesNotLoadedAndWantAsyncMatchesFalse) {
// Assume that top sites list has not been loaded yet from the DB.
ASSERT_FALSE(top_sites_->loaded());
auto input = BuildAutocompleteInputForWebOnFocus();
input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_DEFAULT);
input.set_omit_asynchronous_matches(true);
provider_->Start(input, true);
EXPECT_TRUE(provider_->done());
EXPECT_EQ(0u, NumMostVisitedMatches());
// No callbacks should have been added due to early return.
EXPECT_FALSE(top_sites_->EmitURLs(DefaultTestData()));
EXPECT_EQ(0u, NumMostVisitedMatches());
}
TEST_F(MostVisitedSitesProviderTest, TestDeleteMostVisitedElement) {
base::test::ScopedFeatureList features;
features.InitAndDisableFeature(
omnibox::kMostVisitedTilesHorizontalRenderGroup);
// Make a copy (intentional - we'll modify this later)
provider_->Start(BuildAutocompleteInputForWebOnFocus(), true);
// Accept only direct TopSites data.
auto test_data = DefaultTestData();
EXPECT_TRUE(top_sites_->EmitURLs(test_data));
CheckMatchesEquivalentTo(test_data, ExpectedUiType::kAggregateMatch);
// Commence delete.
histogram_.ExpectTotalCount("Omnibox.SuggestTiles.TileTypeCount.Search", 1);
histogram_.ExpectBucketCount("Omnibox.SuggestTiles.TileTypeCount.Search", 1,
1);
histogram_.ExpectTotalCount("Omnibox.SuggestTiles.TileTypeCount.URL", 1);
histogram_.ExpectBucketCount("Omnibox.SuggestTiles.TileTypeCount.URL", 4, 1);
histogram_.ExpectTotalCount("Omnibox.SuggestTiles.DeletedTileIndex", 0);
auto* match = GetMatch(AutocompleteMatchType::TILE_NAVSUGGEST, 0);
ASSERT_NE(nullptr, match) << "No TILE_NAVSUGGEST Match found";
provider_->DeleteMatchElement(*match, 1);
histogram_.ExpectTotalCount("Omnibox.SuggestTiles.DeletedTileIndex", 1);
histogram_.ExpectBucketCount("Omnibox.SuggestTiles.DeletedTileIndex", 1, 1);
// Note: TileTypeCounts are not emitted after deletion.
// Observe that the URL is now blocked and removed from suggestion.
auto deleted_url = test_data[1].entry.url;
test_data.erase(test_data.begin() + 1);
CheckMatchesEquivalentTo(test_data, ExpectedUiType::kAggregateMatch);
EXPECT_TRUE(top_sites_->IsBlocked(deleted_url));
}
TEST_F(MostVisitedSitesProviderTest, NoMatchesWhenLastURLIsDeleted) {
base::test::ScopedFeatureList features;
features.InitAndDisableFeature(
omnibox::kMostVisitedTilesHorizontalRenderGroup);
// Start with just one URL.
std::vector<TestData> urls{{
{false, {GURL("http://www.a.art/"), u"A art"}},
}};
provider_->Start(BuildAutocompleteInputForWebOnFocus(), true);
EXPECT_TRUE(top_sites_->EmitURLs(urls));
CheckMatchesEquivalentTo(urls, ExpectedUiType::kAggregateMatch);
// Commence delete of the only item that we have.
histogram_.ExpectTotalCount("Omnibox.SuggestTiles.TileTypeCount.Search", 1);
histogram_.ExpectBucketCount("Omnibox.SuggestTiles.TileTypeCount.Search", 0,
1);
histogram_.ExpectTotalCount("Omnibox.SuggestTiles.TileTypeCount.URL", 1);
histogram_.ExpectBucketCount("Omnibox.SuggestTiles.TileTypeCount.URL", 1, 1);
histogram_.ExpectTotalCount("Omnibox.SuggestTiles.DeletedTileIndex", 0);
auto* match = GetMatch(AutocompleteMatchType::TILE_NAVSUGGEST, 0);
ASSERT_NE(nullptr, match) << "No TILE_NAVSUGGEST Match found";
provider_->DeleteMatchElement(*match, 0);
histogram_.ExpectTotalCount("Omnibox.SuggestTiles.DeletedTileIndex", 1);
histogram_.ExpectBucketCount("Omnibox.SuggestTiles.DeletedTileIndex", 0, 1);
// Note: TileTypeCounts are not emitted after deletion.
// Confirm no more NAVSUGGEST matches are offered.
EXPECT_EQ(0u, NumMostVisitedMatches());
}
TEST_F(MostVisitedSitesProviderTest,
TestCreateMostVisitedHorizontalGroupTiles) {
base::test::ScopedFeatureList features;
features.InitWithFeatures({omnibox::kMostVisitedTilesHorizontalRenderGroup},
{});
provider_->Start(BuildAutocompleteInputForWebOnFocus(), true);
EXPECT_EQ(0u, NumMostVisitedMatches());
// Accept only direct TopSites data.
auto test_data = DefaultTestData();
EXPECT_TRUE(top_sites_->EmitURLs(test_data));
CheckMatchesEquivalentTo(test_data, ExpectedUiType::kIndividualTiles);
}
#endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
#if !(BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS))
TEST_F(MostVisitedSitesProviderTest, TestCreateMostVisitedTopSitesMatches) {
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::OmniboxUrlSuggestionsOnFocus>
scoped_config;
scoped_config.Get().enabled = true;
scoped_config.Get().directly_query_history_service = false;
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::OmniboxZpsSuggestionLimit>
suggestion_limit_scoped_config;
suggestion_limit_scoped_config.Get().enabled = true;
suggestion_limit_scoped_config.Get().max_suggestions = 8U;
suggestion_limit_scoped_config.Get().max_url_suggestions = 4U;
suggestion_limit_scoped_config.Get().max_search_suggestions = 4U;
provider_->Start(BuildAutocompleteInputForWebOnFocus(), true);
EXPECT_EQ(0u, NumMostVisitedMatches());
// Accept only direct TopSites data.
auto test_data = DefaultTestData();
EXPECT_TRUE(top_sites_->EmitURLs(test_data));
CheckDesktopMatchesEquivalentTo(
test_data, suggestion_limit_scoped_config.Get().max_url_suggestions);
}
TEST_F(MostVisitedSitesProviderTest, DesktopProviderDoesNotAllowChromeSites) {
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::OmniboxUrlSuggestionsOnFocus>
scoped_config;
scoped_config.Get().enabled = true;
const auto* chrome_url = u"chrome://omnibox";
// Verifies that non-permitted schemes are rejected.
EXPECT_TRUE(provider_->AllowMostVisitedSitesSuggestions(
&client_, BuildAutocompleteInputForWebOnFocus()));
// Verifies that non-permitted schemes are rejected.
EXPECT_FALSE(provider_->AllowMostVisitedSitesSuggestions(
&client_, BuildAutocompleteInput(chrome_url, chrome_url, OEP::OTHER,
OFT::INTERACTION_FOCUS)));
}
TEST_F(MostVisitedSitesProviderTest, TestDesktopQueryingHistoryService) {
// Set a MockHistoryService.
auto history_service = std::make_unique<MockHistoryService>();
auto& history_service_ref = *history_service;
client_.set_history_service(std::move(history_service));
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::OmniboxUrlSuggestionsOnFocus>
scoped_config;
scoped_config.Get().enabled = true;
scoped_config.Get().most_visited_recency_window = 4;
scoped_config.Get().most_visited_recency_factor =
history::kMvtScoringParamRecencyFactor_Classic;
scoped_config.Get().prefetch_most_visited_sites = false;
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::OmniboxZpsSuggestionLimit>
suggestion_limit_scoped_config;
suggestion_limit_scoped_config.Get().enabled = true;
suggestion_limit_scoped_config.Get().max_suggestions = 8U;
AutocompleteInput input(BuildAutocompleteInputForWebOnFocus());
history::HistoryService::QueryMostVisitedURLsCallback callback;
EXPECT_CALL(history_service_ref, QueryMostVisitedURLs(_, _, _, _, _, _))
.WillOnce([&](int result_count,
history::HistoryService::QueryMostVisitedURLsCallback cb,
base::CancelableTaskTracker* tracker,
std::optional<std::string> recency_factor_name,
std::optional<size_t> recency_window_days,
bool check_visual_deduplication_flag)
-> base::CancelableTaskTracker::TaskId {
// Add 1 to simulate 1 site being open.
EXPECT_EQ(static_cast<int>(provider_->GetRequestedResultSize(input)),
result_count);
EXPECT_TRUE(recency_factor_name.has_value());
EXPECT_EQ(history::kMvtScoringParamRecencyFactor_Classic,
recency_factor_name.value());
EXPECT_TRUE(recency_window_days.has_value());
EXPECT_EQ(4u, recency_window_days.value());
callback = std::move(cb);
return {};
});
provider_->Start(input, false);
EXPECT_FALSE(provider_->done());
history::MostVisitedURLList result;
for (const auto& test_element : DefaultTestData()) {
result.push_back(test_element.entry);
}
std::move(callback).Run(std::move(result));
// Shouldn't include the match from the SRP.
EXPECT_TRUE(provider_->done());
ASSERT_EQ(4u, provider_->matches().size());
}
TEST_F(MostVisitedSitesProviderTest, TestDeleteMatch) {
// Set a MockHistoryService.
auto history_service = std::make_unique<MockHistoryService>();
auto& history_service_ref = *history_service;
client_.set_history_service(std::move(history_service));
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::OmniboxUrlSuggestionsOnFocus>
scoped_config;
scoped_config.Get().enabled = true;
scoped_config.Get().prefetch_most_visited_sites = false;
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::OmniboxZpsSuggestionLimit>
suggestion_limit_scoped_config;
suggestion_limit_scoped_config.Get().enabled = true;
suggestion_limit_scoped_config.Get().max_suggestions = 8U;
AutocompleteInput input(BuildAutocompleteInputForWebOnFocus());
history::HistoryService::QueryMostVisitedURLsCallback callback;
EXPECT_CALL(history_service_ref, QueryMostVisitedURLs(_, _, _, _, _, _))
.WillOnce([&](int result_count,
history::HistoryService::QueryMostVisitedURLsCallback cb,
base::CancelableTaskTracker* tracker,
std::optional<std::string> recency_factor_name,
std::optional<size_t> recency_window_days,
bool check_visual_deduplication_flag)
-> base::CancelableTaskTracker::TaskId {
// Add 1 to simulate 1 site being open.
EXPECT_EQ(static_cast<int>(provider_->GetRequestedResultSize(input)),
result_count);
callback = std::move(cb);
return {};
});
provider_->Start(input, false);
histogram_.ExpectTotalCount(
"Omnibox.MostVisitedProvider.QueryMostVisitedURLs.NumRequested", 1);
EXPECT_FALSE(provider_->done());
history::MostVisitedURLList result;
for (const auto& test_element : DefaultTestData()) {
result.push_back(test_element.entry);
}
std::move(callback).Run(std::move(result));
histogram_.ExpectTotalCount(
"Omnibox.MostVisitedProvider.QueryMostVisitedURLs.NumReceived", 1);
histogram_.ExpectTotalCount(
"Omnibox.MostVisitedProvider.QueryMostVisitedURLs.Duration", 1);
// Shouldn't include the match from the SRP.
EXPECT_TRUE(provider_->done());
ASSERT_EQ(4u, provider_->matches().size());
EXPECT_CALL(history_service_ref, DeleteURLs(_))
.WillOnce([&](const std::vector<GURL>& urls) -> void {
EXPECT_FALSE(urls.empty());
EXPECT_EQ(provider_->matches().at(0).destination_url, urls.at(0));
return;
});
provider_->DeleteMatch(provider_->matches().at(0));
ASSERT_EQ(3u, provider_->matches().size());
}
TEST_F(MostVisitedSitesProviderTest, PrefetchingUpdatesCachedSites) {
// Set a MockHistoryService.
auto history_service = std::make_unique<MockHistoryService>();
auto& history_service_ref = *history_service;
client_.set_history_service(std::move(history_service));
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::OmniboxUrlSuggestionsOnFocus>
scoped_config;
scoped_config.Get().enabled = true;
scoped_config.Get().prefetch_most_visited_sites = true;
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::OmniboxZpsSuggestionLimit>
suggestion_limit_scoped_config;
suggestion_limit_scoped_config.Get().enabled = true;
suggestion_limit_scoped_config.Get().max_suggestions = 8U;
AutocompleteInput input(BuildAutocompletePrefetchInputForWeb());
history::HistoryService::QueryMostVisitedURLsCallback callback;
EXPECT_CALL(history_service_ref, QueryMostVisitedURLs(_, _, _, _, _, _))
.Times(2)
.WillRepeatedly(
[&](int result_count,
history::HistoryService::QueryMostVisitedURLsCallback cb,
base::CancelableTaskTracker* tracker,
std::optional<std::string> recency_factor_name,
std::optional<size_t> recency_window_days,
bool check_visual_deduplication_flag)
-> base::CancelableTaskTracker::TaskId {
// Add 1 to simulate 1 site being open.
EXPECT_EQ(
static_cast<int>(provider_->GetRequestedResultSize(input)),
result_count);
callback = std::move(cb);
return {};
});
// `StartPrefetch()` shouldn't affect provider state.
EXPECT_TRUE(provider_->done());
provider_->StartPrefetch(input);
// Number of sites requested should be logged at the beginnign of the history
// query.
histogram_.ExpectTotalCount(
"Omnibox.MostVisitedProvider.QueryMostVisitedURLs.NumRequested", 1);
EXPECT_TRUE(provider_->done());
history::MostVisitedURLList result;
for (const auto& test_element : DefaultTestData()) {
result.push_back(test_element.entry);
}
// Updates cache.
std::move(callback).Run(std::move(result));
// Duration should be logged when cache is updated.
histogram_.ExpectTotalCount(
"Omnibox.MostVisitedProvider.QueryMostVisitedURLs.Duration", 1);
histogram_.ExpectTotalCount(
"Omnibox.MostVisitedProvider.QueryMostVisitedURLs.NumReceived", 1);
// `StartPrefetch()` should update the list of cached sites, but not
// update the actual provider's matches.
ASSERT_EQ(5u, provider_->GetCachedSitesForTesting().size());
ASSERT_EQ(0u, provider_->matches().size());
// Calling Start() should use the cached sites to create matches. Shouldn't
// include the SRP result.
provider_->Start(input, false);
ASSERT_EQ(4u, provider_->matches().size());
// Duration shouldn't be recorded again when results are returned
// synchronously.
histogram_.ExpectTotalCount(
"Omnibox.MostVisitedProvider.QueryMostVisitedURLs.Duration", 1);
histogram_.ExpectTotalCount(
"Omnibox.MostVisitedProvider.QueryMostVisitedURLs.NumReceived", 1);
}
TEST_F(MostVisitedSitesProviderTest,
StartDoesNotUpdateMatchesWhenPrefetchEnabled) {
// Set a MockHistoryService.
auto history_service = std::make_unique<MockHistoryService>();
auto& history_service_ref = *history_service;
client_.set_history_service(std::move(history_service));
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::OmniboxUrlSuggestionsOnFocus>
scoped_config;
scoped_config.Get().enabled = true;
scoped_config.Get().prefetch_most_visited_sites = true;
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::OmniboxZpsSuggestionLimit>
suggestion_limit_scoped_config;
suggestion_limit_scoped_config.Get().enabled = true;
suggestion_limit_scoped_config.Get().max_suggestions = 8U;
AutocompleteInput input(BuildAutocompleteInputForWebOnFocus());
history::HistoryService::QueryMostVisitedURLsCallback callback;
EXPECT_CALL(history_service_ref, QueryMostVisitedURLs(_, _, _, _, _, _))
.WillOnce([&](int result_count,
history::HistoryService::QueryMostVisitedURLsCallback cb,
base::CancelableTaskTracker* tracker,
std::optional<std::string> recency_factor_name,
std::optional<size_t> recency_window_days,
bool check_visual_deduplication_flag)
-> base::CancelableTaskTracker::TaskId {
// Add 1 to simulate 1 site being open.
EXPECT_EQ(static_cast<int>(provider_->GetRequestedResultSize(input)),
result_count);
callback = std::move(cb);
return {};
});
provider_->Start(input, false);
history::MostVisitedURLList result;
for (const auto& test_element : DefaultTestData()) {
result.push_back(test_element.entry);
}
std::move(callback).Run(std::move(result));
EXPECT_TRUE(provider_->done());
// `Start()` when prefetch is enabled should update the list of cached sites,
// but not update the actual provider's matches if the cache is empty.
ASSERT_EQ(5u, provider_->GetCachedSitesForTesting().size());
ASSERT_EQ(0u, provider_->matches().size());
}
TEST_F(MostVisitedSitesProviderTest, BlocklistedURLs) {
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::OmniboxUrlSuggestionsOnFocus>
scoped_config;
scoped_config.Get().enabled = true;
AutocompleteInput input(BuildAutocompleteInputForWebOnFocus());
history::MostVisitedURLList result;
std::vector<TestData> test_data = {
{false, {GURL("http://www.nonblocked.com"), u"Non blocked"}},
{false, {GURL("http://accounts.google.com/signin"), u"Blocked"}}};
result.push_back(test_data[0].entry);
result.push_back(test_data[1].entry);
provider_->OnMostVisitedUrlsAvailable(input, result);
// Shouldn't include the "accounts.google.com" URL since it is blocklisted.
ASSERT_EQ(1u, provider_->matches().size());
ASSERT_EQ("http://www.nonblocked.com/",
provider_->matches().at(0).destination_url.spec());
}
TEST_F(MostVisitedSitesProviderTest, TestDeleteWithPrefetching) {
// Set a MockHistoryService.
auto history_service = std::make_unique<MockHistoryService>();
auto& history_service_ref = *history_service;
client_.set_history_service(std::move(history_service));
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::OmniboxUrlSuggestionsOnFocus>
scoped_config;
scoped_config.Get().enabled = true;
scoped_config.Get().prefetch_most_visited_sites = true;
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::OmniboxZpsSuggestionLimit>
suggestion_limit_scoped_config;
suggestion_limit_scoped_config.Get().enabled = true;
suggestion_limit_scoped_config.Get().max_suggestions = 8U;
history::HistoryService::QueryMostVisitedURLsCallback callback;
EXPECT_CALL(history_service_ref, QueryMostVisitedURLs(_, _, _, _, _, _))
.WillRepeatedly(
[&](int result_count,
history::HistoryService::QueryMostVisitedURLsCallback cb,
base::CancelableTaskTracker* tracker,
std::optional<std::string> recency_factor_name,
std::optional<size_t> recency_window_days,
bool check_visual_deduplication_flag)
-> base::CancelableTaskTracker::TaskId {
callback = std::move(cb);
return {};
});
AutocompleteInput input(BuildAutocompleteInputForWebOnFocus());
provider_->StartPrefetch(input);
history::MostVisitedURLList result;
// Populates cached sites.
for (const auto& test_element : DefaultTestData()) {
result.push_back(test_element.entry);
}
std::move(callback).Run(std::move(result));
ASSERT_EQ(5u, provider_->GetCachedSitesForTesting().size());
// Shouldn't include the match from the SRP.
EXPECT_TRUE(provider_->done());
ASSERT_EQ(0u, provider_->matches().size());
EXPECT_CALL(history_service_ref, DeleteURLs(_))
.WillOnce([&](const std::vector<GURL>& urls) -> void {
EXPECT_FALSE(urls.empty());
EXPECT_EQ(provider_->matches().at(0).destination_url, urls.at(0));
return;
});
// Calling `Start()` will use the list of cached sites, but won't include the
// SRP site..
provider_->Start(input, false);
ASSERT_EQ(4u, provider_->matches().size());
provider_->DeleteMatch(provider_->matches().at(0));
// `DeleteMatch()` should remove from both the cached sites list and from
// `matches_`.
ASSERT_EQ(3u, provider_->matches().size());
ASSERT_EQ(4u, provider_->GetCachedSitesForTesting().size());
// Calling `Start()` uses the `cached_sites_`, which should now only return
// 3 valid sites.
provider_->Start(input, false);
ASSERT_EQ(3u, provider_->matches().size());
}
TEST_F(MostVisitedSitesProviderTest, DuplicateSuggestions) {
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::OmniboxUrlSuggestionsOnFocus>
scoped_config;
scoped_config.Get().enabled = true;
std::vector<TestData> test_data = {
{false, {GURL("http://www.mail.com"), u"Mail"}},
{false, {GURL("http://different.mail.com/signin"), u"Mail"}},
{false, {GURL("http://www.samesites.com/c/#ref"), u"Same URL"}},
{false,
{GURL("http://www.samesites.com/c/#differentref"), u"Same URL 2"}},
{false,
{GURL("http://www.samesites.com/differentpath/#ref"),
u"Different URL"}}};
history::MostVisitedURLList result;
for (auto& data : test_data) {
result.push_back(data.entry);
}
AutocompleteInput input(BuildAutocompleteInputForWebOnFocus());
provider_->OnMostVisitedUrlsAvailable(input, result);
// Filter by same titles and stripped urls when deduping suggestions within
// the suggestion list.
ASSERT_EQ(3u, provider_->matches().size());
ASSERT_EQ("http://www.mail.com/",
provider_->matches().at(0).destination_url.spec());
ASSERT_EQ("http://www.samesites.com/c/#ref",
provider_->matches().at(1).destination_url.spec());
ASSERT_EQ("http://www.samesites.com/differentpath/#ref",
provider_->matches().at(2).destination_url.spec());
}
TEST_F(MostVisitedSitesProviderTest, TestProviderDoneWithEmptyCachedSites) {
// Set a MockHistoryService.
auto history_service = std::make_unique<MockHistoryService>();
auto& history_service_ref = *history_service;
client_.set_history_service(std::move(history_service));
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::OmniboxUrlSuggestionsOnFocus>
scoped_config;
scoped_config.Get().enabled = true;
scoped_config.Get().prefetch_most_visited_sites = true;
history::HistoryService::QueryMostVisitedURLsCallback callback;
EXPECT_CALL(history_service_ref, QueryMostVisitedURLs(_, _, _, _, _, _))
.WillRepeatedly(
[&](int result_count,
history::HistoryService::QueryMostVisitedURLsCallback cb,
base::CancelableTaskTracker* tracker,
std::optional<std::string> recency_factor_name,
std::optional<size_t> recency_window_days,
bool check_visual_deduplication_flag)
-> base::CancelableTaskTracker::TaskId {
callback = std::move(cb);
return {};
});
AutocompleteInput input(BuildAutocompleteInputForWebOnFocus());
// Simulate a prefetch that returns no results.
provider_->StartPrefetch(input);
history::MostVisitedURLList result;
std::move(callback).Run(std::move(result));
ASSERT_EQ(0u, provider_->GetCachedSitesForTesting().size());
// Provider should still be set to done immediately after calling `Start()`
// even if cached sites is empty.
provider_->Start(input, false);
EXPECT_TRUE(provider_->done());
}
TEST_F(MostVisitedSitesProviderTest, DedupingOpenTabs) {
omnibox_feature_configs::ScopedConfigForTesting<
omnibox_feature_configs::OmniboxUrlSuggestionsOnFocus>
scoped_config;
scoped_config.Get().enabled = true;
AutocompleteInput input(BuildAutocompleteInputForWebOnFocus());
history::MostVisitedURLList result;
FakeTabMatcher& tab_matcher = static_cast<FakeTabMatcher&>(
const_cast<TabMatcher&>(client_.GetTabMatcher()));
tab_matcher.AddOpenTab(FakeTabMatcher::TabWrapper(
u"Test", GURL("http://foo.org/path/#ref"), base::Time::Now()));
tab_matcher.AddOpenTab(FakeTabMatcher::TabWrapper(
u"Testing", GURL("http://bar.org"), base::Time::Now()));
std::vector<TestData> test_data = {
// Tabs with same title and url should be considered matches.
{false, {GURL("http://bar.org"), u"Testing"}},
// Tabs with same title should be considered matches even if urls are
// different.
{false, {GURL("http://different.org"), u"Testing"}},
// Sites with same path and different refs should be considered matches.
{false, {GURL("http://foo.org/path/#differentref"), u"Different Name"}},
// Sites with different path should not be considered matches.
{false, {GURL("http://foo.org/differentpath/#ref"), u"Different Name"}},
// Sites with same match and different refs and query params should be
// considered matches.
{false, {GURL("http://foo.org/path/#ref?param=123"), u"Different Name"}}};
for (auto& data : test_data) {
result.push_back(data.entry);
}
provider_->OnMostVisitedUrlsAvailable(input, result);
ASSERT_EQ(1u, provider_->matches().size());
ASSERT_EQ("http://foo.org/differentpath/#ref",
provider_->matches().at(0).destination_url.spec());
}
#endif // !(BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS))