Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(310)

Unified Diff: chrome/browser/android/most_visited_sites.cc

Issue 1330773002: [Android] Persist ordering of NTP suggestions. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: nit Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/android/most_visited_sites.h ('k') | chrome/browser/android/most_visited_sites_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/browser/android/most_visited_sites.cc
diff --git a/chrome/browser/android/most_visited_sites.cc b/chrome/browser/android/most_visited_sites.cc
index 72733008c0f002c5f60635eeab19ebf028f4c220..9b0c1746d7279584f9f04e4d641ea66f817fa9d6 100644
--- a/chrome/browser/android/most_visited_sites.cc
+++ b/chrome/browser/android/most_visited_sites.cc
@@ -12,6 +12,7 @@
#include "base/command_line.h"
#include "base/metrics/histogram.h"
#include "base/metrics/sparse_histogram.h"
+#include "base/prefs/pref_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
@@ -26,7 +27,9 @@
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/thumbnails/thumbnail_list_source.h"
#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
#include "components/history/core/browser/top_sites.h"
+#include "components/pref_registry/pref_registry_syncable.h"
#include "components/suggestions/suggestions_service.h"
#include "components/suggestions/suggestions_utils.h"
#include "content/public/browser/browser_thread.h"
@@ -131,6 +134,41 @@ std::string GetPopularSitesFilename() {
} // namespace
+MostVisitedSites::Suggestion::Suggestion(const base::string16& title,
+ const std::string& url,
+ MostVisitedSource source)
+ : title(title), url(url), source(source), provider_index(-1) {}
+
+MostVisitedSites::Suggestion::Suggestion(const base::string16& title,
+ const GURL& url,
+ MostVisitedSource source)
+ : title(title), url(url), source(source), provider_index(-1) {}
+
+MostVisitedSites::Suggestion::Suggestion(const base::string16& title,
+ const std::string& url,
+ MostVisitedSource source,
+ int provider_index)
+ : title(title), url(url), source(source), provider_index(provider_index) {
+ DCHECK_EQ(MostVisitedSites::SUGGESTIONS_SERVICE, source);
+}
+
+MostVisitedSites::Suggestion::~Suggestion() {}
+
+std::string MostVisitedSites::Suggestion::GetSourceHistogramName() const {
+ switch (source) {
+ case MostVisitedSites::TOP_SITES:
+ return kHistogramClientName;
+ case MostVisitedSites::POPULAR:
+ return kHistogramPopularName;
+ case MostVisitedSites::SUGGESTIONS_SERVICE:
+ return provider_index >= 0
+ ? base::StringPrintf(kHistogramServerFormat, provider_index)
+ : kHistogramServerName;
+ }
+ NOTREACHED();
+ return std::string();
+}
+
MostVisitedSites::MostVisitedSites(Profile* profile)
: profile_(profile), num_sites_(0), received_most_visited_sites_(false),
received_popular_sites_(false), recorded_uma_(false),
@@ -291,9 +329,10 @@ void MostVisitedSites::RecordOpenedMostVisitedItem(JNIEnv* env,
jobject obj,
jint index) {
DCHECK_GE(index, 0);
- DCHECK_LT(index, static_cast<int>(tile_sources_.size()));
- std::string histogram = base::StringPrintf(kOpenedItemHistogramFormat,
- tile_sources_[index].c_str());
+ DCHECK_LT(index, static_cast<int>(current_suggestions_.size()));
+ std::string histogram = base::StringPrintf(
+ kOpenedItemHistogramFormat,
+ current_suggestions_[index]->GetSourceHistogramName().c_str());
LogHistogramEvent(histogram, index, num_sites_);
}
@@ -309,6 +348,13 @@ bool MostVisitedSites::Register(JNIEnv* env) {
return RegisterNativesImpl(env);
}
+// static
+void MostVisitedSites::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterListPref(prefs::kNTPSuggestionsURL);
+ registry->RegisterListPref(prefs::kNTPSuggestionsIsPersonal);
+}
+
void MostVisitedSites::QueryMostVisitedURLs() {
SuggestionsService* suggestions_service =
SuggestionsServiceFactory::GetForProfile(profile_);
@@ -336,25 +382,23 @@ void MostVisitedSites::InitiateTopSitesQuery() {
void MostVisitedSites::OnMostVisitedURLsAvailable(
const history::MostVisitedURLList& visited_list) {
- std::vector<base::string16> titles;
- std::vector<std::string> urls;
- tile_sources_.clear();
- int num_tiles = std::min(static_cast<int>(visited_list.size()), num_sites_);
- for (int i = 0; i < num_tiles; ++i) {
+ ScopedVector<Suggestion> suggestions;
+ size_t num_tiles =
+ std::min(visited_list.size(), static_cast<size_t>(num_sites_));
+ for (size_t i = 0; i < num_tiles; ++i) {
const history::MostVisitedURL& visited = visited_list[i];
if (visited.url.is_empty()) {
num_tiles = i;
break; // This is the signal that there are no more real visited sites.
}
- titles.push_back(visited.title);
- urls.push_back(visited.url.spec());
- tile_sources_.push_back(kHistogramClientName);
+ suggestions.push_back(
+ new Suggestion(visited.title, visited.url.spec(), TOP_SITES));
}
received_most_visited_sites_ = true;
mv_source_ = TOP_SITES;
- AddPopularSites(&titles, &urls);
- NotifyMostVisitedURLsObserver(titles, urls);
+ AddPopularSites(&suggestions);
+ NotifyMostVisitedURLsObserver();
}
void MostVisitedSites::OnSuggestionsProfileAvailable(
@@ -368,150 +412,220 @@ void MostVisitedSites::OnSuggestionsProfileAvailable(
if (num_sites_ < num_tiles)
num_tiles = num_sites_;
- std::vector<base::string16> titles;
- std::vector<std::string> urls;
- tile_sources_.clear();
+ ScopedVector<Suggestion> suggestions;
for (int i = 0; i < num_tiles; ++i) {
const ChromeSuggestion& suggestion = suggestions_profile.suggestions(i);
- titles.push_back(base::UTF8ToUTF16(suggestion.title()));
- urls.push_back(suggestion.url());
- std::string tile_source;
- if (suggestion.providers_size() > 0) {
- tile_source =
- base::StringPrintf(kHistogramServerFormat, suggestion.providers(0));
- } else {
- tile_source = kHistogramServerName;
- }
- tile_sources_.push_back(tile_source);
+ suggestions.push_back(new Suggestion(
+ base::UTF8ToUTF16(suggestion.title()), suggestion.url(),
+ SUGGESTIONS_SERVICE,
+ suggestion.providers_size() > 0 ? suggestion.providers(0) : -1));
}
received_most_visited_sites_ = true;
mv_source_ = SUGGESTIONS_SERVICE;
- AddPopularSites(&titles, &urls);
- NotifyMostVisitedURLsObserver(titles, urls);
+ AddPopularSites(&suggestions);
+ NotifyMostVisitedURLsObserver();
}
-void MostVisitedSites::AddPopularSites(std::vector<base::string16>* titles,
- std::vector<std::string>* urls) {
- if (!popular_sites_)
- return;
+void MostVisitedSites::AddPopularSites(
+ ScopedVector<Suggestion>* personal_suggestions) {
+ size_t num_personal_suggestions = personal_suggestions->size();
+ DCHECK_LE(num_personal_suggestions, static_cast<size_t>(num_sites_));
+
+ // Collect non-blacklisted popular suggestions, skipping those already present
+ // in the personal suggestions.
+ size_t num_popular_suggestions = num_sites_ - num_personal_suggestions;
+ ScopedVector<Suggestion> popular_suggestions;
+ popular_suggestions.reserve(num_popular_suggestions);
+
+ if (num_popular_suggestions > 0 && popular_sites_) {
+ std::set<std::string> personal_hosts;
+ for (const Suggestion* suggestion : *personal_suggestions)
+ personal_hosts.insert(suggestion->url.host());
+ scoped_refptr<TopSites> top_sites(TopSitesFactory::GetForProfile(profile_));
+ for (const PopularSites::Site& popular_site : popular_sites_->sites()) {
+ // Skip blacklisted sites.
+ if (top_sites && top_sites->IsBlacklisted(popular_site.url))
+ continue;
+ std::string host = popular_site.url.host();
+ // Skip suggestions already present in personal.
+ if (personal_hosts.find(host) != personal_hosts.end())
+ continue;
+
+ popular_suggestions.push_back(
+ new Suggestion(popular_site.title, popular_site.url, POPULAR));
+ if (popular_suggestions.size() >= num_popular_suggestions)
+ break;
+ }
+ }
+ num_popular_suggestions = popular_suggestions.size();
+ size_t num_actual_tiles = num_personal_suggestions + num_popular_suggestions;
+ std::vector<std::string> old_sites_url;
+ std::vector<bool> old_sites_is_personal;
+ GetPreviousNTPSites(num_actual_tiles, &old_sites_url, &old_sites_is_personal);
+ ScopedVector<Suggestion> merged_suggestions =
+ MergeSuggestions(personal_suggestions, &popular_suggestions,
+ old_sites_url, old_sites_is_personal);
+ DCHECK_EQ(num_actual_tiles, merged_suggestions.size());
+ current_suggestions_.swap(merged_suggestions);
+ if (received_popular_sites_)
+ SaveCurrentNTPSites();
+}
- DCHECK_EQ(titles->size(), urls->size());
- DCHECK_EQ(titles->size(), tile_sources_.size());
- DCHECK_LE(static_cast<int>(titles->size()), num_sites_);
+// static
+ScopedVector<MostVisitedSites::Suggestion> MostVisitedSites::MergeSuggestions(
+ ScopedVector<Suggestion>* personal_suggestions,
+ ScopedVector<Suggestion>* popular_suggestions,
+ const std::vector<std::string>& old_sites_url,
+ const std::vector<bool>& old_sites_is_personal) {
+ size_t num_personal_suggestions = personal_suggestions->size();
+ size_t num_popular_suggestions = popular_suggestions->size();
+ size_t num_tiles = num_popular_suggestions + num_personal_suggestions;
+ ScopedVector<Suggestion> merged_suggestions;
+ merged_suggestions.resize(num_tiles);
+
+ size_t num_old_tiles = old_sites_url.size();
+ DCHECK_LE(num_old_tiles, num_tiles);
+ DCHECK_EQ(num_old_tiles, old_sites_is_personal.size());
+ std::vector<std::string> old_sites_host;
+ old_sites_host.reserve(num_old_tiles);
+ // Only populate the hosts for popular suggestions as only they can be
+ // replaced by host. Personal suggestions require an exact url match to be
+ // replaced.
+ for (size_t i = 0; i < num_old_tiles; ++i) {
+ old_sites_host.push_back(old_sites_is_personal[i]
+ ? std::string()
+ : GURL(old_sites_url[i]).host());
+ }
- // Collect all non-blacklisted popular suggestions.
- std::vector<base::string16> popular_titles;
- std::vector<std::string> popular_urls;
- scoped_refptr<TopSites> top_sites(TopSitesFactory::GetForProfile(profile_));
- for (const PopularSites::Site& popular_site : popular_sites_->sites()) {
- // Skip blacklisted sites.
- if (top_sites && top_sites->IsBlacklisted(popular_site.url))
- continue;
+ // Insert personal suggestions if they existed previously.
+ std::vector<size_t> new_personal_suggestions = InsertMatchingSuggestions(
+ personal_suggestions, &merged_suggestions, old_sites_url, old_sites_host);
+ // Insert popular suggestions if they existed previously.
+ std::vector<size_t> new_popular_suggestions = InsertMatchingSuggestions(
+ popular_suggestions, &merged_suggestions, old_sites_url, old_sites_host);
+ // Insert leftover personal suggestions.
+ size_t filled_so_far = InsertAllSuggestions(
+ 0, new_personal_suggestions, personal_suggestions, &merged_suggestions);
+ // Insert leftover popular suggestions.
+ InsertAllSuggestions(filled_so_far, new_popular_suggestions,
+ popular_suggestions, &merged_suggestions);
+ return merged_suggestions.Pass();
+}
- popular_titles.push_back(popular_site.title);
- popular_urls.push_back(popular_site.url.spec());
- if (static_cast<int>(popular_titles.size()) >= num_sites_)
- break;
+void MostVisitedSites::GetPreviousNTPSites(
+ size_t num_tiles,
+ std::vector<std::string>* old_sites_url,
+ std::vector<bool>* old_sites_is_personal) const {
+ const PrefService* prefs = profile_->GetPrefs();
+ const base::ListValue* url_list = prefs->GetList(prefs::kNTPSuggestionsURL);
+ const base::ListValue* source_list =
+ prefs->GetList(prefs::kNTPSuggestionsIsPersonal);
+ DCHECK_EQ(url_list->GetSize(), source_list->GetSize());
+ if (url_list->GetSize() < num_tiles)
+ num_tiles = url_list->GetSize();
+ if (num_tiles == 0) {
+ // No fallback required as Personal suggestions take precedence anyway.
+ return;
}
+ old_sites_url->reserve(num_tiles);
+ old_sites_is_personal->reserve(num_tiles);
+ for (size_t i = 0; i < num_tiles; ++i) {
+ std::string url_string;
+ bool success = url_list->GetString(i, &url_string);
+ DCHECK(success);
+ old_sites_url->push_back(url_string);
+ bool is_personal;
+ success = source_list->GetBoolean(i, &is_personal);
+ DCHECK(success);
+ old_sites_is_personal->push_back(is_personal);
+ }
+}
- AddPopularSitesImpl(
- num_sites_, popular_titles, popular_urls, titles, urls, &tile_sources_);
+void MostVisitedSites::SaveCurrentNTPSites() {
+ base::ListValue url_list;
+ base::ListValue source_list;
+ for (const Suggestion* suggestion : current_suggestions_) {
+ url_list.AppendString(suggestion->url.spec());
+ source_list.AppendBoolean(suggestion->source != MostVisitedSites::POPULAR);
+ }
+ PrefService* prefs = profile_->GetPrefs();
+ prefs->Set(prefs::kNTPSuggestionsIsPersonal, source_list);
+ prefs->Set(prefs::kNTPSuggestionsURL, url_list);
}
// static
-void MostVisitedSites::AddPopularSitesImpl(
- int num_sites,
- const std::vector<base::string16>& popular_titles,
- const std::vector<std::string>& popular_urls,
- std::vector<base::string16>* titles,
- std::vector<std::string>* urls,
- std::vector<std::string>* tile_sources) {
- // Start off with the popular suggestions.
- std::vector<base::string16> new_titles(popular_titles);
- std::vector<std::string> new_urls(popular_urls);
- std::vector<std::string> new_tile_sources(new_titles.size(),
- kHistogramPopularName);
-
- // Now, go over the personalized suggestions and replace matching popular
- // suggestions. This is so that when some of the popular suggestions become
- // personal, they retain their absolute positions.
- std::vector<base::string16> titles_to_insert;
- std::vector<std::string> urls_to_insert;
- std::vector<std::string> tile_sources_to_insert;
- for (size_t site_index = 0; site_index < titles->size(); site_index++) {
- const base::string16& title = (*titles)[site_index];
- const std::string& url = (*urls)[site_index];
- const std::string& tile_source = (*tile_sources)[site_index];
- // See if we already have a matching popular site.
- bool found = false;
- for (size_t i = 0; i < new_urls.size(); i++) {
- if (new_tile_sources[i] == kHistogramPopularName &&
- GURL(new_urls[i]).host() == GURL(url).host()) {
- // We have a matching popular sites suggestion. Replace it with the
- // actual URL and title.
- new_titles[i] = title;
- new_urls[i] = url;
- new_tile_sources[i] = tile_source;
- found = true;
+std::vector<size_t> MostVisitedSites::InsertMatchingSuggestions(
+ ScopedVector<Suggestion>* src_suggestions,
+ ScopedVector<Suggestion>* dst_suggestions,
+ const std::vector<std::string>& match_urls,
+ const std::vector<std::string>& match_hosts) {
+ std::vector<size_t> unmatched_suggestions;
+ size_t num_src_suggestions = src_suggestions->size();
+ size_t num_matchers = match_urls.size();
+ for (size_t i = 0; i < num_src_suggestions; ++i) {
+ size_t position;
+ for (position = 0; position < num_matchers; ++position) {
+ if ((*dst_suggestions)[position] != nullptr)
+ continue;
+ if (match_urls[position] == (*src_suggestions)[i]->url.spec())
+ break;
+ // match_hosts is only populated for suggestions which can be replaced by
+ // host matching like popular suggestions.
+ if (match_hosts[position] == (*src_suggestions)[i]->url.host())
break;
- }
}
- if (!found) {
- titles_to_insert.push_back(title);
- urls_to_insert.push_back(url);
- tile_sources_to_insert.push_back(tile_source);
+ if (position == num_matchers) {
+ unmatched_suggestions.push_back(i);
+ } else {
+ // A move is required as the source and destination containers own the
+ // elements.
+ std::swap((*dst_suggestions)[position], (*src_suggestions)[i]);
}
}
+ return unmatched_suggestions;
+}
- // Append personalized suggestions at the end if there's room.
- size_t num_to_append =
- std::min(static_cast<size_t>(num_sites) - new_titles.size(),
- titles_to_insert.size());
- new_titles.insert(new_titles.end(),
- titles_to_insert.end() - num_to_append,
- titles_to_insert.end());
- new_urls.insert(new_urls.end(),
- urls_to_insert.end() - num_to_append,
- urls_to_insert.end());
- new_tile_sources.insert(new_tile_sources.end(),
- tile_sources_to_insert.end() - num_to_append,
- tile_sources_to_insert.end());
-
- // Finally, go over the remaining personalized suggestions and evict popular
- // suggestions to accommodate them. Do it in reverse order, so the least
- // important popular suggestions will be evicted.
- for (size_t i = titles_to_insert.size() - num_to_append; i > 0; --i) {
- const base::string16& title = titles_to_insert[i - 1];
- const std::string& url = urls_to_insert[i - 1];
- const std::string& tile_source = tile_sources_to_insert[i - 1];
- for (size_t insert_i = new_titles.size(); insert_i > 0; --insert_i) {
- size_t insert_index = insert_i - 1;
- if (new_tile_sources[insert_index] == kHistogramPopularName) {
- new_titles[insert_index] = title;
- new_urls[insert_index] = url;
- new_tile_sources[insert_index] = tile_source;
- break;
- }
- }
+// static
+size_t MostVisitedSites::InsertAllSuggestions(
+ size_t start_position,
+ const std::vector<size_t>& insert_positions,
+ ScopedVector<Suggestion>* src_suggestions,
+ ScopedVector<Suggestion>* dst_suggestions) {
+ size_t num_inserts = insert_positions.size();
+ size_t num_dests = dst_suggestions->size();
+
+ size_t src_pos = 0;
+ size_t i = start_position;
+ for (; i < num_dests && src_pos < num_inserts; ++i) {
+ if ((*dst_suggestions)[i] != nullptr)
+ continue;
+ size_t src = insert_positions[src_pos++];
+ std::swap((*dst_suggestions)[i], (*src_suggestions)[src]);
}
-
- titles->swap(new_titles);
- urls->swap(new_urls);
- tile_sources->swap(new_tile_sources);
+ // Return destination postions filled so far which becomes the start_position
+ // for future runs.
+ return i;
}
-void MostVisitedSites::NotifyMostVisitedURLsObserver(
- const std::vector<base::string16>& titles,
- const std::vector<std::string>& urls) {
- DCHECK_EQ(titles.size(), urls.size());
+void MostVisitedSites::NotifyMostVisitedURLsObserver() {
+ size_t num_suggestions = current_suggestions_.size();
if (received_most_visited_sites_ && received_popular_sites_ &&
!recorded_uma_) {
RecordImpressionUMAMetrics();
- UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName, titles.size());
+ UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName, num_suggestions);
recorded_uma_ = true;
}
+ std::vector<base::string16> titles;
+ std::vector<std::string> urls;
+ titles.reserve(num_suggestions);
+ urls.reserve(num_suggestions);
+ for (const Suggestion* suggestion : current_suggestions_) {
+ titles.push_back(suggestion->title);
+ urls.push_back(suggestion->url.spec());
+ }
JNIEnv* env = AttachCurrentThread();
+ DCHECK_EQ(titles.size(), urls.size());
Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
env, observer_.obj(), ToJavaArrayOfStrings(env, titles).obj(),
ToJavaArrayOfStrings(env, urls).obj());
@@ -553,9 +667,10 @@ void MostVisitedSites::RecordThumbnailUMAMetrics() {
}
void MostVisitedSites::RecordImpressionUMAMetrics() {
- for (size_t i = 0; i < tile_sources_.size(); i++) {
- std::string histogram = base::StringPrintf(kImpressionHistogramFormat,
- tile_sources_[i].c_str());
+ for (size_t i = 0; i < current_suggestions_.size(); i++) {
+ std::string histogram = base::StringPrintf(
+ kImpressionHistogramFormat,
+ current_suggestions_[i]->GetSourceHistogramName().c_str());
LogHistogramEvent(histogram, static_cast<int>(i), num_sites_);
}
}
« no previous file with comments | « chrome/browser/android/most_visited_sites.h ('k') | chrome/browser/android/most_visited_sites_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698