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