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_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 TopSitesImpl::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 // TopSitesImpl::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 TopSitesImpl::TopSitesImpl(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 TopSitesImpl::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(&TopSitesImpl::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 TopSitesImpl::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 TopSitesImpl::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 TopSitesImpl::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 TopSitesImpl::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 TopSitesImpl::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 TopSitesImpl::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 TopSitesImpl::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 TopSitesImpl::FinishHistoryMigration(const ThumbnailMigration& data) { 361 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 362 DCHECK_EQ(history_state_, HISTORY_MIGRATING); 363 364 history_state_ = HISTORY_LOADED; 365 366 SetTopSites(data.most_visited); 367 368 for (size_t i = 0; i < data.most_visited.size(); ++i) { 369 URLToThumbnailMap::const_iterator image_i = 370 data.url_to_thumbnail_map.find(data.most_visited[i].url); 371 if (image_i != data.url_to_thumbnail_map.end()) { 372 SetPageThumbnailEncoded( 373 data.most_visited[i].url, image_i->second.get(), ThumbnailScore()); 374 } 375 } 376 377 MoveStateToLoaded(); 378 379 ResetThreadSafeImageCache(); 380 381 // We've scheduled all the thumbnails and top sites to be written to the top 382 // sites db, but it hasn't happened yet. Schedule a request on the db thread 383 // that notifies us when done. When done we'll know everything was written and 384 // we can tell history to finish its part of migration. 385 backend_->DoEmptyRequest( 386 base::Bind(&TopSitesImpl::OnHistoryMigrationWrittenToDisk, 387 base::Unretained(this)), 388 &cancelable_task_tracker_); 389 } 390 391 void TopSitesImpl::HistoryLoaded() { 392 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 393 394 if (history_state_ != HISTORY_MIGRATING) { 395 // No migration from history is needed. 396 history_state_ = HISTORY_LOADED; 397 if (top_sites_state_ == TOP_SITES_LOADED_WAITING_FOR_HISTORY) { 398 // TopSites thought it needed migration, but it really didn't. This 399 // typically happens the first time a profile is run with Top Sites 400 // enabled 401 SetTopSites(MostVisitedURLList()); 402 MoveStateToLoaded(); 403 } 404 } 405 // else case can happen if history is unloaded, then loaded again. 406 } 407 408 void TopSitesImpl::SyncWithHistory() { 409 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 410 if (loaded_ && temp_images_.size()) { 411 // If we have temporary thumbnails it means there isn't much data, and most 412 // likely the user is first running Chrome. During this time we throttle 413 // updating from history by 30 seconds. If the user creates a new tab page 414 // during this window of time we force updating from history so that the new 415 // tab page isn't so far out of date. 416 timer_.Stop(); 417 StartQueryForMostVisited(); 418 } 419 } 420 421 bool TopSitesImpl::HasBlacklistedItems() const { 422 const DictionaryValue* blacklist = 423 profile_->GetPrefs()->GetDictionary(prefs::kNtpMostVisitedURLsBlacklist); 424 return blacklist && !blacklist->empty(); 425 } 426 427 void TopSitesImpl::AddBlacklistedURL(const GURL& url) { 428 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 429 430 Value* dummy = Value::CreateNullValue(); 431 { 432 DictionaryPrefUpdate update(profile_->GetPrefs(), 433 prefs::kNtpMostVisitedURLsBlacklist); 434 DictionaryValue* blacklist = update.Get(); 435 blacklist->SetWithoutPathExpansion(GetURLHash(url), dummy); 436 } 437 438 ResetThreadSafeCache(); 439 NotifyTopSitesChanged(); 440 } 441 442 void TopSitesImpl::RemoveBlacklistedURL(const GURL& url) { 443 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 444 { 445 DictionaryPrefUpdate update(profile_->GetPrefs(), 446 prefs::kNtpMostVisitedURLsBlacklist); 447 DictionaryValue* blacklist = update.Get(); 448 blacklist->RemoveWithoutPathExpansion(GetURLHash(url), NULL); 449 } 450 ResetThreadSafeCache(); 451 NotifyTopSitesChanged(); 452 } 453 454 bool TopSitesImpl::IsBlacklisted(const GURL& url) { 455 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 456 const DictionaryValue* blacklist = 457 profile_->GetPrefs()->GetDictionary(prefs::kNtpMostVisitedURLsBlacklist); 458 return blacklist && blacklist->HasKey(GetURLHash(url)); 459 } 460 461 void TopSitesImpl::ClearBlacklistedURLs() { 462 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 463 { 464 DictionaryPrefUpdate update(profile_->GetPrefs(), 465 prefs::kNtpMostVisitedURLsBlacklist); 466 DictionaryValue* blacklist = update.Get(); 467 blacklist->Clear(); 468 } 469 ResetThreadSafeCache(); 470 NotifyTopSitesChanged(); 471 } 472 473 void TopSitesImpl::Shutdown() { 474 profile_ = NULL; 475 // Cancel all requests so that the service doesn't callback to us after we've 476 // invoked Shutdown (this could happen if we have a pending request and 477 // Shutdown is invoked). 478 history_consumer_.CancelAllRequests(); 479 backend_->Shutdown(); 480 } 481 482 // static 483 void TopSitesImpl::DiffMostVisited(const MostVisitedURLList& old_list, 484 const MostVisitedURLList& new_list, 485 TopSitesDelta* delta) { 486 // Add all the old URLs for quick lookup. This maps URLs to the corresponding 487 // index in the input. 488 std::map<GURL, size_t> all_old_urls; 489 for (size_t i = 0; i < old_list.size(); i++) 490 all_old_urls[old_list[i].url] = i; 491 492 // Check all the URLs in the new set to see which ones are new or just moved. 493 // When we find a match in the old set, we'll reset its index to our special 494 // marker. This allows us to quickly identify the deleted ones in a later 495 // pass. 496 const size_t kAlreadyFoundMarker = static_cast<size_t>(-1); 497 for (size_t i = 0; i < new_list.size(); i++) { 498 std::map<GURL, size_t>::iterator found = all_old_urls.find(new_list[i].url); 499 if (found == all_old_urls.end()) { 500 MostVisitedURLWithRank added; 501 added.url = new_list[i]; 502 added.rank = i; 503 delta->added.push_back(added); 504 } else { 505 if (found->second != i) { 506 MostVisitedURLWithRank moved; 507 moved.url = new_list[i]; 508 moved.rank = i; 509 delta->moved.push_back(moved); 510 } 511 found->second = kAlreadyFoundMarker; 512 } 513 } 514 515 // Any member without the special marker in the all_old_urls list means that 516 // there wasn't a "new" URL that mapped to it, so it was deleted. 517 for (std::map<GURL, size_t>::const_iterator i = all_old_urls.begin(); 518 i != all_old_urls.end(); ++i) { 519 if (i->second != kAlreadyFoundMarker) 520 delta->deleted.push_back(old_list[i->second]); 521 } 522 } 523 524 CancelableRequestProvider::Handle TopSitesImpl::StartQueryForMostVisited() { 525 DCHECK(loaded_); 526 if (!profile_) 527 return 0; 528 529 HistoryService* hs = HistoryServiceFactory::GetForProfile( 530 profile_, Profile::EXPLICIT_ACCESS); 531 // |hs| may be null during unit tests. 532 if (hs) { 533 return hs->QueryMostVisitedURLs( 534 num_results_to_request_from_history(), 535 kDaysOfHistory, 536 &history_consumer_, 537 base::Bind(&TopSitesImpl::OnTopSitesAvailableFromHistory, 538 base::Unretained(this))); 539 } 540 return 0; 541 } 542 543 bool TopSitesImpl::IsKnownURL(const GURL& url) { 544 return loaded_ && cache_->IsKnownURL(url); 545 } 546 547 const std::string& TopSitesImpl::GetCanonicalURLString(const GURL& url) const { 548 return cache_->GetCanonicalURL(url).spec(); 549 } 550 551 bool TopSitesImpl::IsFull() { 552 return loaded_ && cache_->top_sites().size() >= kTopSitesNumber; 553 } 554 555 TopSitesImpl::~TopSitesImpl() { 556 } 557 558 bool TopSitesImpl::SetPageThumbnailNoDB( 559 const GURL& url, 560 const base::RefCountedMemory* thumbnail_data, 561 const ThumbnailScore& score) { 562 // This should only be invoked when we know about the url. 563 DCHECK(cache_->IsKnownURL(url)); 564 565 const MostVisitedURL& most_visited = 566 cache_->top_sites()[cache_->GetURLIndex(url)]; 567 Images* image = cache_->GetImage(url); 568 569 // When comparing the thumbnail scores, we need to take into account the 570 // redirect hops, which are not generated when the thumbnail is because the 571 // redirects weren't known. We fill that in here since we know the redirects. 572 ThumbnailScore new_score_with_redirects(score); 573 new_score_with_redirects.redirect_hops_from_dest = 574 GetRedirectDistanceForURL(most_visited, url); 575 576 if (!ShouldReplaceThumbnailWith(image->thumbnail_score, 577 new_score_with_redirects) && 578 image->thumbnail.get()) 579 return false; // The one we already have is better. 580 581 image->thumbnail = const_cast<base::RefCountedMemory*>(thumbnail_data); 582 image->thumbnail_score = new_score_with_redirects; 583 584 ResetThreadSafeImageCache(); 585 return true; 586 } 587 588 bool TopSitesImpl::SetPageThumbnailEncoded( 589 const GURL& url, 590 const base::RefCountedMemory* thumbnail, 591 const ThumbnailScore& score) { 592 if (!SetPageThumbnailNoDB(url, thumbnail, score)) 593 return false; 594 595 // Update the database. 596 if (!cache_->IsKnownURL(url)) 597 return false; 598 599 size_t index = cache_->GetURLIndex(url); 600 const MostVisitedURL& most_visited = cache_->top_sites()[index]; 601 backend_->SetPageThumbnail(most_visited, 602 index, 603 *(cache_->GetImage(most_visited.url))); 604 return true; 605 } 606 607 // static 608 bool TopSitesImpl::EncodeBitmap(const gfx::Image& bitmap, 609 scoped_refptr<base::RefCountedBytes>* bytes) { 610 if (bitmap.IsEmpty()) 611 return false; 612 *bytes = new base::RefCountedBytes(); 613 std::vector<unsigned char> data; 614 if (!gfx::JPEG1xEncodedDataFromImage(bitmap, kTopSitesImageQuality, &data)) 615 return false; 616 617 // As we're going to cache this data, make sure the vector is only as big as 618 // it needs to be, as JPEGCodec::Encode() over-allocates data.capacity(). 619 // (In a C++0x future, we can just call shrink_to_fit() in Encode()) 620 (*bytes)->data() = data; 621 return true; 622 } 623 624 void TopSitesImpl::RemoveTemporaryThumbnailByURL(const GURL& url) { 625 for (TempImages::iterator i = temp_images_.begin(); i != temp_images_.end(); 626 ++i) { 627 if (i->first == url) { 628 temp_images_.erase(i); 629 return; 630 } 631 } 632 } 633 634 void TopSitesImpl::AddTemporaryThumbnail( 635 const GURL& url, 636 const base::RefCountedMemory* thumbnail, 637 const ThumbnailScore& score) { 638 if (temp_images_.size() == kMaxTempTopImages) 639 temp_images_.erase(temp_images_.begin()); 640 641 TempImage image; 642 image.first = url; 643 image.second.thumbnail = const_cast<base::RefCountedMemory*>(thumbnail); 644 image.second.thumbnail_score = score; 645 temp_images_.push_back(image); 646 } 647 648 void TopSitesImpl::TimerFired() { 649 StartQueryForMostVisited(); 650 } 651 652 // static 653 int TopSitesImpl::GetRedirectDistanceForURL(const MostVisitedURL& most_visited, 654 const GURL& url) { 655 for (size_t i = 0; i < most_visited.redirects.size(); i++) { 656 if (most_visited.redirects[i] == url) 657 return static_cast<int>(most_visited.redirects.size() - i - 1); 658 } 659 NOTREACHED() << "URL should always be found."; 660 return 0; 661 } 662 663 MostVisitedURLList TopSitesImpl::GetPrepopulatePages() { 664 MostVisitedURLList urls; 665 urls.resize(arraysize(kPrepopulatedPages)); 666 for (size_t i = 0; i < urls.size(); ++i) { 667 MostVisitedURL& url = urls[i]; 668 url.url = GURL(prepopulated_page_urls_[i]); 669 url.redirects.push_back(url.url); 670 url.title = l10n_util::GetStringUTF16(kPrepopulatedPages[i].title_id); 671 } 672 return urls; 673 } 674 675 bool TopSitesImpl::loaded() const { 676 return loaded_; 677 } 678 679 bool TopSitesImpl::AddPrepopulatedPages(MostVisitedURLList* urls) { 680 bool added = false; 681 MostVisitedURLList prepopulate_urls = GetPrepopulatePages(); 682 for (size_t i = 0; i < prepopulate_urls.size(); ++i) { 683 if (urls->size() < kTopSitesNumber && 684 IndexOf(*urls, prepopulate_urls[i].url) == -1) { 685 urls->push_back(prepopulate_urls[i]); 686 added = true; 687 } 688 } 689 return added; 690 } 691 692 void TopSitesImpl::ApplyBlacklist(const MostVisitedURLList& urls, 693 MostVisitedURLList* out) { 694 for (size_t i = 0; i < urls.size() && i < kTopSitesNumber; ++i) { 695 if (!IsBlacklisted(urls[i].url)) 696 out->push_back(urls[i]); 697 } 698 } 699 700 std::string TopSitesImpl::GetURLHash(const GURL& url) { 701 // We don't use canonical URLs here to be able to blacklist only one of 702 // the two 'duplicate' sites, e.g. 'gmail.com' and 'mail.google.com'. 703 return base::MD5String(url.spec()); 704 } 705 706 base::TimeDelta TopSitesImpl::GetUpdateDelay() { 707 if (cache_->top_sites().size() <= arraysize(kPrepopulatedPages)) 708 return base::TimeDelta::FromSeconds(30); 709 710 int64 range = kMaxUpdateIntervalMinutes - kMinUpdateIntervalMinutes; 711 int64 minutes = kMaxUpdateIntervalMinutes - 712 last_num_urls_changed_ * range / cache_->top_sites().size(); 713 return base::TimeDelta::FromMinutes(minutes); 714 } 715 716 void TopSitesImpl::Observe(int type, 717 const content::NotificationSource& source, 718 const content::NotificationDetails& details) { 719 if (!loaded_) 720 return; 721 722 if (type == chrome::NOTIFICATION_HISTORY_URLS_DELETED) { 723 content::Details<history::URLsDeletedDetails> deleted_details(details); 724 if (deleted_details->all_history) { 725 SetTopSites(MostVisitedURLList()); 726 backend_->ResetDatabase(); 727 } else { 728 std::set<size_t> indices_to_delete; // Indices into top_sites_. 729 for (URLRows::const_iterator i = deleted_details->rows.begin(); 730 i != deleted_details->rows.end(); ++i) { 731 if (cache_->IsKnownURL(i->url())) 732 indices_to_delete.insert(cache_->GetURLIndex(i->url())); 733 } 734 735 if (indices_to_delete.empty()) 736 return; 737 738 MostVisitedURLList new_top_sites(cache_->top_sites()); 739 for (std::set<size_t>::reverse_iterator i = indices_to_delete.rbegin(); 740 i != indices_to_delete.rend(); i++) { 741 new_top_sites.erase(new_top_sites.begin() + *i); 742 } 743 SetTopSites(new_top_sites); 744 } 745 StartQueryForMostVisited(); 746 } else if (type == content::NOTIFICATION_NAV_ENTRY_COMMITTED) { 747 NavigationController* controller = 748 content::Source<NavigationController>(source).ptr(); 749 Profile* profile = Profile::FromBrowserContext( 750 controller->GetWebContents()->GetBrowserContext()); 751 if (profile == profile_ && !IsFull()) { 752 content::LoadCommittedDetails* load_details = 753 content::Details<content::LoadCommittedDetails>(details).ptr(); 754 if (!load_details) 755 return; 756 const GURL& url = load_details->entry->GetURL(); 757 if (!cache_->IsKnownURL(url) && HistoryService::CanAddURL(url)) { 758 // To avoid slamming history we throttle requests when the url updates. 759 // To do otherwise negatively impacts perf tests. 760 RestartQueryForTopSitesTimer(GetUpdateDelay()); 761 } 762 } 763 } 764 } 765 766 void TopSitesImpl::SetTopSites(const MostVisitedURLList& new_top_sites) { 767 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 768 769 MostVisitedURLList top_sites(new_top_sites); 770 AddPrepopulatedPages(&top_sites); 771 772 TopSitesDelta delta; 773 DiffMostVisited(cache_->top_sites(), top_sites, &delta); 774 if (!delta.deleted.empty() || !delta.added.empty() || !delta.moved.empty()) { 775 backend_->UpdateTopSites(delta); 776 } 777 778 last_num_urls_changed_ = delta.added.size() + delta.moved.size(); 779 780 // We always do the following steps (setting top sites in cache, and resetting 781 // thread safe cache ...) as this method is invoked during startup at which 782 // point the caches haven't been updated yet. 783 cache_->SetTopSites(top_sites); 784 785 // See if we have any tmp thumbnails for the new sites. 786 if (!temp_images_.empty()) { 787 for (size_t i = 0; i < top_sites.size(); ++i) { 788 const MostVisitedURL& mv = top_sites[i]; 789 GURL canonical_url = cache_->GetCanonicalURL(mv.url); 790 // At the time we get the thumbnail redirects aren't known, so we have to 791 // iterate through all the images. 792 for (TempImages::iterator it = temp_images_.begin(); 793 it != temp_images_.end(); ++it) { 794 if (canonical_url == cache_->GetCanonicalURL(it->first)) { 795 SetPageThumbnailEncoded( 796 mv.url, it->second.thumbnail.get(), it->second.thumbnail_score); 797 temp_images_.erase(it); 798 break; 799 } 800 } 801 } 802 } 803 804 if (top_sites.size() >= kTopSitesNumber) 805 temp_images_.clear(); 806 807 ResetThreadSafeCache(); 808 ResetThreadSafeImageCache(); 809 NotifyTopSitesChanged(); 810 811 // Restart the timer that queries history for top sites. This is done to 812 // ensure we stay in sync with history. 813 RestartQueryForTopSitesTimer(GetUpdateDelay()); 814 } 815 816 int TopSitesImpl::num_results_to_request_from_history() const { 817 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 818 819 const DictionaryValue* blacklist = 820 profile_->GetPrefs()->GetDictionary(prefs::kNtpMostVisitedURLsBlacklist); 821 return kTopSitesNumber + (blacklist ? blacklist->size() : 0); 822 } 823 824 void TopSitesImpl::MoveStateToLoaded() { 825 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 826 827 MostVisitedURLList filtered_urls; 828 PendingCallbacks pending_callbacks; 829 { 830 base::AutoLock lock(lock_); 831 832 if (loaded_) 833 return; // Don't do anything if we're already loaded. 834 loaded_ = true; 835 836 // Now that we're loaded we can service the queued up callbacks. Copy them 837 // here and service them outside the lock. 838 if (!pending_callbacks_.empty()) { 839 filtered_urls = thread_safe_cache_->top_sites(); 840 pending_callbacks.swap(pending_callbacks_); 841 } 842 } 843 844 for (size_t i = 0; i < pending_callbacks.size(); i++) 845 pending_callbacks[i].Run(filtered_urls); 846 847 content::NotificationService::current()->Notify( 848 chrome::NOTIFICATION_TOP_SITES_LOADED, 849 content::Source<Profile>(profile_), 850 content::Details<TopSites>(this)); 851 } 852 853 void TopSitesImpl::ResetThreadSafeCache() { 854 base::AutoLock lock(lock_); 855 MostVisitedURLList cached; 856 ApplyBlacklist(cache_->top_sites(), &cached); 857 thread_safe_cache_->SetTopSites(cached); 858 } 859 860 void TopSitesImpl::ResetThreadSafeImageCache() { 861 base::AutoLock lock(lock_); 862 thread_safe_cache_->SetThumbnails(cache_->images()); 863 } 864 865 void TopSitesImpl::NotifyTopSitesChanged() { 866 content::NotificationService::current()->Notify( 867 chrome::NOTIFICATION_TOP_SITES_CHANGED, 868 content::Source<TopSites>(this), 869 content::NotificationService::NoDetails()); 870 } 871 872 void TopSitesImpl::RestartQueryForTopSitesTimer(base::TimeDelta delta) { 873 if (timer_.IsRunning() && ((timer_start_time_ + timer_.GetCurrentDelay()) < 874 (base::TimeTicks::Now() + delta))) { 875 return; 876 } 877 878 timer_start_time_ = base::TimeTicks::Now(); 879 timer_.Stop(); 880 timer_.Start(FROM_HERE, delta, this, &TopSitesImpl::TimerFired); 881 } 882 883 void TopSitesImpl::OnHistoryMigrationWrittenToDisk() { 884 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 885 886 if (!profile_) 887 return; 888 889 HistoryService* history = HistoryServiceFactory::GetForProfile( 890 profile_, Profile::EXPLICIT_ACCESS); 891 if (history) 892 history->OnTopSitesReady(); 893 } 894 895 void TopSitesImpl::OnGotMostVisitedThumbnails( 896 const scoped_refptr<MostVisitedThumbnails>& thumbnails, 897 const bool* need_history_migration) { 898 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 899 DCHECK_EQ(top_sites_state_, TOP_SITES_LOADING); 900 901 if (!*need_history_migration) { 902 top_sites_state_ = TOP_SITES_LOADED; 903 904 // Set the top sites directly in the cache so that SetTopSites diffs 905 // correctly. 906 cache_->SetTopSites(thumbnails->most_visited); 907 SetTopSites(thumbnails->most_visited); 908 cache_->SetThumbnails(thumbnails->url_to_images_map); 909 910 ResetThreadSafeImageCache(); 911 912 MoveStateToLoaded(); 913 914 // Start a timer that refreshes top sites from history. 915 RestartQueryForTopSitesTimer( 916 base::TimeDelta::FromSeconds(kUpdateIntervalSecs)); 917 } else { 918 // The top sites file didn't exist or is the wrong version. We need to wait 919 // for history to finish loading to know if we really needed to migrate. 920 if (history_state_ == HISTORY_LOADED) { 921 top_sites_state_ = TOP_SITES_LOADED; 922 SetTopSites(MostVisitedURLList()); 923 MoveStateToLoaded(); 924 } else { 925 top_sites_state_ = TOP_SITES_LOADED_WAITING_FOR_HISTORY; 926 // Ask for history just in case it hasn't been loaded yet. When history 927 // finishes loading we'll do migration and/or move to loaded. 928 HistoryServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS); 929 } 930 } 931 } 932 933 void TopSitesImpl::OnTopSitesAvailableFromHistory( 934 CancelableRequestProvider::Handle handle, 935 MostVisitedURLList pages) { 936 SetTopSites(pages); 937 938 // Used only in testing. 939 content::NotificationService::current()->Notify( 940 chrome::NOTIFICATION_TOP_SITES_UPDATED, 941 content::Source<TopSitesImpl>(this), 942 content::Details<CancelableRequestProvider::Handle>(&handle)); 943 } 944 945 } // namespace history 946