• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
LoadThumbnailsFromHistoryTask(TopSites * top_sites,int result_count)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 
RunOnDBThread(history::HistoryBackend * backend,history::HistoryDatabase * db)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 
DoneRunOnMainThread()105   virtual void DoneRunOnMainThread() {
106     top_sites_->FinishHistoryMigration(data_);
107   }
108 
109  private:
ShouldFetchThumbnailFor(const GURL & url)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 
TopSites(Profile * profile)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 
Init(const FilePath & db_name)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 
SetPageThumbnail(const GURL & url,const SkBitmap & thumbnail,const ThumbnailScore & score)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 
GetMostVisitedURLs(CancelableRequestConsumer * consumer,GetTopSitesCallback * callback)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 
GetPageThumbnail(const GURL & url,scoped_refptr<RefCountedBytes> * bytes)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 
GetPageThumbnailScore(const GURL & url,ThumbnailScore * score)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 
GetTemporaryPageThumbnailScore(const GURL & url,ThumbnailScore * score)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.
IndexOf(const MostVisitedURLList & urls,const GURL & url)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 
MigrateFromHistory()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 
FinishHistoryMigration(const ThumbnailMigration & data)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 
HistoryLoaded()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 
HasBlacklistedItems() const343 bool TopSites::HasBlacklistedItems() const {
344   return !blacklist_->empty();
345 }
346 
AddBlacklistedURL(const GURL & url)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 
RemoveBlacklistedURL(const GURL & url)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 
IsBlacklisted(const GURL & url)373 bool TopSites::IsBlacklisted(const GURL& url) {
374   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
375   return blacklist_->HasKey(GetURLHash(url));
376 }
377 
ClearBlacklistedURLs()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 
AddPinnedURL(const GURL & url,size_t pinned_index)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 
IsURLPinned(const GURL & url)411 bool TopSites::IsURLPinned(const GURL& url) {
412   int tmp;
413   return pinned_urls_->GetIntegerWithoutPathExpansion(GetURLString(url), &tmp);
414 }
415 
RemovePinnedURL(const GURL & url)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 
GetPinnedURLAtIndex(size_t index,GURL * url)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, &current_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 
Shutdown()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
DiffMostVisited(const MostVisitedURLList & old_list,const MostVisitedURLList & new_list,TopSitesDelta * delta)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 
StartQueryForMostVisited()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 
IsKnownURL(const GURL & url)511 bool TopSites::IsKnownURL(const GURL& url) {
512   return loaded_ && cache_->IsKnownURL(url);
513 }
514 
IsFull()515 bool TopSites::IsFull() {
516   return loaded_ && cache_->top_sites().size() >= kTopSitesNumber;
517 }
518 
~TopSites()519 TopSites::~TopSites() {
520 }
521 
SetPageThumbnailNoDB(const GURL & url,const RefCountedBytes * thumbnail_data,const ThumbnailScore & score)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 
SetPageThumbnailEncoded(const GURL & url,const RefCountedBytes * thumbnail,const ThumbnailScore & score)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
EncodeBitmap(const SkBitmap & bitmap,scoped_refptr<RefCountedBytes> * bytes)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 
RemoveTemporaryThumbnailByURL(const GURL & url)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 
AddTemporaryThumbnail(const GURL & url,const RefCountedBytes * thumbnail,const ThumbnailScore & score)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 
TimerFired()612 void TopSites::TimerFired() {
613   StartQueryForMostVisited();
614 }
615 
616 // static
GetRedirectDistanceForURL(const MostVisitedURL & most_visited,const GURL & url)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
GetPrepopulatePages()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
AddPrepopulatedPages(MostVisitedURLList * urls)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 
MigratePinnedURLs()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 
ApplyBlacklistAndPinnedURLs(const MostVisitedURLList & urls,MostVisitedURLList * out)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 
GetURLString(const GURL & url)735 std::string TopSites::GetURLString(const GURL& url) {
736   return cache_->GetCanonicalURL(url).spec();
737 }
738 
GetURLHash(const GURL & url)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 
GetUpdateDelay()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
ProcessPendingCallbacks(const PendingCallbackSet & pending_callbacks,const MostVisitedURLList & urls)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 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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 
SetTopSites(const MostVisitedURLList & new_top_sites)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 
num_results_to_request_from_history() const865 int TopSites::num_results_to_request_from_history() const {
866   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
867 
868   return kTopSitesNumber + blacklist_->size();
869 }
870 
MoveStateToLoaded()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 
ResetThreadSafeCache()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 
ResetThreadSafeImageCache()905 void TopSites::ResetThreadSafeImageCache() {
906   base::AutoLock lock(lock_);
907   thread_safe_cache_->SetThumbnails(cache_->images());
908   thread_safe_cache_->RemoveUnreferencedThumbnails();
909 }
910 
RestartQueryForTopSitesTimer(base::TimeDelta delta)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 
OnHistoryMigrationWrittenToDisk(TopSitesBackend::Handle handle)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 
OnGotMostVisitedThumbnails(CancelableRequestProvider::Handle handle,scoped_refptr<MostVisitedThumbnails> data,bool may_need_history_migration)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 
OnTopSitesAvailableFromHistory(CancelableRequestProvider::Handle handle,MostVisitedURLList pages)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