| OLD | NEW |
| (Empty) |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/android/ntp/most_visited_sites.h" | |
| 6 | |
| 7 #include <utility> | |
| 8 | |
| 9 #include "base/callback.h" | |
| 10 #include "base/command_line.h" | |
| 11 #include "base/metrics/field_trial.h" | |
| 12 #include "base/metrics/histogram.h" | |
| 13 #include "base/metrics/sparse_histogram.h" | |
| 14 #include "base/strings/string_number_conversions.h" | |
| 15 #include "base/strings/string_util.h" | |
| 16 #include "base/strings/stringprintf.h" | |
| 17 #include "base/strings/utf_string_conversions.h" | |
| 18 #include "base/time/time.h" | |
| 19 #include "components/history/core/browser/top_sites.h" | |
| 20 #include "components/ntp_tiles/pref_names.h" | |
| 21 #include "components/ntp_tiles/switches.h" | |
| 22 #include "components/pref_registry/pref_registry_syncable.h" | |
| 23 #include "components/prefs/pref_service.h" | |
| 24 #include "components/variations/variations_associated_data.h" | |
| 25 #include "third_party/skia/include/core/SkBitmap.h" | |
| 26 #include "ui/gfx/codec/jpeg_codec.h" | |
| 27 #include "url/gurl.h" | |
| 28 | |
| 29 using history::TopSites; | |
| 30 using suggestions::ChromeSuggestion; | |
| 31 using suggestions::SuggestionsProfile; | |
| 32 using suggestions::SuggestionsService; | |
| 33 | |
| 34 namespace { | |
| 35 | |
| 36 // Identifiers for the various tile sources. | |
| 37 const char kHistogramClientName[] = "client"; | |
| 38 const char kHistogramServerName[] = "server"; | |
| 39 const char kHistogramServerFormat[] = "server%d"; | |
| 40 const char kHistogramPopularName[] = "popular"; | |
| 41 const char kHistogramWhitelistName[] = "whitelist"; | |
| 42 | |
| 43 const char kPopularSitesFieldTrialName[] = "NTPPopularSites"; | |
| 44 | |
| 45 // The visual type of a most visited tile. | |
| 46 // | |
| 47 // These values must stay in sync with the MostVisitedTileType enum | |
| 48 // in histograms.xml. | |
| 49 // | |
| 50 // A Java counterpart will be generated for this enum. | |
| 51 // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.ntp | |
| 52 enum MostVisitedTileType { | |
| 53 // The icon or thumbnail hasn't loaded yet. | |
| 54 NONE, | |
| 55 // The item displays a site's actual favicon or touch icon. | |
| 56 ICON_REAL, | |
| 57 // The item displays a color derived from the site's favicon or touch icon. | |
| 58 ICON_COLOR, | |
| 59 // The item displays a default gray box in place of an icon. | |
| 60 ICON_DEFAULT, | |
| 61 NUM_TILE_TYPES, | |
| 62 }; | |
| 63 | |
| 64 // May only be called from blocking thread pool. | |
| 65 std::unique_ptr<SkBitmap> MaybeFetchLocalThumbnail( | |
| 66 const GURL& url, | |
| 67 const scoped_refptr<TopSites>& top_sites) { | |
| 68 scoped_refptr<base::RefCountedMemory> image; | |
| 69 std::unique_ptr<SkBitmap> bitmap; | |
| 70 if (top_sites && top_sites->GetPageThumbnail(url, false, &image)) | |
| 71 bitmap = gfx::JPEGCodec::Decode(image->front(), image->size()); | |
| 72 return bitmap; | |
| 73 } | |
| 74 | |
| 75 // Log an event for a given |histogram| at a given element |position|. This | |
| 76 // routine exists because regular histogram macros are cached thus can't be used | |
| 77 // if the name of the histogram will change at a given call site. | |
| 78 void LogHistogramEvent(const std::string& histogram, | |
| 79 int position, | |
| 80 int num_sites) { | |
| 81 base::HistogramBase* counter = base::LinearHistogram::FactoryGet( | |
| 82 histogram, | |
| 83 1, | |
| 84 num_sites, | |
| 85 num_sites + 1, | |
| 86 base::Histogram::kUmaTargetedHistogramFlag); | |
| 87 if (counter) | |
| 88 counter->Add(position); | |
| 89 } | |
| 90 | |
| 91 bool ShouldShowPopularSites() { | |
| 92 // Note: It's important to query the field trial state first, to ensure that | |
| 93 // UMA reports the correct group. | |
| 94 const std::string group_name = | |
| 95 base::FieldTrialList::FindFullName(kPopularSitesFieldTrialName); | |
| 96 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); | |
| 97 if (cmd_line->HasSwitch(ntp_tiles::switches::kDisableNTPPopularSites)) | |
| 98 return false; | |
| 99 if (cmd_line->HasSwitch(ntp_tiles::switches::kEnableNTPPopularSites)) | |
| 100 return true; | |
| 101 return base::StartsWith(group_name, "Enabled", | |
| 102 base::CompareCase::INSENSITIVE_ASCII); | |
| 103 } | |
| 104 | |
| 105 std::string GetPopularSitesCountry() { | |
| 106 return variations::GetVariationParamValue(kPopularSitesFieldTrialName, | |
| 107 "country"); | |
| 108 } | |
| 109 | |
| 110 std::string GetPopularSitesVersion() { | |
| 111 return variations::GetVariationParamValue(kPopularSitesFieldTrialName, | |
| 112 "version"); | |
| 113 } | |
| 114 | |
| 115 // Determine whether we need any popular suggestions to fill up a grid of | |
| 116 // |num_tiles| tiles. | |
| 117 bool NeedPopularSites(const PrefService* prefs, size_t num_tiles) { | |
| 118 const base::ListValue* source_list = | |
| 119 prefs->GetList(ntp_tiles::prefs::kNTPSuggestionsIsPersonal); | |
| 120 // If there aren't enough previous suggestions to fill the grid, we need | |
| 121 // popular suggestions. | |
| 122 if (source_list->GetSize() < num_tiles) | |
| 123 return true; | |
| 124 // Otherwise, if any of the previous suggestions is not personal, then also | |
| 125 // get popular suggestions. | |
| 126 for (size_t i = 0; i < num_tiles; ++i) { | |
| 127 bool is_personal = false; | |
| 128 if (source_list->GetBoolean(i, &is_personal) && !is_personal) | |
| 129 return true; | |
| 130 } | |
| 131 // The whole grid is already filled with personal suggestions, no point in | |
| 132 // bothering with popular ones. | |
| 133 return false; | |
| 134 } | |
| 135 | |
| 136 bool AreURLsEquivalent(const GURL& url1, const GURL& url2) { | |
| 137 return url1.host() == url2.host() && url1.path() == url2.path(); | |
| 138 } | |
| 139 | |
| 140 std::string GetSourceHistogramName( | |
| 141 const MostVisitedSites::Suggestion& suggestion) { | |
| 142 switch (suggestion.source) { | |
| 143 case MostVisitedSites::TOP_SITES: | |
| 144 return kHistogramClientName; | |
| 145 case MostVisitedSites::POPULAR: | |
| 146 return kHistogramPopularName; | |
| 147 case MostVisitedSites::WHITELIST: | |
| 148 return kHistogramWhitelistName; | |
| 149 case MostVisitedSites::SUGGESTIONS_SERVICE: | |
| 150 return suggestion.provider_index >= 0 | |
| 151 ? base::StringPrintf(kHistogramServerFormat, | |
| 152 suggestion.provider_index) | |
| 153 : kHistogramServerName; | |
| 154 } | |
| 155 NOTREACHED(); | |
| 156 return std::string(); | |
| 157 } | |
| 158 | |
| 159 } // namespace | |
| 160 | |
| 161 MostVisitedSites::Suggestion::Suggestion() : provider_index(-1) {} | |
| 162 | |
| 163 MostVisitedSites::Suggestion::~Suggestion() {} | |
| 164 | |
| 165 MostVisitedSites::Suggestion::Suggestion(Suggestion&&) = default; | |
| 166 MostVisitedSites::Suggestion& | |
| 167 MostVisitedSites::Suggestion::operator=(Suggestion&&) = default; | |
| 168 | |
| 169 MostVisitedSites::MostVisitedSites( | |
| 170 scoped_refptr<base::SequencedWorkerPool> blocking_pool, | |
| 171 PrefService* prefs, | |
| 172 const TemplateURLService* template_url_service, | |
| 173 variations::VariationsService* variations_service, | |
| 174 net::URLRequestContextGetter* download_context, | |
| 175 const base::FilePath& popular_sites_directory, | |
| 176 scoped_refptr<history::TopSites> top_sites, | |
| 177 SuggestionsService* suggestions, | |
| 178 MostVisitedSitesSupervisor* supervisor) | |
| 179 : prefs_(prefs), | |
| 180 template_url_service_(template_url_service), | |
| 181 variations_service_(variations_service), | |
| 182 download_context_(download_context), | |
| 183 popular_sites_directory_(popular_sites_directory), | |
| 184 top_sites_(top_sites), | |
| 185 suggestions_service_(suggestions), | |
| 186 supervisor_(supervisor), | |
| 187 observer_(nullptr), | |
| 188 num_sites_(0), | |
| 189 received_most_visited_sites_(false), | |
| 190 received_popular_sites_(false), | |
| 191 recorded_uma_(false), | |
| 192 scoped_observer_(this), | |
| 193 mv_source_(SUGGESTIONS_SERVICE), | |
| 194 blocking_pool_(std::move(blocking_pool)), | |
| 195 blocking_runner_(blocking_pool_->GetTaskRunnerWithShutdownBehavior( | |
| 196 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN)), | |
| 197 weak_ptr_factory_(this) { | |
| 198 supervisor_->SetObserver(this); | |
| 199 } | |
| 200 | |
| 201 MostVisitedSites::~MostVisitedSites() { | |
| 202 supervisor_->SetObserver(nullptr); | |
| 203 } | |
| 204 | |
| 205 void MostVisitedSites::SetMostVisitedURLsObserver(Observer* observer, | |
| 206 int num_sites) { | |
| 207 DCHECK(observer); | |
| 208 observer_ = observer; | |
| 209 num_sites_ = num_sites; | |
| 210 | |
| 211 if (ShouldShowPopularSites() && | |
| 212 NeedPopularSites(prefs_, num_sites_)) { | |
| 213 popular_sites_.reset(new PopularSites( | |
| 214 blocking_pool_, prefs_, template_url_service_, variations_service_, | |
| 215 download_context_, popular_sites_directory_, GetPopularSitesCountry(), | |
| 216 GetPopularSitesVersion(), false, | |
| 217 base::Bind(&MostVisitedSites::OnPopularSitesAvailable, | |
| 218 base::Unretained(this)))); | |
| 219 } else { | |
| 220 received_popular_sites_ = true; | |
| 221 } | |
| 222 | |
| 223 // TODO(treib): Can |top_sites_| ever be null? If not, remove these checks. | |
| 224 if (top_sites_) { | |
| 225 // TopSites updates itself after a delay. To ensure up-to-date results, | |
| 226 // force an update now. | |
| 227 top_sites_->SyncWithHistory(); | |
| 228 | |
| 229 // Register as TopSitesObserver so that we can update ourselves when the | |
| 230 // TopSites changes. | |
| 231 scoped_observer_.Add(top_sites_.get()); | |
| 232 } | |
| 233 | |
| 234 suggestions_subscription_ = suggestions_service_->AddCallback( | |
| 235 base::Bind(&MostVisitedSites::OnSuggestionsProfileAvailable, | |
| 236 base::Unretained(this))); | |
| 237 | |
| 238 // Immediately build the current suggestions, getting personal suggestions | |
| 239 // from the SuggestionsService's cache or, if that is empty, from TopSites. | |
| 240 BuildCurrentSuggestions(); | |
| 241 // Also start a request for fresh suggestions. | |
| 242 suggestions_service_->FetchSuggestionsData(); | |
| 243 } | |
| 244 | |
| 245 void MostVisitedSites::GetURLThumbnail(const GURL& url, | |
| 246 const ThumbnailCallback& callback) { | |
| 247 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 248 | |
| 249 base::PostTaskAndReplyWithResult( | |
| 250 blocking_runner_.get(), FROM_HERE, | |
| 251 base::Bind(&MaybeFetchLocalThumbnail, url, top_sites_), | |
| 252 base::Bind(&MostVisitedSites::OnLocalThumbnailFetched, | |
| 253 weak_ptr_factory_.GetWeakPtr(), url, callback)); | |
| 254 } | |
| 255 | |
| 256 void MostVisitedSites::OnLocalThumbnailFetched( | |
| 257 const GURL& url, | |
| 258 const ThumbnailCallback& callback, | |
| 259 std::unique_ptr<SkBitmap> bitmap) { | |
| 260 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 261 if (bitmap.get()) { | |
| 262 callback.Run(true /* is_local_thumbnail */, bitmap.get()); | |
| 263 return; | |
| 264 } | |
| 265 | |
| 266 // A thumbnail is not locally available for |url|. Make sure it is put in | |
| 267 // the list to be fetched at the next visit to this site. | |
| 268 if (top_sites_) | |
| 269 top_sites_->AddForcedURL(url, base::Time::Now()); | |
| 270 // Also fetch a remote thumbnail if possible. PopularSites or the | |
| 271 // SuggestionsService can supply a thumbnail download URL. | |
| 272 if (popular_sites_) { | |
| 273 const std::vector<PopularSites::Site>& sites = popular_sites_->sites(); | |
| 274 auto it = std::find_if( | |
| 275 sites.begin(), sites.end(), | |
| 276 [&url](const PopularSites::Site& site) { return site.url == url; }); | |
| 277 if (it != sites.end() && it->thumbnail_url.is_valid()) { | |
| 278 return suggestions_service_->GetPageThumbnailWithURL( | |
| 279 url, it->thumbnail_url, | |
| 280 base::Bind(&MostVisitedSites::OnObtainedThumbnail, | |
| 281 weak_ptr_factory_.GetWeakPtr(), false, callback)); | |
| 282 } | |
| 283 } | |
| 284 if (mv_source_ == SUGGESTIONS_SERVICE) { | |
| 285 return suggestions_service_->GetPageThumbnail( | |
| 286 url, base::Bind(&MostVisitedSites::OnObtainedThumbnail, | |
| 287 weak_ptr_factory_.GetWeakPtr(), false, callback)); | |
| 288 } | |
| 289 // If no bitmap could be fetched and neither PopularSites nor the | |
| 290 // SuggestionsService is available then a nullptr is passed to the callback. | |
| 291 callback.Run(true /* is_local_thumbnail */, nullptr); | |
| 292 } | |
| 293 | |
| 294 void MostVisitedSites::OnObtainedThumbnail(bool is_local_thumbnail, | |
| 295 const ThumbnailCallback& callback, | |
| 296 const GURL& url, | |
| 297 const gfx::Image& image) { | |
| 298 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 299 const SkBitmap* bitmap = nullptr; | |
| 300 if (!image.IsEmpty()) | |
| 301 bitmap = image.ToSkBitmap(); | |
| 302 callback.Run(is_local_thumbnail, bitmap); | |
| 303 } | |
| 304 | |
| 305 void MostVisitedSites::AddOrRemoveBlacklistedUrl(const GURL& url, | |
| 306 bool add_url) { | |
| 307 // Always blacklist in the local TopSites. | |
| 308 if (top_sites_) { | |
| 309 if (add_url) | |
| 310 top_sites_->AddBlacklistedURL(url); | |
| 311 else | |
| 312 top_sites_->RemoveBlacklistedURL(url); | |
| 313 } | |
| 314 | |
| 315 // Only blacklist in the server-side suggestions service if it's active. | |
| 316 if (mv_source_ == SUGGESTIONS_SERVICE) { | |
| 317 if (add_url) | |
| 318 suggestions_service_->BlacklistURL(url); | |
| 319 else | |
| 320 suggestions_service_->UndoBlacklistURL(url); | |
| 321 } | |
| 322 } | |
| 323 | |
| 324 void MostVisitedSites::RecordTileTypeMetrics( | |
| 325 const std::vector<int>& tile_types) { | |
| 326 DCHECK_EQ(current_suggestions_.size(), tile_types.size()); | |
| 327 int counts_per_type[NUM_TILE_TYPES] = {0}; | |
| 328 for (size_t i = 0; i < tile_types.size(); ++i) { | |
| 329 int tile_type = tile_types[i]; | |
| 330 ++counts_per_type[tile_type]; | |
| 331 std::string histogram = base::StringPrintf( | |
| 332 "NewTabPage.TileType.%s", | |
| 333 GetSourceHistogramName(current_suggestions_[i]).c_str()); | |
| 334 LogHistogramEvent(histogram, tile_type, NUM_TILE_TYPES); | |
| 335 } | |
| 336 | |
| 337 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.IconsReal", | |
| 338 counts_per_type[ICON_REAL]); | |
| 339 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.IconsColor", | |
| 340 counts_per_type[ICON_COLOR]); | |
| 341 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.IconsGray", | |
| 342 counts_per_type[ICON_DEFAULT]); | |
| 343 } | |
| 344 | |
| 345 void MostVisitedSites::RecordOpenedMostVisitedItem(int index, int tile_type) { | |
| 346 DCHECK_GE(index, 0); | |
| 347 DCHECK_LT(index, static_cast<int>(current_suggestions_.size())); | |
| 348 std::string histogram = base::StringPrintf( | |
| 349 "NewTabPage.MostVisited.%s", | |
| 350 GetSourceHistogramName(current_suggestions_[index]).c_str()); | |
| 351 LogHistogramEvent(histogram, index, num_sites_); | |
| 352 | |
| 353 histogram = base::StringPrintf( | |
| 354 "NewTabPage.TileTypeClicked.%s", | |
| 355 GetSourceHistogramName(current_suggestions_[index]).c_str()); | |
| 356 LogHistogramEvent(histogram, tile_type, NUM_TILE_TYPES); | |
| 357 } | |
| 358 | |
| 359 void MostVisitedSites::OnBlockedSitesChanged() { | |
| 360 BuildCurrentSuggestions(); | |
| 361 } | |
| 362 | |
| 363 // static | |
| 364 void MostVisitedSites::RegisterProfilePrefs( | |
| 365 user_prefs::PrefRegistrySyncable* registry) { | |
| 366 // TODO(treib): Remove this, it's unused. Do we need migration code to clean | |
| 367 // up existing entries? | |
| 368 registry->RegisterListPref(ntp_tiles::prefs::kNTPSuggestionsURL); | |
| 369 // TODO(treib): Remove this. It's only used to determine if we need | |
| 370 // PopularSites at all. Find a way to do that without prefs, or failing that, | |
| 371 // replace this list pref by a simple bool. | |
| 372 registry->RegisterListPref(ntp_tiles::prefs::kNTPSuggestionsIsPersonal); | |
| 373 } | |
| 374 | |
| 375 void MostVisitedSites::BuildCurrentSuggestions() { | |
| 376 // Get the current suggestions from cache. If the cache is empty, this will | |
| 377 // fall back to TopSites. | |
| 378 OnSuggestionsProfileAvailable( | |
| 379 suggestions_service_->GetSuggestionsDataFromCache()); | |
| 380 } | |
| 381 | |
| 382 void MostVisitedSites::InitiateTopSitesQuery() { | |
| 383 if (!top_sites_) | |
| 384 return; | |
| 385 | |
| 386 top_sites_->GetMostVisitedURLs( | |
| 387 base::Bind(&MostVisitedSites::OnMostVisitedURLsAvailable, | |
| 388 weak_ptr_factory_.GetWeakPtr()), | |
| 389 false); | |
| 390 } | |
| 391 | |
| 392 base::FilePath MostVisitedSites::GetWhitelistLargeIconPath(const GURL& url) { | |
| 393 for (const auto& whitelist : supervisor_->whitelists()) { | |
| 394 if (AreURLsEquivalent(whitelist.entry_point, url)) | |
| 395 return whitelist.large_icon_path; | |
| 396 } | |
| 397 return base::FilePath(); | |
| 398 } | |
| 399 | |
| 400 void MostVisitedSites::OnMostVisitedURLsAvailable( | |
| 401 const history::MostVisitedURLList& visited_list) { | |
| 402 SuggestionsPtrVector suggestions; | |
| 403 size_t num_tiles = | |
| 404 std::min(visited_list.size(), static_cast<size_t>(num_sites_)); | |
| 405 for (size_t i = 0; i < num_tiles; ++i) { | |
| 406 const history::MostVisitedURL& visited = visited_list[i]; | |
| 407 if (visited.url.is_empty()) { | |
| 408 num_tiles = i; | |
| 409 break; // This is the signal that there are no more real visited sites. | |
| 410 } | |
| 411 if (supervisor_->IsBlocked(visited.url)) | |
| 412 continue; | |
| 413 | |
| 414 std::unique_ptr<Suggestion> suggestion(new Suggestion()); | |
| 415 suggestion->title = visited.title; | |
| 416 suggestion->url = visited.url; | |
| 417 suggestion->source = TOP_SITES; | |
| 418 suggestion->whitelist_icon_path = GetWhitelistLargeIconPath(visited.url); | |
| 419 | |
| 420 suggestions.push_back(std::move(suggestion)); | |
| 421 } | |
| 422 | |
| 423 received_most_visited_sites_ = true; | |
| 424 mv_source_ = TOP_SITES; | |
| 425 SaveNewSuggestions(&suggestions); | |
| 426 NotifyMostVisitedURLsObserver(); | |
| 427 } | |
| 428 | |
| 429 void MostVisitedSites::OnSuggestionsProfileAvailable( | |
| 430 const SuggestionsProfile& suggestions_profile) { | |
| 431 int num_tiles = suggestions_profile.suggestions_size(); | |
| 432 // With no server suggestions, fall back to local TopSites. | |
| 433 if (num_tiles == 0) { | |
| 434 InitiateTopSitesQuery(); | |
| 435 return; | |
| 436 } | |
| 437 if (num_sites_ < num_tiles) | |
| 438 num_tiles = num_sites_; | |
| 439 | |
| 440 SuggestionsPtrVector suggestions; | |
| 441 for (int i = 0; i < num_tiles; ++i) { | |
| 442 const ChromeSuggestion& suggestion = suggestions_profile.suggestions(i); | |
| 443 if (supervisor_->IsBlocked(GURL(suggestion.url()))) | |
| 444 continue; | |
| 445 | |
| 446 std::unique_ptr<Suggestion> generated_suggestion(new Suggestion()); | |
| 447 generated_suggestion->title = base::UTF8ToUTF16(suggestion.title()); | |
| 448 generated_suggestion->url = GURL(suggestion.url()); | |
| 449 generated_suggestion->source = SUGGESTIONS_SERVICE; | |
| 450 generated_suggestion->whitelist_icon_path = GetWhitelistLargeIconPath( | |
| 451 GURL(suggestion.url())); | |
| 452 if (suggestion.providers_size() > 0) | |
| 453 generated_suggestion->provider_index = suggestion.providers(0); | |
| 454 | |
| 455 suggestions.push_back(std::move(generated_suggestion)); | |
| 456 } | |
| 457 | |
| 458 received_most_visited_sites_ = true; | |
| 459 mv_source_ = SUGGESTIONS_SERVICE; | |
| 460 SaveNewSuggestions(&suggestions); | |
| 461 NotifyMostVisitedURLsObserver(); | |
| 462 } | |
| 463 | |
| 464 MostVisitedSites::SuggestionsPtrVector | |
| 465 MostVisitedSites::CreateWhitelistEntryPointSuggestions( | |
| 466 const SuggestionsPtrVector& personal_suggestions) { | |
| 467 size_t num_personal_suggestions = personal_suggestions.size(); | |
| 468 DCHECK_LE(num_personal_suggestions, static_cast<size_t>(num_sites_)); | |
| 469 | |
| 470 size_t num_whitelist_suggestions = num_sites_ - num_personal_suggestions; | |
| 471 SuggestionsPtrVector whitelist_suggestions; | |
| 472 | |
| 473 std::set<std::string> personal_hosts; | |
| 474 for (const auto& suggestion : personal_suggestions) | |
| 475 personal_hosts.insert(suggestion->url.host()); | |
| 476 | |
| 477 for (const auto& whitelist : supervisor_->whitelists()) { | |
| 478 // Skip blacklisted sites. | |
| 479 if (top_sites_ && top_sites_->IsBlacklisted(whitelist.entry_point)) | |
| 480 continue; | |
| 481 | |
| 482 // Skip suggestions already present. | |
| 483 if (personal_hosts.find(whitelist.entry_point.host()) != | |
| 484 personal_hosts.end()) | |
| 485 continue; | |
| 486 | |
| 487 // Skip whitelist entry points that are manually blocked. | |
| 488 if (supervisor_->IsBlocked(whitelist.entry_point)) | |
| 489 continue; | |
| 490 | |
| 491 std::unique_ptr<Suggestion> suggestion(new Suggestion()); | |
| 492 suggestion->title = whitelist.title; | |
| 493 suggestion->url = whitelist.entry_point; | |
| 494 suggestion->source = WHITELIST; | |
| 495 suggestion->whitelist_icon_path = whitelist.large_icon_path; | |
| 496 | |
| 497 whitelist_suggestions.push_back(std::move(suggestion)); | |
| 498 if (whitelist_suggestions.size() >= num_whitelist_suggestions) | |
| 499 break; | |
| 500 } | |
| 501 | |
| 502 return whitelist_suggestions; | |
| 503 } | |
| 504 | |
| 505 MostVisitedSites::SuggestionsPtrVector | |
| 506 MostVisitedSites::CreatePopularSitesSuggestions( | |
| 507 const SuggestionsPtrVector& personal_suggestions, | |
| 508 const SuggestionsPtrVector& whitelist_suggestions) { | |
| 509 // For child accounts popular sites suggestions will not be added. | |
| 510 if (supervisor_->IsChildProfile()) | |
| 511 return SuggestionsPtrVector(); | |
| 512 | |
| 513 size_t num_suggestions = | |
| 514 personal_suggestions.size() + whitelist_suggestions.size(); | |
| 515 DCHECK_LE(num_suggestions, static_cast<size_t>(num_sites_)); | |
| 516 | |
| 517 // Collect non-blacklisted popular suggestions, skipping those already present | |
| 518 // in the personal suggestions. | |
| 519 size_t num_popular_sites_suggestions = num_sites_ - num_suggestions; | |
| 520 SuggestionsPtrVector popular_sites_suggestions; | |
| 521 | |
| 522 if (num_popular_sites_suggestions > 0 && popular_sites_) { | |
| 523 std::set<std::string> hosts; | |
| 524 for (const auto& suggestion : personal_suggestions) | |
| 525 hosts.insert(suggestion->url.host()); | |
| 526 for (const auto& suggestion : whitelist_suggestions) | |
| 527 hosts.insert(suggestion->url.host()); | |
| 528 for (const PopularSites::Site& popular_site : popular_sites_->sites()) { | |
| 529 // Skip blacklisted sites. | |
| 530 if (top_sites_ && top_sites_->IsBlacklisted(popular_site.url)) | |
| 531 continue; | |
| 532 std::string host = popular_site.url.host(); | |
| 533 // Skip suggestions already present in personal or whitelists. | |
| 534 if (hosts.find(host) != hosts.end()) | |
| 535 continue; | |
| 536 | |
| 537 std::unique_ptr<Suggestion> suggestion(new Suggestion()); | |
| 538 suggestion->title = popular_site.title; | |
| 539 suggestion->url = GURL(popular_site.url); | |
| 540 suggestion->source = POPULAR; | |
| 541 | |
| 542 popular_sites_suggestions.push_back(std::move(suggestion)); | |
| 543 if (popular_sites_suggestions.size() >= num_popular_sites_suggestions) | |
| 544 break; | |
| 545 } | |
| 546 } | |
| 547 return popular_sites_suggestions; | |
| 548 } | |
| 549 | |
| 550 void MostVisitedSites::SaveNewSuggestions( | |
| 551 SuggestionsPtrVector* personal_suggestions) { | |
| 552 SuggestionsPtrVector whitelist_suggestions = | |
| 553 CreateWhitelistEntryPointSuggestions(*personal_suggestions); | |
| 554 SuggestionsPtrVector popular_sites_suggestions = | |
| 555 CreatePopularSitesSuggestions(*personal_suggestions, | |
| 556 whitelist_suggestions); | |
| 557 | |
| 558 size_t num_actual_tiles = personal_suggestions->size() + | |
| 559 whitelist_suggestions.size() + | |
| 560 popular_sites_suggestions.size(); | |
| 561 DCHECK_LE(num_actual_tiles, static_cast<size_t>(num_sites_)); | |
| 562 | |
| 563 SuggestionsPtrVector merged_suggestions = MergeSuggestions( | |
| 564 personal_suggestions, &whitelist_suggestions, &popular_sites_suggestions); | |
| 565 DCHECK_EQ(num_actual_tiles, merged_suggestions.size()); | |
| 566 | |
| 567 current_suggestions_.resize(merged_suggestions.size()); | |
| 568 for (size_t i = 0; i < merged_suggestions.size(); ++i) | |
| 569 std::swap(*merged_suggestions[i], current_suggestions_[i]); | |
| 570 | |
| 571 if (received_popular_sites_) | |
| 572 SaveCurrentSuggestionsToPrefs(); | |
| 573 } | |
| 574 | |
| 575 // static | |
| 576 MostVisitedSites::SuggestionsPtrVector MostVisitedSites::MergeSuggestions( | |
| 577 SuggestionsPtrVector* personal_suggestions, | |
| 578 SuggestionsPtrVector* whitelist_suggestions, | |
| 579 SuggestionsPtrVector* popular_suggestions) { | |
| 580 size_t num_personal_suggestions = personal_suggestions->size(); | |
| 581 size_t num_whitelist_suggestions = whitelist_suggestions->size(); | |
| 582 size_t num_popular_suggestions = popular_suggestions->size(); | |
| 583 size_t num_tiles = num_popular_suggestions + num_whitelist_suggestions + | |
| 584 num_personal_suggestions; | |
| 585 SuggestionsPtrVector merged_suggestions; | |
| 586 AppendSuggestions(personal_suggestions, &merged_suggestions); | |
| 587 AppendSuggestions(whitelist_suggestions, &merged_suggestions); | |
| 588 AppendSuggestions(popular_suggestions, &merged_suggestions); | |
| 589 DCHECK_EQ(num_tiles, merged_suggestions.size()); | |
| 590 | |
| 591 return merged_suggestions; | |
| 592 } | |
| 593 | |
| 594 // TODO(treib): Once we use SuggestionsVector (non-Ptr) everywhere, move this | |
| 595 // into an anonymous namespace. | |
| 596 // static | |
| 597 void MostVisitedSites::AppendSuggestions(SuggestionsPtrVector* src, | |
| 598 SuggestionsPtrVector* dst) { | |
| 599 dst->insert(dst->end(), | |
| 600 std::make_move_iterator(src->begin()), | |
| 601 std::make_move_iterator(src->end())); | |
| 602 } | |
| 603 | |
| 604 void MostVisitedSites::SaveCurrentSuggestionsToPrefs() { | |
| 605 base::ListValue url_list; | |
| 606 base::ListValue source_list; | |
| 607 for (const auto& suggestion : current_suggestions_) { | |
| 608 url_list.AppendString(suggestion.url.spec()); | |
| 609 source_list.AppendBoolean(suggestion.source != POPULAR); | |
| 610 } | |
| 611 prefs_->Set(ntp_tiles::prefs::kNTPSuggestionsIsPersonal, source_list); | |
| 612 prefs_->Set(ntp_tiles::prefs::kNTPSuggestionsURL, url_list); | |
| 613 } | |
| 614 | |
| 615 void MostVisitedSites::NotifyMostVisitedURLsObserver() { | |
| 616 if (received_most_visited_sites_ && received_popular_sites_ && | |
| 617 !recorded_uma_) { | |
| 618 RecordImpressionUMAMetrics(); | |
| 619 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.NumberOfTiles", | |
| 620 current_suggestions_.size()); | |
| 621 recorded_uma_ = true; | |
| 622 } | |
| 623 | |
| 624 if (!observer_) | |
| 625 return; | |
| 626 | |
| 627 observer_->OnMostVisitedURLsAvailable(current_suggestions_); | |
| 628 } | |
| 629 | |
| 630 void MostVisitedSites::OnPopularSitesAvailable(bool success) { | |
| 631 received_popular_sites_ = true; | |
| 632 | |
| 633 if (!success) { | |
| 634 LOG(WARNING) << "Download of popular sites failed"; | |
| 635 return; | |
| 636 } | |
| 637 | |
| 638 // Pass the popular sites to the observer. This will cause it to fetch any | |
| 639 // missing icons, but will *not* cause it to display the popular sites. | |
| 640 observer_->OnPopularURLsAvailable(popular_sites_->sites()); | |
| 641 | |
| 642 // Re-build the suggestions list. Once done, this will notify the observer. | |
| 643 BuildCurrentSuggestions(); | |
| 644 } | |
| 645 | |
| 646 void MostVisitedSites::RecordImpressionUMAMetrics() { | |
| 647 for (size_t i = 0; i < current_suggestions_.size(); i++) { | |
| 648 std::string histogram = base::StringPrintf( | |
| 649 "NewTabPage.SuggestionsImpression.%s", | |
| 650 GetSourceHistogramName(current_suggestions_[i]).c_str()); | |
| 651 LogHistogramEvent(histogram, static_cast<int>(i), num_sites_); | |
| 652 } | |
| 653 } | |
| 654 | |
| 655 void MostVisitedSites::TopSitesLoaded(TopSites* top_sites) {} | |
| 656 | |
| 657 void MostVisitedSites::TopSitesChanged(TopSites* top_sites, | |
| 658 ChangeReason change_reason) { | |
| 659 if (mv_source_ == TOP_SITES) { | |
| 660 // The displayed suggestions are invalidated. | |
| 661 InitiateTopSitesQuery(); | |
| 662 } | |
| 663 } | |
| OLD | NEW |