1 // Copyright (c) 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/history/top_sites_likely_impl.h" 6 7 #include <algorithm> 8 #include <set> 9 10 #include "base/bind.h" 11 #include "base/bind_helpers.h" 12 #include "base/logging.h" 13 #include "base/md5.h" 14 #include "base/memory/ref_counted_memory.h" 15 #include "base/message_loop/message_loop_proxy.h" 16 #include "base/prefs/pref_service.h" 17 #include "base/strings/string_util.h" 18 #include "base/strings/utf_string_conversions.h" 19 #include "base/task_runner.h" 20 #include "base/values.h" 21 #include "chrome/browser/chrome_notification_types.h" 22 #include "chrome/browser/history/history_backend.h" 23 #include "chrome/browser/history/history_db_task.h" 24 #include "chrome/browser/history/history_notifications.h" 25 #include "chrome/browser/history/history_service_factory.h" 26 #include "chrome/browser/history/page_usage_data.h" 27 #include "chrome/browser/history/top_sites_cache.h" 28 #include "chrome/browser/prefs/scoped_user_pref_update.h" 29 #include "chrome/browser/profiles/profile.h" 30 #include "chrome/browser/ui/webui/ntp/most_visited_handler.h" 31 #include "chrome/browser/ui/webui/ntp/new_tab_ui.h" 32 #include "chrome/common/pref_names.h" 33 #include "chrome/common/thumbnail_score.h" 34 #include "content/public/browser/browser_thread.h" 35 #include "content/public/browser/navigation_controller.h" 36 #include "content/public/browser/navigation_details.h" 37 #include "content/public/browser/navigation_entry.h" 38 #include "content/public/browser/notification_service.h" 39 #include "content/public/browser/web_contents.h" 40 #include "grit/locale_settings.h" 41 #include "ui/base/l10n/l10n_util.h" 42 #include "ui/base/layout.h" 43 #include "ui/base/resource/resource_bundle.h" 44 #include "ui/gfx/image/image_util.h" 45 46 using base::DictionaryValue; 47 using content::BrowserThread; 48 using content::NavigationController; 49 50 namespace history { 51 52 namespace { 53 54 void RunOrPostGetMostVisitedURLsCallback( 55 base::TaskRunner* task_runner, 56 const TopSitesLikelyImpl::GetMostVisitedURLsCallback& callback, 57 const MostVisitedURLList& urls) { 58 if (task_runner->RunsTasksOnCurrentThread()) 59 callback.Run(urls); 60 else 61 task_runner->PostTask(FROM_HERE, base::Bind(callback, urls)); 62 } 63 64 } // namespace 65 66 // How many top sites to store in the cache. 67 static const size_t kTopSitesNumber = 20; 68 69 // Max number of temporary images we'll cache. See comment above 70 // temp_images_ for details. 71 static const size_t kMaxTempTopImages = 8; 72 73 static const int kDaysOfHistory = 90; 74 // Time from startup to first HistoryService query. 75 static const int64 kUpdateIntervalSecs = 15; 76 // Intervals between requests to HistoryService. 77 static const int64 kMinUpdateIntervalMinutes = 1; 78 static const int64 kMaxUpdateIntervalMinutes = 60; 79 80 // Use 100 quality (highest quality) because we're very sensitive to 81 // artifacts for these small sized, highly detailed images. 82 static const int kTopSitesImageQuality = 100; 83 84 namespace { 85 86 // HistoryDBTask used during migration of thumbnails from history to top sites. 87 // When run on the history thread it collects the top sites and the 88 // corresponding thumbnails. When run back on the ui thread it calls into 89 // TopSitesLikelyImpl::FinishHistoryMigration. 90 class LoadThumbnailsFromHistoryTask : public HistoryDBTask { 91 public: 92 LoadThumbnailsFromHistoryTask(TopSites* top_sites, 93 int result_count) 94 : top_sites_(top_sites), 95 result_count_(result_count) { 96 // l10n_util isn't thread safe, so cache for use on the db thread. 97 ignore_urls_.insert(l10n_util::GetStringUTF8(IDS_CHROME_WELCOME_URL)); 98 ignore_urls_.insert(l10n_util::GetStringUTF8(IDS_WEBSTORE_URL)); 99 #if defined(OS_ANDROID) 100 ignore_urls_.insert(l10n_util::GetStringUTF8(IDS_MOBILE_WELCOME_URL)); 101 #endif 102 } 103 104 virtual bool RunOnDBThread(history::HistoryBackend* backend, 105 history::HistoryDatabase* db) OVERRIDE { 106 // Get the most visited urls. 107 backend->QueryMostVisitedURLsImpl(result_count_, 108 kDaysOfHistory, 109 &data_.most_visited); 110 111 // And fetch the thumbnails. 112 for (size_t i = 0; i < data_.most_visited.size(); ++i) { 113 const GURL& url = data_.most_visited[i].url; 114 if (ShouldFetchThumbnailFor(url)) { 115 scoped_refptr<base::RefCountedBytes> data; 116 backend->GetPageThumbnailDirectly(url, &data); 117 data_.url_to_thumbnail_map[url] = data; 118 } 119 } 120 return true; 121 } 122 123 virtual void DoneRunOnMainThread() OVERRIDE { 124 top_sites_->FinishHistoryMigration(data_); 125 } 126 127 private: 128 virtual ~LoadThumbnailsFromHistoryTask() {} 129 130 bool ShouldFetchThumbnailFor(const GURL& url) { 131 return ignore_urls_.find(url.spec()) == ignore_urls_.end(); 132 } 133 134 // Set of URLs we don't load thumbnails for. This is created on the UI thread 135 // and used on the history thread. 136 std::set<std::string> ignore_urls_; 137 138 scoped_refptr<TopSites> top_sites_; 139 140 // Number of results to request from history. 141 const int result_count_; 142 143 ThumbnailMigration data_; 144 145 DISALLOW_COPY_AND_ASSIGN(LoadThumbnailsFromHistoryTask); 146 }; 147 148 } // namespace 149 150 TopSitesLikelyImpl::TopSitesLikelyImpl(Profile* profile) 151 : backend_(NULL), 152 cache_(new TopSitesCache()), 153 thread_safe_cache_(new TopSitesCache()), 154 profile_(profile), 155 last_num_urls_changed_(0), 156 history_state_(HISTORY_LOADING), 157 top_sites_state_(TOP_SITES_LOADING), 158 loaded_(false) { 159 if (!profile_) 160 return; 161 162 if (content::NotificationService::current()) { 163 registrar_.Add(this, chrome::NOTIFICATION_HISTORY_URLS_DELETED, 164 content::Source<Profile>(profile_)); 165 // Listen for any nav commits. We'll ignore those not related to this 166 // profile when we get the notification. 167 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, 168 content::NotificationService::AllSources()); 169 } 170 for (size_t i = 0; i < arraysize(kPrepopulatedPages); i++) { 171 int url_id = kPrepopulatedPages[i].url_id; 172 prepopulated_page_urls_.push_back( 173 GURL(l10n_util::GetStringUTF8(url_id))); 174 } 175 } 176 177 void TopSitesLikelyImpl::Init(const base::FilePath& db_name) { 178 // Create the backend here, rather than in the constructor, so that 179 // unit tests that do not need the backend can run without a problem. 180 backend_ = new TopSitesBackend; 181 backend_->Init(db_name); 182 backend_->GetMostVisitedThumbnails( 183 base::Bind(&TopSitesLikelyImpl::OnGotMostVisitedThumbnails, 184 base::Unretained(this)), 185 &cancelable_task_tracker_); 186 187 // History may have already finished loading by the time we're created. 188 HistoryService* history = 189 HistoryServiceFactory::GetForProfileWithoutCreating(profile_); 190 if (history && history->backend_loaded()) { 191 if (history->needs_top_sites_migration()) 192 MigrateFromHistory(); 193 else 194 history_state_ = HISTORY_LOADED; 195 } 196 } 197 198 bool TopSitesLikelyImpl::SetPageThumbnail(const GURL& url, 199 const gfx::Image& thumbnail, 200 const ThumbnailScore& score) { 201 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 202 203 if (!loaded_) { 204 // TODO(sky): I need to cache these and apply them after the load 205 // completes. 206 return false; 207 } 208 209 bool add_temp_thumbnail = false; 210 if (!IsKnownURL(url)) { 211 if (!IsFull()) { 212 add_temp_thumbnail = true; 213 } else { 214 return false; // This URL is not known to us. 215 } 216 } 217 218 if (!HistoryService::CanAddURL(url)) 219 return false; // It's not a real webpage. 220 221 scoped_refptr<base::RefCountedBytes> thumbnail_data; 222 if (!EncodeBitmap(thumbnail, &thumbnail_data)) 223 return false; 224 225 if (add_temp_thumbnail) { 226 // Always remove the existing entry and then add it back. That way if we end 227 // up with too many temp thumbnails we'll prune the oldest first. 228 RemoveTemporaryThumbnailByURL(url); 229 AddTemporaryThumbnail(url, thumbnail_data.get(), score); 230 return true; 231 } 232 233 return SetPageThumbnailEncoded(url, thumbnail_data.get(), score); 234 } 235 236 bool TopSitesLikelyImpl::SetPageThumbnailToJPEGBytes( 237 const GURL& url, 238 const base::RefCountedMemory* memory, 239 const ThumbnailScore& score) { 240 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 241 242 if (!loaded_) { 243 // TODO(sky): I need to cache these and apply them after the load 244 // completes. 245 return false; 246 } 247 248 bool add_temp_thumbnail = false; 249 if (!IsKnownURL(url)) { 250 if (!IsFull()) { 251 add_temp_thumbnail = true; 252 } else { 253 return false; // This URL is not known to us. 254 } 255 } 256 257 if (!HistoryService::CanAddURL(url)) 258 return false; // It's not a real webpage. 259 260 if (add_temp_thumbnail) { 261 // Always remove the existing entry and then add it back. That way if we end 262 // up with too many temp thumbnails we'll prune the oldest first. 263 RemoveTemporaryThumbnailByURL(url); 264 AddTemporaryThumbnail(url, memory, score); 265 return true; 266 } 267 268 return SetPageThumbnailEncoded(url, memory, score); 269 } 270 271 // WARNING: this function may be invoked on any thread. 272 void TopSitesLikelyImpl::GetMostVisitedURLs( 273 const GetMostVisitedURLsCallback& callback) { 274 MostVisitedURLList filtered_urls; 275 { 276 base::AutoLock lock(lock_); 277 if (!loaded_) { 278 // A request came in before we finished loading. Store the callback and 279 // we'll run it on current thread when we finish loading. 280 pending_callbacks_.push_back( 281 base::Bind(&RunOrPostGetMostVisitedURLsCallback, 282 base::MessageLoopProxy::current(), 283 callback)); 284 return; 285 } 286 filtered_urls = thread_safe_cache_->top_sites(); 287 } 288 callback.Run(filtered_urls); 289 } 290 291 bool TopSitesLikelyImpl::GetPageThumbnail( 292 const GURL& url, scoped_refptr<base::RefCountedMemory>* bytes) { 293 // WARNING: this may be invoked on any thread. 294 { 295 base::AutoLock lock(lock_); 296 if (thread_safe_cache_->GetPageThumbnail(url, bytes)) 297 return true; 298 } 299 300 // Resource bundle is thread safe. 301 for (size_t i = 0; i < arraysize(kPrepopulatedPages); i++) { 302 if (url == prepopulated_page_urls_[i]) { 303 *bytes = ResourceBundle::GetSharedInstance(). 304 LoadDataResourceBytesForScale( 305 kPrepopulatedPages[i].thumbnail_id, 306 ui::SCALE_FACTOR_100P); 307 return true; 308 } 309 } 310 311 return false; 312 } 313 314 bool TopSitesLikelyImpl::GetPageThumbnailScore(const GURL& url, 315 ThumbnailScore* score) { 316 // WARNING: this may be invoked on any thread. 317 base::AutoLock lock(lock_); 318 return thread_safe_cache_->GetPageThumbnailScore(url, score); 319 } 320 321 bool TopSitesLikelyImpl::GetTemporaryPageThumbnailScore(const GURL& url, 322 ThumbnailScore* score) { 323 for (TempImages::iterator i = temp_images_.begin(); i != temp_images_.end(); 324 ++i) { 325 if (i->first == url) { 326 *score = i->second.thumbnail_score; 327 return true; 328 } 329 } 330 return false; 331 } 332 333 334 // Returns the index of |url| in |urls|, or -1 if not found. 335 static int IndexOf(const MostVisitedURLList& urls, const GURL& url) { 336 for (size_t i = 0; i < urls.size(); i++) { 337 if (urls[i].url == url) 338 return i; 339 } 340 return -1; 341 } 342 343 void TopSitesLikelyImpl::MigrateFromHistory() { 344 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 345 346 if (history_state_ != HISTORY_LOADING) { 347 // This can happen if history was unloaded then loaded again. 348 return; 349 } 350 351 history_state_ = HISTORY_MIGRATING; 352 HistoryServiceFactory::GetForProfile( 353 profile_, Profile::EXPLICIT_ACCESS)->ScheduleDBTask( 354 new LoadThumbnailsFromHistoryTask( 355 this, 356 num_results_to_request_from_history()), 357 &history_consumer_); 358 } 359 360 void TopSitesLikelyImpl::FinishHistoryMigration( 361 const ThumbnailMigration& data) { 362 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 363 DCHECK_EQ(history_state_, HISTORY_MIGRATING); 364 365 history_state_ = HISTORY_LOADED; 366 367 SetTopSites(data.most_visited); 368 369 for (size_t i = 0; i < data.most_visited.size(); ++i) { 370 URLToThumbnailMap::const_iterator image_i = 371 data.url_to_thumbnail_map.find(data.most_visited[i].url); 372 if (image_i != data.url_to_thumbnail_map.end()) { 373 SetPageThumbnailEncoded( 374 data.most_visited[i].url, image_i->second.get(), ThumbnailScore()); 375 } 376 } 377 378 MoveStateToLoaded(); 379 380 ResetThreadSafeImageCache(); 381 382 // We've scheduled all the thumbnails and top sites to be written to the top 383 // sites db, but it hasn't happened yet. Schedule a request on the db thread 384 // that notifies us when done. When done we'll know everything was written and 385 // we can tell history to finish its part of migration. 386 backend_->DoEmptyRequest( 387 base::Bind(&TopSitesLikelyImpl::OnHistoryMigrationWrittenToDisk, 388 base::Unretained(this)), 389 &cancelable_task_tracker_); 390 } 391 392 void TopSitesLikelyImpl::HistoryLoaded() { 393 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 394 395 if (history_state_ != HISTORY_MIGRATING) { 396 // No migration from history is needed. 397 history_state_ = HISTORY_LOADED; 398 if (top_sites_state_ == TOP_SITES_LOADED_WAITING_FOR_HISTORY) { 399 // TopSites thought it needed migration, but it really didn't. This 400 // typically happens the first time a profile is run with Top Sites 401 // enabled 402 SetTopSites(MostVisitedURLList()); 403 MoveStateToLoaded(); 404 } 405 } 406 // else case can happen if history is unloaded, then loaded again. 407 } 408 409 void TopSitesLikelyImpl::SyncWithHistory() { 410 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 411 if (loaded_ && temp_images_.size()) { 412 // If we have temporary thumbnails it means there isn't much data, and most 413 // likely the user is first running Chrome. During this time we throttle 414 // updating from history by 30 seconds. If the user creates a new tab page 415 // during this window of time we force updating from history so that the new 416 // tab page isn't so far out of date. 417 timer_.Stop(); 418 StartQueryForMostVisited(); 419 } 420 } 421 422 bool TopSitesLikelyImpl::HasBlacklistedItems() const { 423 const DictionaryValue* blacklist = 424 profile_->GetPrefs()->GetDictionary(prefs::kNtpMostVisitedURLsBlacklist); 425 return blacklist && !blacklist->empty(); 426 } 427 428 void TopSitesLikelyImpl::AddBlacklistedURL(const GURL& url) { 429 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 430 431 Value* dummy = Value::CreateNullValue(); 432 { 433 DictionaryPrefUpdate update(profile_->GetPrefs(), 434 prefs::kNtpMostVisitedURLsBlacklist); 435 DictionaryValue* blacklist = update.Get(); 436 blacklist->SetWithoutPathExpansion(GetURLHash(url), dummy); 437 } 438 439 ResetThreadSafeCache(); 440 NotifyTopSitesChanged(); 441 } 442 443 void TopSitesLikelyImpl::RemoveBlacklistedURL(const GURL& url) { 444 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 445 { 446 DictionaryPrefUpdate update(profile_->GetPrefs(), 447 prefs::kNtpMostVisitedURLsBlacklist); 448 DictionaryValue* blacklist = update.Get(); 449 blacklist->RemoveWithoutPathExpansion(GetURLHash(url), NULL); 450 } 451 ResetThreadSafeCache(); 452 NotifyTopSitesChanged(); 453 } 454 455 bool TopSitesLikelyImpl::IsBlacklisted(const GURL& url) { 456 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 457 const DictionaryValue* blacklist = 458 profile_->GetPrefs()->GetDictionary(prefs::kNtpMostVisitedURLsBlacklist); 459 return blacklist && blacklist->HasKey(GetURLHash(url)); 460 } 461 462 void TopSitesLikelyImpl::ClearBlacklistedURLs() { 463 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 464 { 465 DictionaryPrefUpdate update(profile_->GetPrefs(), 466 prefs::kNtpMostVisitedURLsBlacklist); 467 DictionaryValue* blacklist = update.Get(); 468 blacklist->Clear(); 469 } 470 ResetThreadSafeCache(); 471 NotifyTopSitesChanged(); 472 } 473 474 void TopSitesLikelyImpl::Shutdown() { 475 profile_ = NULL; 476 // Cancel all requests so that the service doesn't callback to us after we've 477 // invoked Shutdown (this could happen if we have a pending request and 478 // Shutdown is invoked). 479 history_consumer_.CancelAllRequests(); 480 backend_->Shutdown(); 481 } 482 483 // static 484 void TopSitesLikelyImpl::DiffMostVisited(const MostVisitedURLList& old_list, 485 const MostVisitedURLList& new_list, 486 TopSitesDelta* delta) { 487 // Add all the old URLs for quick lookup. This maps URLs to the corresponding 488 // index in the input. 489 std::map<GURL, size_t> all_old_urls; 490 for (size_t i = 0; i < old_list.size(); i++) 491 all_old_urls[old_list[i].url] = i; 492 493 // Check all the URLs in the new set to see which ones are new or just moved. 494 // When we find a match in the old set, we'll reset its index to our special 495 // marker. This allows us to quickly identify the deleted ones in a later 496 // pass. 497 const size_t kAlreadyFoundMarker = static_cast<size_t>(-1); 498 for (size_t i = 0; i < new_list.size(); i++) { 499 std::map<GURL, size_t>::iterator found = all_old_urls.find(new_list[i].url); 500 if (found == all_old_urls.end()) { 501 MostVisitedURLWithRank added; 502 added.url = new_list[i]; 503 added.rank = i; 504 delta->added.push_back(added); 505 } else { 506 if (found->second != i) { 507 MostVisitedURLWithRank moved; 508 moved.url = new_list[i]; 509 moved.rank = i; 510 delta->moved.push_back(moved); 511 } 512 found->second = kAlreadyFoundMarker; 513 } 514 } 515 516 // Any member without the special marker in the all_old_urls list means that 517 // there wasn't a "new" URL that mapped to it, so it was deleted. 518 for (std::map<GURL, size_t>::const_iterator i = all_old_urls.begin(); 519 i != all_old_urls.end(); ++i) { 520 if (i->second != kAlreadyFoundMarker) 521 delta->deleted.push_back(old_list[i->second]); 522 } 523 } 524 525 CancelableRequestProvider::Handle 526 TopSitesLikelyImpl::StartQueryForMostVisited() { 527 DCHECK(loaded_); 528 if (!profile_) 529 return 0; 530 531 HistoryService* hs = HistoryServiceFactory::GetForProfile( 532 profile_, Profile::EXPLICIT_ACCESS); 533 // |hs| may be null during unit tests. 534 if (hs) { 535 return hs->QueryMostVisitedURLs( 536 num_results_to_request_from_history(), 537 kDaysOfHistory, 538 &history_consumer_, 539 base::Bind(&TopSitesLikelyImpl::OnTopSitesAvailableFromHistory, 540 base::Unretained(this))); 541 } 542 return 0; 543 } 544 545 bool TopSitesLikelyImpl::IsKnownURL(const GURL& url) { 546 return loaded_ && cache_->IsKnownURL(url); 547 } 548 549 const std::string& TopSitesLikelyImpl::GetCanonicalURLString( 550 const GURL& url) const { 551 return cache_->GetCanonicalURL(url).spec(); 552 } 553 554 bool TopSitesLikelyImpl::IsFull() { 555 return loaded_ && cache_->top_sites().size() >= kTopSitesNumber; 556 } 557 558 TopSitesLikelyImpl::~TopSitesLikelyImpl() { 559 } 560 561 bool TopSitesLikelyImpl::SetPageThumbnailNoDB( 562 const GURL& url, 563 const base::RefCountedMemory* thumbnail_data, 564 const ThumbnailScore& score) { 565 // This should only be invoked when we know about the url. 566 DCHECK(cache_->IsKnownURL(url)); 567 568 const MostVisitedURL& most_visited = 569 cache_->top_sites()[cache_->GetURLIndex(url)]; 570 Images* image = cache_->GetImage(url); 571 572 // When comparing the thumbnail scores, we need to take into account the 573 // redirect hops, which are not generated when the thumbnail is because the 574 // redirects weren't known. We fill that in here since we know the redirects. 575 ThumbnailScore new_score_with_redirects(score); 576 new_score_with_redirects.redirect_hops_from_dest = 577 GetRedirectDistanceForURL(most_visited, url); 578 579 if (!ShouldReplaceThumbnailWith(image->thumbnail_score, 580 new_score_with_redirects) && 581 image->thumbnail.get()) 582 return false; // The one we already have is better. 583 584 image->thumbnail = const_cast<base::RefCountedMemory*>(thumbnail_data); 585 image->thumbnail_score = new_score_with_redirects; 586 587 ResetThreadSafeImageCache(); 588 return true; 589 } 590 591 bool TopSitesLikelyImpl::SetPageThumbnailEncoded( 592 const GURL& url, 593 const base::RefCountedMemory* thumbnail, 594 const ThumbnailScore& score) { 595 if (!SetPageThumbnailNoDB(url, thumbnail, score)) 596 return false; 597 598 // Update the database. 599 if (!cache_->IsKnownURL(url)) 600 return false; 601 602 size_t index = cache_->GetURLIndex(url); 603 const MostVisitedURL& most_visited = cache_->top_sites()[index]; 604 backend_->SetPageThumbnail(most_visited, 605 index, 606 *(cache_->GetImage(most_visited.url))); 607 return true; 608 } 609 610 // static 611 bool TopSitesLikelyImpl::EncodeBitmap(const gfx::Image& bitmap, 612 scoped_refptr<base::RefCountedBytes>* bytes) { 613 if (bitmap.IsEmpty()) 614 return false; 615 *bytes = new base::RefCountedBytes(); 616 std::vector<unsigned char> data; 617 if (!gfx::JPEG1xEncodedDataFromImage(bitmap, kTopSitesImageQuality, &data)) 618 return false; 619 620 // As we're going to cache this data, make sure the vector is only as big as 621 // it needs to be, as JPEGCodec::Encode() over-allocates data.capacity(). 622 // (In a C++0x future, we can just call shrink_to_fit() in Encode()) 623 (*bytes)->data() = data; 624 return true; 625 } 626 627 void TopSitesLikelyImpl::RemoveTemporaryThumbnailByURL(const GURL& url) { 628 for (TempImages::iterator i = temp_images_.begin(); i != temp_images_.end(); 629 ++i) { 630 if (i->first == url) { 631 temp_images_.erase(i); 632 return; 633 } 634 } 635 } 636 637 void TopSitesLikelyImpl::AddTemporaryThumbnail( 638 const GURL& url, 639 const base::RefCountedMemory* thumbnail, 640 const ThumbnailScore& score) { 641 if (temp_images_.size() == kMaxTempTopImages) 642 temp_images_.erase(temp_images_.begin()); 643 644 TempImage image; 645 image.first = url; 646 image.second.thumbnail = const_cast<base::RefCountedMemory*>(thumbnail); 647 image.second.thumbnail_score = score; 648 temp_images_.push_back(image); 649 } 650 651 void TopSitesLikelyImpl::TimerFired() { 652 StartQueryForMostVisited(); 653 } 654 655 // static 656 int TopSitesLikelyImpl::GetRedirectDistanceForURL( 657 const MostVisitedURL& most_visited, 658 const GURL& url) { 659 for (size_t i = 0; i < most_visited.redirects.size(); i++) { 660 if (most_visited.redirects[i] == url) 661 return static_cast<int>(most_visited.redirects.size() - i - 1); 662 } 663 NOTREACHED() << "URL should always be found."; 664 return 0; 665 } 666 667 MostVisitedURLList TopSitesLikelyImpl::GetPrepopulatePages() { 668 MostVisitedURLList urls; 669 urls.resize(arraysize(kPrepopulatedPages)); 670 for (size_t i = 0; i < urls.size(); ++i) { 671 MostVisitedURL& url = urls[i]; 672 url.url = GURL(prepopulated_page_urls_[i]); 673 url.redirects.push_back(url.url); 674 url.title = l10n_util::GetStringUTF16(kPrepopulatedPages[i].title_id); 675 } 676 return urls; 677 } 678 679 bool TopSitesLikelyImpl::loaded() const { 680 return loaded_; 681 } 682 683 bool TopSitesLikelyImpl::AddPrepopulatedPages(MostVisitedURLList* urls) { 684 bool added = false; 685 MostVisitedURLList prepopulate_urls = GetPrepopulatePages(); 686 for (size_t i = 0; i < prepopulate_urls.size(); ++i) { 687 if (urls->size() < kTopSitesNumber && 688 IndexOf(*urls, prepopulate_urls[i].url) == -1) { 689 urls->push_back(prepopulate_urls[i]); 690 added = true; 691 } 692 } 693 return added; 694 } 695 696 void TopSitesLikelyImpl::ApplyBlacklist(const MostVisitedURLList& urls, 697 MostVisitedURLList* out) { 698 for (size_t i = 0; i < urls.size() && i < kTopSitesNumber; ++i) { 699 if (!IsBlacklisted(urls[i].url)) 700 out->push_back(urls[i]); 701 } 702 } 703 704 std::string TopSitesLikelyImpl::GetURLHash(const GURL& url) { 705 // We don't use canonical URLs here to be able to blacklist only one of 706 // the two 'duplicate' sites, e.g. 'gmail.com' and 'mail.google.com'. 707 return base::MD5String(url.spec()); 708 } 709 710 base::TimeDelta TopSitesLikelyImpl::GetUpdateDelay() { 711 if (cache_->top_sites().size() <= arraysize(kPrepopulatedPages)) 712 return base::TimeDelta::FromSeconds(30); 713 714 int64 range = kMaxUpdateIntervalMinutes - kMinUpdateIntervalMinutes; 715 int64 minutes = kMaxUpdateIntervalMinutes - 716 last_num_urls_changed_ * range / cache_->top_sites().size(); 717 return base::TimeDelta::FromMinutes(minutes); 718 } 719 720 void TopSitesLikelyImpl::Observe(int type, 721 const content::NotificationSource& source, 722 const content::NotificationDetails& details) { 723 if (!loaded_) 724 return; 725 726 if (type == chrome::NOTIFICATION_HISTORY_URLS_DELETED) { 727 content::Details<history::URLsDeletedDetails> deleted_details(details); 728 if (deleted_details->all_history) { 729 SetTopSites(MostVisitedURLList()); 730 backend_->ResetDatabase(); 731 } else { 732 std::set<size_t> indices_to_delete; // Indices into top_sites_. 733 for (URLRows::const_iterator i = deleted_details->rows.begin(); 734 i != deleted_details->rows.end(); ++i) { 735 if (cache_->IsKnownURL(i->url())) 736 indices_to_delete.insert(cache_->GetURLIndex(i->url())); 737 } 738 739 if (indices_to_delete.empty()) 740 return; 741 742 MostVisitedURLList new_top_sites(cache_->top_sites()); 743 for (std::set<size_t>::reverse_iterator i = indices_to_delete.rbegin(); 744 i != indices_to_delete.rend(); i++) { 745 new_top_sites.erase(new_top_sites.begin() + *i); 746 } 747 SetTopSites(new_top_sites); 748 } 749 StartQueryForMostVisited(); 750 } else if (type == content::NOTIFICATION_NAV_ENTRY_COMMITTED) { 751 NavigationController* controller = 752 content::Source<NavigationController>(source).ptr(); 753 Profile* profile = Profile::FromBrowserContext( 754 controller->GetWebContents()->GetBrowserContext()); 755 if (profile == profile_ && !IsFull()) { 756 content::LoadCommittedDetails* load_details = 757 content::Details<content::LoadCommittedDetails>(details).ptr(); 758 if (!load_details) 759 return; 760 const GURL& url = load_details->entry->GetURL(); 761 if (!cache_->IsKnownURL(url) && HistoryService::CanAddURL(url)) { 762 // To avoid slamming history we throttle requests when the url updates. 763 // To do otherwise negatively impacts perf tests. 764 RestartQueryForTopSitesTimer(GetUpdateDelay()); 765 } 766 } 767 } 768 } 769 770 void TopSitesLikelyImpl::SetTopSites(const MostVisitedURLList& new_top_sites) { 771 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 772 773 MostVisitedURLList top_sites(new_top_sites); 774 AddPrepopulatedPages(&top_sites); 775 776 TopSitesDelta delta; 777 DiffMostVisited(cache_->top_sites(), top_sites, &delta); 778 if (!delta.deleted.empty() || !delta.added.empty() || !delta.moved.empty()) { 779 backend_->UpdateTopSites(delta); 780 } 781 782 last_num_urls_changed_ = delta.added.size() + delta.moved.size(); 783 784 // We always do the following steps (setting top sites in cache, and resetting 785 // thread safe cache ...) as this method is invoked during startup at which 786 // point the caches haven't been updated yet. 787 cache_->SetTopSites(top_sites); 788 789 // See if we have any tmp thumbnails for the new sites. 790 if (!temp_images_.empty()) { 791 for (size_t i = 0; i < top_sites.size(); ++i) { 792 const MostVisitedURL& mv = top_sites[i]; 793 GURL canonical_url = cache_->GetCanonicalURL(mv.url); 794 // At the time we get the thumbnail redirects aren't known, so we have to 795 // iterate through all the images. 796 for (TempImages::iterator it = temp_images_.begin(); 797 it != temp_images_.end(); ++it) { 798 if (canonical_url == cache_->GetCanonicalURL(it->first)) { 799 SetPageThumbnailEncoded( 800 mv.url, it->second.thumbnail.get(), it->second.thumbnail_score); 801 temp_images_.erase(it); 802 break; 803 } 804 } 805 } 806 } 807 808 if (top_sites.size() >= kTopSitesNumber) 809 temp_images_.clear(); 810 811 ResetThreadSafeCache(); 812 ResetThreadSafeImageCache(); 813 NotifyTopSitesChanged(); 814 815 // Restart the timer that queries history for top sites. This is done to 816 // ensure we stay in sync with history. 817 RestartQueryForTopSitesTimer(GetUpdateDelay()); 818 } 819 820 int TopSitesLikelyImpl::num_results_to_request_from_history() const { 821 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 822 823 const DictionaryValue* blacklist = 824 profile_->GetPrefs()->GetDictionary(prefs::kNtpMostVisitedURLsBlacklist); 825 return kTopSitesNumber + (blacklist ? blacklist->size() : 0); 826 } 827 828 void TopSitesLikelyImpl::MoveStateToLoaded() { 829 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 830 831 MostVisitedURLList filtered_urls; 832 PendingCallbacks pending_callbacks; 833 { 834 base::AutoLock lock(lock_); 835 836 if (loaded_) 837 return; // Don't do anything if we're already loaded. 838 loaded_ = true; 839 840 // Now that we're loaded we can service the queued up callbacks. Copy them 841 // here and service them outside the lock. 842 if (!pending_callbacks_.empty()) { 843 filtered_urls = thread_safe_cache_->top_sites(); 844 pending_callbacks.swap(pending_callbacks_); 845 } 846 } 847 848 for (size_t i = 0; i < pending_callbacks.size(); i++) 849 pending_callbacks[i].Run(filtered_urls); 850 851 content::NotificationService::current()->Notify( 852 chrome::NOTIFICATION_TOP_SITES_LOADED, 853 content::Source<Profile>(profile_), 854 content::Details<TopSites>(this)); 855 } 856 857 void TopSitesLikelyImpl::ResetThreadSafeCache() { 858 base::AutoLock lock(lock_); 859 MostVisitedURLList cached; 860 ApplyBlacklist(cache_->top_sites(), &cached); 861 thread_safe_cache_->SetTopSites(cached); 862 } 863 864 void TopSitesLikelyImpl::ResetThreadSafeImageCache() { 865 base::AutoLock lock(lock_); 866 thread_safe_cache_->SetThumbnails(cache_->images()); 867 } 868 869 void TopSitesLikelyImpl::NotifyTopSitesChanged() { 870 content::NotificationService::current()->Notify( 871 chrome::NOTIFICATION_TOP_SITES_CHANGED, 872 content::Source<TopSites>(this), 873 content::NotificationService::NoDetails()); 874 } 875 876 void TopSitesLikelyImpl::RestartQueryForTopSitesTimer(base::TimeDelta delta) { 877 if (timer_.IsRunning() && ((timer_start_time_ + timer_.GetCurrentDelay()) < 878 (base::TimeTicks::Now() + delta))) { 879 return; 880 } 881 882 timer_start_time_ = base::TimeTicks::Now(); 883 timer_.Stop(); 884 timer_.Start(FROM_HERE, delta, this, &TopSitesLikelyImpl::TimerFired); 885 } 886 887 void TopSitesLikelyImpl::OnHistoryMigrationWrittenToDisk() { 888 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 889 890 if (!profile_) 891 return; 892 893 HistoryService* history = HistoryServiceFactory::GetForProfile( 894 profile_, Profile::EXPLICIT_ACCESS); 895 if (history) 896 history->OnTopSitesReady(); 897 } 898 899 void TopSitesLikelyImpl::OnGotMostVisitedThumbnails( 900 const scoped_refptr<MostVisitedThumbnails>& thumbnails, 901 const bool* need_history_migration) { 902 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 903 DCHECK_EQ(top_sites_state_, TOP_SITES_LOADING); 904 905 if (!*need_history_migration) { 906 top_sites_state_ = TOP_SITES_LOADED; 907 908 // Set the top sites directly in the cache so that SetTopSites diffs 909 // correctly. 910 cache_->SetTopSites(thumbnails->most_visited); 911 SetTopSites(thumbnails->most_visited); 912 cache_->SetThumbnails(thumbnails->url_to_images_map); 913 914 ResetThreadSafeImageCache(); 915 916 MoveStateToLoaded(); 917 918 // Start a timer that refreshes top sites from history. 919 RestartQueryForTopSitesTimer( 920 base::TimeDelta::FromSeconds(kUpdateIntervalSecs)); 921 } else { 922 // The top sites file didn't exist or is the wrong version. We need to wait 923 // for history to finish loading to know if we really needed to migrate. 924 if (history_state_ == HISTORY_LOADED) { 925 top_sites_state_ = TOP_SITES_LOADED; 926 SetTopSites(MostVisitedURLList()); 927 MoveStateToLoaded(); 928 } else { 929 top_sites_state_ = TOP_SITES_LOADED_WAITING_FOR_HISTORY; 930 // Ask for history just in case it hasn't been loaded yet. When history 931 // finishes loading we'll do migration and/or move to loaded. 932 HistoryServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS); 933 } 934 } 935 } 936 937 void TopSitesLikelyImpl::OnTopSitesAvailableFromHistory( 938 CancelableRequestProvider::Handle handle, 939 MostVisitedURLList pages) { 940 SetTopSites(pages); 941 942 // Used only in testing. 943 content::NotificationService::current()->Notify( 944 chrome::NOTIFICATION_TOP_SITES_UPDATED, 945 content::Source<TopSitesLikelyImpl>(this), 946 content::Details<CancelableRequestProvider::Handle>(&handle)); 947 } 948 949 } // namespace history 950