Home | History | Annotate | Download | only in history
      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