diff options
author | Ben Murdoch <benm@google.com> | 2010-11-18 18:32:45 +0000 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2010-11-18 18:38:07 +0000 |
commit | 513209b27ff55e2841eac0e4120199c23acce758 (patch) | |
tree | aeba30bb08c5f47c57003544e378a377c297eee6 /chrome/browser/history | |
parent | 164f7496de0fbee436b385a79ead9e3cb81a50c1 (diff) | |
download | external_chromium-513209b27ff55e2841eac0e4120199c23acce758.zip external_chromium-513209b27ff55e2841eac0e4120199c23acce758.tar.gz external_chromium-513209b27ff55e2841eac0e4120199c23acce758.tar.bz2 |
Merge Chromium at r65505: Initial merge by git.
Change-Id: I31d8f1d8cd33caaf7f47ffa7350aef42d5fbdb45
Diffstat (limited to 'chrome/browser/history')
32 files changed, 2734 insertions, 1993 deletions
diff --git a/chrome/browser/history/archived_database.cc b/chrome/browser/history/archived_database.cc index 72fa998..08a742f 100644 --- a/chrome/browser/history/archived_database.cc +++ b/chrome/browser/history/archived_database.cc @@ -62,6 +62,7 @@ bool ArchivedDatabase::Init(const FilePath& file_name) { return false; } CreateMainURLIndex(); + CreateKeywordSearchTermsIndices(); if (EnsureCurrentVersion() != sql::INIT_OK) { db_.Close(); diff --git a/chrome/browser/history/expire_history_backend_unittest.cc b/chrome/browser/history/expire_history_backend_unittest.cc index 91129ed..3177dde 100644 --- a/chrome/browser/history/expire_history_backend_unittest.cc +++ b/chrome/browser/history/expire_history_backend_unittest.cc @@ -8,6 +8,7 @@ #include "base/file_util.h" #include "base/path_service.h" #include "base/scoped_ptr.h" +#include "base/scoped_temp_dir.h" #include "base/string16.h" #include "base/utf_string_conversions.h" #include "chrome/browser/bookmarks/bookmark_model.h" @@ -31,7 +32,6 @@ using base::TimeDelta; using base::TimeTicks; // Filename constants. -static const FilePath::CharType kTestDir[] = FILE_PATH_LITERAL("ExpireTest"); static const FilePath::CharType kHistoryFile[] = FILE_PATH_LITERAL("History"); static const FilePath::CharType kArchivedHistoryFile[] = FILE_PATH_LITERAL("Archived History"); @@ -49,6 +49,8 @@ class ExpireHistoryTest : public testing::Test, public: ExpireHistoryTest() : bookmark_model_(NULL), + ui_thread_(BrowserThread::UI, &message_loop_), + db_thread_(BrowserThread::DB, &message_loop_), ALLOW_THIS_IN_INITIALIZER_LIST(expirer_(this, &bookmark_model_)), now_(Time::Now()) { } @@ -85,9 +87,17 @@ class ExpireHistoryTest : public testing::Test, static bool IsStringInFile(const FilePath& filename, const char* str); + // Returns the path the db files are created in. + const FilePath& path() const { return tmp_dir_.path(); } + + // This must be destroyed last. + ScopedTempDir tmp_dir_; + BookmarkModel bookmark_model_; - MessageLoop message_loop_; + MessageLoopForUI message_loop_; + BrowserThread ui_thread_; + BrowserThread db_thread_; ExpireHistoryBackend expirer_; @@ -108,43 +118,40 @@ class ExpireHistoryTest : public testing::Test, NotificationList; NotificationList notifications_; - // Directory for the history files. - FilePath dir_; - private: void SetUp() { - FilePath temp_dir; - PathService::Get(base::DIR_TEMP, &temp_dir); - dir_ = temp_dir.Append(kTestDir); - file_util::Delete(dir_, true); - file_util::CreateDirectory(dir_); + ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir()); - FilePath history_name = dir_.Append(kHistoryFile); + FilePath history_name = path().Append(kHistoryFile); main_db_.reset(new HistoryDatabase); if (main_db_->Init(history_name, FilePath()) != sql::INIT_OK) main_db_.reset(); - FilePath archived_name = dir_.Append(kArchivedHistoryFile); + FilePath archived_name = path().Append(kArchivedHistoryFile); archived_db_.reset(new ArchivedDatabase); if (!archived_db_->Init(archived_name)) archived_db_.reset(); - FilePath thumb_name = dir_.Append(kThumbnailFile); + FilePath thumb_name = path().Append(kThumbnailFile); thumb_db_.reset(new ThumbnailDatabase); if (thumb_db_->Init(thumb_name, NULL) != sql::INIT_OK) thumb_db_.reset(); - text_db_.reset(new TextDatabaseManager(dir_, + text_db_.reset(new TextDatabaseManager(path(), main_db_.get(), main_db_.get())); if (!text_db_->Init(NULL)) text_db_.reset(); expirer_.SetDatabases(main_db_.get(), archived_db_.get(), thumb_db_.get(), text_db_.get()); + profile_.CreateTopSites(); + profile_.BlockUntilTopSitesLoaded(); top_sites_ = profile_.GetTopSites(); } void TearDown() { + top_sites_ = NULL; + ClearLastNotifications(); expirer_.SetDatabases(NULL, NULL, NULL, NULL); @@ -153,8 +160,6 @@ class ExpireHistoryTest : public testing::Test, archived_db_.reset(); thumb_db_.reset(); text_db_.reset(); - TopSites::DeleteTopSites(top_sites_); - file_util::Delete(dir_, true); } // BroadcastNotificationDelegate implementation. @@ -309,11 +314,13 @@ bool ExpireHistoryTest::HasFavIcon(FavIconID favicon_id) { } bool ExpireHistoryTest::HasThumbnail(URLID url_id) { + // TODO(sky): fix this. This test isn't really valid for TopSites. For + // TopSites we should be checking URL always, not the id. URLRow info; if (!main_db_->GetURLRow(url_id, &info)) return false; GURL url = info.url(); - RefCountedBytes *data; + scoped_refptr<RefCountedBytes> data; return top_sites_->GetPageThumbnail(url, &data); } @@ -350,7 +357,8 @@ void ExpireHistoryTest::EnsureURLInfoGone(const URLRow& row) { EXPECT_EQ(0U, visits.size()); // Thumbnail should be gone. - EXPECT_FALSE(HasThumbnail(row.id())); + // TODO(sky): fix this, see comment in HasThumbnail. + // EXPECT_FALSE(HasThumbnail(row.id())); // Check the notifications. There should be a delete notification with this // URL in it. There should also be a "typed URL changed" notification if the @@ -439,7 +447,8 @@ TEST_F(ExpireHistoryTest, FLAKY_DeleteURLAndFavicon) { URLRow last_row; ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &last_row)); EXPECT_TRUE(HasFavIcon(last_row.favicon_id())); - EXPECT_TRUE(HasThumbnail(url_ids[2])); + // TODO(sky): fix this, see comment in HasThumbnail. + // EXPECT_TRUE(HasThumbnail(url_ids[2])); VisitVector visits; main_db_->GetVisitsForURL(url_ids[2], &visits); @@ -452,14 +461,14 @@ TEST_F(ExpireHistoryTest, FLAKY_DeleteURLAndFavicon) { visits[0].visit_time); // Compute the text DB filename. - FilePath fts_filename = dir_.Append( + FilePath fts_filename = path().Append( TextDatabase::IDToFileName(text_db_->TimeToID(visit_times[3]))); // When checking the file, the database must be closed. We then re-initialize // it just like the test set-up did. text_db_.reset(); EXPECT_TRUE(IsStringInFile(fts_filename, "goats")); - text_db_.reset(new TextDatabaseManager(dir_, + text_db_.reset(new TextDatabaseManager(path(), main_db_.get(), main_db_.get())); ASSERT_TRUE(text_db_->Init(NULL)); expirer_.SetDatabases(main_db_.get(), archived_db_.get(), thumb_db_.get(), @@ -472,7 +481,7 @@ TEST_F(ExpireHistoryTest, FLAKY_DeleteURLAndFavicon) { // doesn't remove it from the file, we want to be sure we're doing the latter. text_db_.reset(); EXPECT_FALSE(IsStringInFile(fts_filename, "goats")); - text_db_.reset(new TextDatabaseManager(dir_, + text_db_.reset(new TextDatabaseManager(path(), main_db_.get(), main_db_.get())); ASSERT_TRUE(text_db_->Init(NULL)); expirer_.SetDatabases(main_db_.get(), archived_db_.get(), thumb_db_.get(), @@ -500,7 +509,8 @@ TEST_F(ExpireHistoryTest, DeleteURLWithoutFavicon) { URLRow last_row; ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &last_row)); EXPECT_TRUE(HasFavIcon(last_row.favicon_id())); - EXPECT_TRUE(HasThumbnail(url_ids[1])); + // TODO(sky): fix this, see comment in HasThumbnail. + // EXPECT_TRUE(HasThumbnail(url_ids[1])); VisitVector visits; main_db_->GetVisitsForURL(url_ids[1], &visits); @@ -546,7 +556,8 @@ TEST_F(ExpireHistoryTest, DontDeleteStarredURL) { ASSERT_EQ(0U, visits.size()); // Should still have the thumbnail. - ASSERT_TRUE(HasThumbnail(url_row.id())); + // TODO(sky): fix this, see comment in HasThumbnail. + // ASSERT_TRUE(HasThumbnail(url_row.id())); // Unstar the URL and delete again. bookmark_model_.SetURLStarred(url, string16(), false); @@ -604,7 +615,8 @@ TEST_F(ExpireHistoryTest, FlushRecentURLsUnstarred) { // Verify that the middle URL's favicon and thumbnail is still there. EXPECT_TRUE(HasFavIcon(url_row1.favicon_id())); - EXPECT_TRUE(HasThumbnail(url_row1.id())); + // TODO(sky): fix this, see comment in HasThumbnail. + // EXPECT_TRUE(HasThumbnail(url_row1.id())); // Verify that the last URL was deleted. EnsureURLInfoGone(url_row2); @@ -660,12 +672,14 @@ TEST_F(ExpireHistoryTest, FlushRecentURLsUnstarredRestricted) { // Verify that the middle URL's favicon and thumbnail is still there. EXPECT_TRUE(HasFavIcon(url_row1.favicon_id())); - EXPECT_TRUE(HasThumbnail(url_row1.id())); + // TODO(sky): fix this, see comment in HasThumbnail. + // EXPECT_TRUE(HasThumbnail(url_row1.id())); // Verify that the last URL was not touched. EXPECT_TRUE(main_db_->GetURLRow(url_ids[2], &temp_row)); EXPECT_TRUE(HasFavIcon(url_row2.favicon_id())); - EXPECT_TRUE(HasThumbnail(url_row2.id())); + // TODO(sky): fix this, see comment in HasThumbnail. + // EXPECT_TRUE(HasThumbnail(url_row2.id())); } // Expire a starred URL, it shouldn't get deleted @@ -706,9 +720,11 @@ TEST_F(ExpireHistoryTest, FlushRecentURLsStarred) { // exists in history, this should not be a privacy problem, we only update // the visit counts in this case for consistency anyway. EXPECT_TRUE(HasFavIcon(new_url_row1.favicon_id())); - EXPECT_TRUE(HasThumbnail(new_url_row1.id())); + // TODO(sky): fix this, see comment in HasThumbnail. + // EXPECT_TRUE(HasThumbnail(new_url_row1.id())); EXPECT_TRUE(HasFavIcon(new_url_row2.favicon_id())); - EXPECT_TRUE(HasThumbnail(new_url_row2.id())); + // TODO(sky): fix this, see comment in HasThumbnail. + // EXPECT_TRUE(HasThumbnail(new_url_row2.id())); } TEST_F(ExpireHistoryTest, ArchiveHistoryBeforeUnstarred) { diff --git a/chrome/browser/history/history.cc b/chrome/browser/history/history.cc index bc5db7d..53a5420 100644 --- a/chrome/browser/history/history.cc +++ b/chrome/browser/history/history.cc @@ -127,12 +127,8 @@ HistoryService::HistoryService() profile_(NULL), backend_loaded_(false), bookmark_service_(NULL), - no_db_(false) { - // Is NULL when running generate_profile. - if (NotificationService::current()) { - registrar_.Add(this, NotificationType::HISTORY_URLS_DELETED, - Source<Profile>(profile_)); - } + no_db_(false), + needs_top_sites_migration_(false) { } HistoryService::HistoryService(Profile* profile) @@ -140,9 +136,13 @@ HistoryService::HistoryService(Profile* profile) profile_(profile), backend_loaded_(false), bookmark_service_(NULL), - no_db_(false) { + no_db_(false), + needs_top_sites_migration_(false) { + DCHECK(profile_); registrar_.Add(this, NotificationType::HISTORY_URLS_DELETED, Source<Profile>(profile_)); + registrar_.Add(this, NotificationType::TEMPLATE_URL_REMOVED, + Source<Profile>(profile_)); } HistoryService::~HistoryService() { @@ -609,30 +609,40 @@ HistoryService::Handle HistoryService::QueryMostVisitedURLs( void HistoryService::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { - if (type != NotificationType::HISTORY_URLS_DELETED) { - NOTREACHED(); + if (!thread_) return; - } - // Update the visited link system for deleted URLs. We will update the - // visited link system for added URLs as soon as we get the add - // notification (we don't have to wait for the backend, which allows us to - // be faster to update the state). - // - // For deleted URLs, we don't typically know what will be deleted since - // delete notifications are by time. We would also like to be more - // respectful of privacy and never tell the user something is gone when it - // isn't. Therefore, we update the delete URLs after the fact. - if (!profile_) - return; // No profile, probably unit testing. - Details<history::URLsDeletedDetails> deleted_details(details); - VisitedLinkMaster* visited_links = profile_->GetVisitedLinkMaster(); - if (!visited_links) - return; // Nobody to update. - if (deleted_details->all_history) - visited_links->DeleteAllURLs(); - else // Delete individual ones. - visited_links->DeleteURLs(deleted_details->urls); + switch (type.value) { + case NotificationType::HISTORY_URLS_DELETED: { + // Update the visited link system for deleted URLs. We will update the + // visited link system for added URLs as soon as we get the add + // notification (we don't have to wait for the backend, which allows us to + // be faster to update the state). + // + // For deleted URLs, we don't typically know what will be deleted since + // delete notifications are by time. We would also like to be more + // respectful of privacy and never tell the user something is gone when it + // isn't. Therefore, we update the delete URLs after the fact. + if (!profile_) + return; // No profile, probably unit testing. + Details<history::URLsDeletedDetails> deleted_details(details); + VisitedLinkMaster* visited_links = profile_->GetVisitedLinkMaster(); + if (!visited_links) + return; // Nobody to update. + if (deleted_details->all_history) + visited_links->DeleteAllURLs(); + else // Delete individual ones. + visited_links->DeleteURLs(deleted_details->urls); + break; + } + + case NotificationType::TEMPLATE_URL_REMOVED: + DeleteAllSearchTermsForKeyword(*(Details<TemplateURLID>(details).ptr())); + break; + + default: + NOTREACHED(); + } } bool HistoryService::Init(const FilePath& history_dir, @@ -673,6 +683,7 @@ bool HistoryService::CanAddURL(const GURL& url) { // typed. Right now, however, these are marked as typed even when triggered // by a shortcut or menu action. if (url.SchemeIs(chrome::kJavaScriptScheme) || + url.SchemeIs(chrome::kChromeDevToolsScheme) || url.SchemeIs(chrome::kChromeUIScheme) || url.SchemeIs(chrome::kViewSourceScheme) || url.SchemeIs(chrome::kChromeInternalScheme)) @@ -733,6 +744,9 @@ void HistoryService::BroadcastNotifications( if (!g_browser_process) return; + if (!thread_) + return; + // The source of all of our notifications is the profile. Note that this // pointer is NULL in unit tests. Source<Profile> source(profile_); @@ -769,12 +783,21 @@ void HistoryService::OnDBLoaded() { NotificationService::current()->Notify(NotificationType::HISTORY_LOADED, Source<Profile>(profile_), Details<HistoryService>(this)); + if (thread_ && profile_ && history::TopSites::IsEnabled()) { + // We don't want to force creation of TopSites. + history::TopSites* ts = profile_->GetTopSitesWithoutCreating(); + if (ts) + ts->HistoryLoaded(); + } } void HistoryService::StartTopSitesMigration() { - if (history::TopSites::IsEnabled()) { - history::TopSites* ts = profile_->GetTopSites(); - ts->StartMigration(); + needs_top_sites_migration_ = true; + if (thread_ && profile_ && history::TopSites::IsEnabled()) { + // We don't want to force creation of TopSites. + history::TopSites* ts = profile_->GetTopSitesWithoutCreating(); + if (ts) + ts->MigrateFromHistory(); } } diff --git a/chrome/browser/history/history.h b/chrome/browser/history/history.h index 83e629e..9d2fd20 100644 --- a/chrome/browser/history/history.h +++ b/chrome/browser/history/history.h @@ -118,6 +118,9 @@ class HistoryService : public CancelableRequestProvider, // it's finished loading. bool BackendLoaded(); + // Returns true if the backend has finished loading. + bool backend_loaded() const { return backend_loaded_; } + // Unloads the backend without actually shutting down the history service. // This can be used to temporarily reduce the browser process' memory // footprint. @@ -405,7 +408,8 @@ class HistoryService : public CancelableRequestProvider, // Implemented by the caller of 'CreateDownload' below, and is called when the // history service has created a new entry for a download in the history db. - typedef Callback2<DownloadCreateInfo, int64>::Type DownloadCreateCallback; + typedef Callback2<const DownloadCreateInfo&, int64>::Type + DownloadCreateCallback; // Begins a history request to create a new persistent entry for a download. // 'info' contains all the download's creation state, and 'callback' runs @@ -515,6 +519,10 @@ class HistoryService : public CancelableRequestProvider, virtual Handle ScheduleDBTask(HistoryDBTask* task, CancelableRequestConsumerBase* consumer); + // Returns true if top sites needs to be migrated out of history into its own + // db. + bool needs_top_sites_migration() const { return needs_top_sites_migration_; } + // Testing ------------------------------------------------------------------- // Designed for unit tests, this passes the given task on to the history @@ -819,7 +827,8 @@ class HistoryService : public CancelableRequestProvider, // when done. We use this internal consumer for this purpose. CancelableRequestConsumer internal_consumer_; - // The thread used by the history service to run complicated operations + // The thread used by the history service to run complicated operations. + // |thread_| is NULL once |Cleanup| is NULL. base::Thread* thread_; // This class has most of the implementation and runs on the 'thread_'. @@ -847,6 +856,9 @@ class HistoryService : public CancelableRequestProvider, BookmarkService* bookmark_service_; bool no_db_; + // True if needs top site migration. + bool needs_top_sites_migration_; + DISALLOW_COPY_AND_ASSIGN(HistoryService); }; diff --git a/chrome/browser/history/history_backend.cc b/chrome/browser/history/history_backend.cc index fb8e3d3..0bf6045 100644 --- a/chrome/browser/history/history_backend.cc +++ b/chrome/browser/history/history_backend.cc @@ -581,12 +581,9 @@ void HistoryBackend::InitImpl(const std::string& languages) { // Thumbnail database. thumbnail_db_.reset(new ThumbnailDatabase()); - if (history::TopSites::IsEnabled()) { - // TODO(sky): once we reenable top sites this needs to be fixed. - // if (!db_->needs_version_18_migration()) { + if (history::TopSites::IsEnabled() && !db_->GetNeedsThumbnailMigration()) { // No convertion needed - use new filename right away. - // thumbnail_name = GetFaviconsFileName(); - // } + thumbnail_name = GetFaviconsFileName(); } if (thumbnail_db_->Init(thumbnail_name, history_publisher_.get()) != sql::INIT_OK) { @@ -599,12 +596,9 @@ void HistoryBackend::InitImpl(const std::string& languages) { thumbnail_db_.reset(); } - if (history::TopSites::IsEnabled()) { - // TODO(sky): fix when reenabling top sites migration. - // if (db_->needs_version_18_migration()) { - // VLOG(1) << "Starting TopSites migration"; - // delegate_->StartTopSitesMigration(); - // } + if (history::TopSites::IsEnabled() && db_->GetNeedsThumbnailMigration()) { + VLOG(1) << "Starting TopSites migration"; + delegate_->StartTopSitesMigration(); } // Archived database. @@ -1041,6 +1035,14 @@ void HistoryBackend::SetKeywordSearchTermsForURL(const GURL& url, } db_->SetKeywordSearchTermsForURL(url_row.id(), keyword_id, term); + + // details is deleted by BroadcastNotifications. + KeywordSearchTermDetails* details = new KeywordSearchTermDetails; + details->url = url; + details->keyword_id = keyword_id; + details->term = term; + BroadcastNotifications(NotificationType::HISTORY_KEYWORD_SEARCH_TERM_UPDATED, + details); ScheduleCommit(); } @@ -1341,6 +1343,16 @@ void HistoryBackend::QueryMostVisitedURLs( } MostVisitedURLList* result = &request->value; + QueryMostVisitedURLsImpl(result_count, days_back, result); + request->ForwardResult(QueryMostVisitedURLsRequest::TupleType( + request->handle(), *result)); +} + +void HistoryBackend::QueryMostVisitedURLsImpl(int result_count, + int days_back, + MostVisitedURLList* result) { + if (!db_.get()) + return; ScopedVector<PageUsageData> data; db_->QuerySegmentUsage(base::Time::Now() - @@ -1354,9 +1366,6 @@ void HistoryBackend::QueryMostVisitedURLs( MostVisitedURL url = MakeMostVisitedURL(*current_data, redirects); result->push_back(url); } - - request->ForwardResult(QueryMostVisitedURLsRequest::TupleType( - request->handle(), *result)); } void HistoryBackend::GetRedirectsFromSpecificVisit( @@ -1516,6 +1525,19 @@ void HistoryBackend::GetPageThumbnailDirectly( } } +void HistoryBackend::MigrateThumbnailsDatabase() { + // If there is no History DB, we can't record that the migration was done. + // It will be recorded on the next run. + if (db_.get()) { + // If there is no thumbnail DB, we can still record a successful migration. + if (thumbnail_db_.get()) { + thumbnail_db_->RenameAndDropThumbnails(GetThumbnailFileName(), + GetFaviconsFileName()); + } + db_->ThumbnailMigrationDone(); + } +} + bool HistoryBackend::GetThumbnailFromOlderRedirect( const GURL& page_url, std::vector<unsigned char>* data) { @@ -2149,17 +2171,4 @@ BookmarkService* HistoryBackend::GetBookmarkService() { return bookmark_service_; } -void HistoryBackend::MigrateThumbnailsDatabase() { - // If there is no History DB, we can't record that the migration was done. - // It will be recorded on the next run. - if (db_.get()) { - // If there is no thumbnail DB, we can still record a successful migration. - if (thumbnail_db_.get()) { - thumbnail_db_->RenameAndDropThumbnails(GetThumbnailFileName(), - GetFaviconsFileName()); - } - db_->MigrationToTopSitesDone(); - } -} - } // namespace history diff --git a/chrome/browser/history/history_backend.h b/chrome/browser/history/history_backend.h index 3a5cea3..54cbf29 100644 --- a/chrome/browser/history/history_backend.h +++ b/chrome/browser/history/history_backend.h @@ -162,6 +162,11 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>, int result_count, int days_back); + // QueryMostVisitedURLs without the request. + void QueryMostVisitedURLsImpl(int result_count, + int days_back, + MostVisitedURLList* result); + // Computes the most recent URL(s) that the given canonical URL has // redirected to and returns true on success. There may be more than one // redirect in a row, so this function will fill the given array with the diff --git a/chrome/browser/history/history_backend_unittest.cc b/chrome/browser/history/history_backend_unittest.cc index 70760bd..ad16e9c 100644 --- a/chrome/browser/history/history_backend_unittest.cc +++ b/chrome/browser/history/history_backend_unittest.cc @@ -139,7 +139,8 @@ class HistoryBackendTest : public testing::Test { backend_->Init(std::string(), false); } virtual void TearDown() { - backend_->Closing(); + if (backend_.get()) + backend_->Closing(); backend_ = NULL; mem_backend_.reset(); file_util::Delete(test_dir_, true); @@ -792,6 +793,8 @@ TEST_F(HistoryBackendTest, RemoveVisitsSource) { // Test for migration of adding visit_source table. TEST_F(HistoryBackendTest, MigrationVisitSource) { ASSERT_TRUE(backend_.get()); + backend_->Closing(); + backend_ = NULL; FilePath old_history_path; ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &old_history_path)); @@ -806,11 +809,12 @@ TEST_F(HistoryBackendTest, MigrationVisitSource) { FilePath new_history_file = new_history_path.Append(chrome::kHistoryFilename); ASSERT_TRUE(file_util::CopyFile(old_history_path, new_history_file)); - backend_->Closing(); backend_ = new HistoryBackend(new_history_path, new HistoryBackendTestDelegate(this), &bookmark_model_); backend_->Init(std::string(), false); + backend_->Closing(); + backend_ = NULL; // Now the database should already be migrated. // Check version first. diff --git a/chrome/browser/history/history_database.cc b/chrome/browser/history/history_database.cc index 56ee054..f0c546c 100644 --- a/chrome/browser/history/history_database.cc +++ b/chrome/browser/history/history_database.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -10,15 +10,16 @@ #include "app/sql/transaction.h" #include "base/command_line.h" #include "base/file_util.h" -#if defined(OS_MACOSX) -#include "base/mac_util.h" -#endif #include "base/metrics/histogram.h" #include "base/rand_util.h" #include "base/string_util.h" #include "chrome/browser/diagnostics/sqlite_diagnostics.h" #include "chrome/common/chrome_switches.h" +#if defined(OS_MACOSX) +#include "base/mac_util.h" +#endif + namespace history { namespace { @@ -26,10 +27,14 @@ namespace { // Current version number. We write databases at the "current" version number, // but any previous version that can read the "compatible" one can make do with // or database without *too* many bad effects. -static const int kCurrentVersionNumber = 19; +static const int kCurrentVersionNumber = 20; static const int kCompatibleVersionNumber = 16; static const char kEarlyExpirationThresholdKey[] = "early_expiration_threshold"; +// Key in the meta table used to determine if we need to migrate thumbnails out +// of history. +static const char kNeedsThumbnailMigrationKey[] = "needs_thumbnail_migration"; + void ComputeDatabaseMetrics(const FilePath& history_name, sql::Connection& db) { if (base::RandInt(1, 100) != 50) @@ -114,6 +119,7 @@ sql::InitStatus HistoryDatabase::Init(const FilePath& history_name, !InitSegmentTables()) return sql::INIT_FAILURE; CreateMainURLIndex(); + CreateKeywordSearchTermsIndices(); CreateSupplimentaryURLIndices(); // Version check. @@ -164,6 +170,7 @@ bool HistoryDatabase::RecreateAllTablesButURL() { // over parts of the URL table that weren't automatically created when the // temporary URL table was CreateSupplimentaryURLIndices(); + CreateKeywordSearchTermsIndices(); return true; } @@ -173,6 +180,16 @@ void HistoryDatabase::Vacuum() { db_.Execute("VACUUM"); } +void HistoryDatabase::ThumbnailMigrationDone() { + meta_table_.SetValue(kNeedsThumbnailMigrationKey, 0); +} + +bool HistoryDatabase::GetNeedsThumbnailMigration() { + int value = 0; + return (meta_table_.GetValue(kNeedsThumbnailMigrationKey, &value) && + value != 0); +} + bool HistoryDatabase::SetSegmentID(VisitID visit_id, SegmentID segment_id) { sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE, "UPDATE visits SET segment_id = ? WHERE id = ?")); @@ -289,6 +306,14 @@ sql::InitStatus HistoryDatabase::EnsureCurrentVersion( meta_table_.SetVersionNumber(cur_version); } + if (cur_version == 19) { + cur_version++; + meta_table_.SetVersionNumber(cur_version); + // Set a key indicating we need to migrate thumbnails. When successfull the + // key is removed (ThumbnailMigrationDone). + meta_table_.SetValue(kNeedsThumbnailMigrationKey, 1); + } + // When the version is too old, we just try to continue anyway, there should // not be a released product that makes a database too old for us to handle. LOG_IF(WARNING, cur_version < GetCurrentVersion()) << @@ -322,8 +347,4 @@ void HistoryDatabase::MigrateTimeEpoch() { } #endif -void HistoryDatabase::MigrationToTopSitesDone() { - // TODO(sky): implement me. -} - } // namespace history diff --git a/chrome/browser/history/history_database.h b/chrome/browser/history/history_database.h index fe74e89..1f0c219 100644 --- a/chrome/browser/history/history_database.h +++ b/chrome/browser/history/history_database.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -123,8 +123,11 @@ class HistoryDatabase : public DownloadDatabase, return needs_version_17_migration_; } - // Update the database version after the TopSites migration. - void MigrationToTopSitesDone(); + // Marks the database as no longer needing migration. + void ThumbnailMigrationDone(); + + // Returns true if thumbnails needs to be migrated. + bool GetNeedsThumbnailMigration(); // Visit table functions ---------------------------------------------------- diff --git a/chrome/browser/history/history_notifications.cc b/chrome/browser/history/history_notifications.cc index c846eaf..0fdfa94 100644 --- a/chrome/browser/history/history_notifications.cc +++ b/chrome/browser/history/history_notifications.cc @@ -28,4 +28,8 @@ FavIconChangeDetails::FavIconChangeDetails() {} FavIconChangeDetails::~FavIconChangeDetails() {} +KeywordSearchTermDetails::KeywordSearchTermDetails() : keyword_id(0) {} + +KeywordSearchTermDetails::~KeywordSearchTermDetails() {} + } // namespace history diff --git a/chrome/browser/history/history_notifications.h b/chrome/browser/history/history_notifications.h index 7a2404c..0d82135 100644 --- a/chrome/browser/history/history_notifications.h +++ b/chrome/browser/history/history_notifications.h @@ -13,6 +13,7 @@ #include "googleurl/src/gurl.h" #include "chrome/browser/history/history_types.h" +#include "chrome/browser/search_engines/template_url_id.h" namespace history { @@ -27,7 +28,7 @@ struct HistoryDetails { // Details for HISTORY_URL_VISITED. struct URLVisitedDetails : public HistoryDetails { URLVisitedDetails(); - ~URLVisitedDetails(); + virtual ~URLVisitedDetails(); PageTransition::Type transition; URLRow row; @@ -42,7 +43,7 @@ struct URLVisitedDetails : public HistoryDetails { // Details for NOTIFY_HISTORY_TYPED_URLS_MODIFIED. struct URLsModifiedDetails : public HistoryDetails { URLsModifiedDetails(); - ~URLsModifiedDetails(); + virtual ~URLsModifiedDetails(); // Lists the information for each of the URLs affected. std::vector<URLRow> changed_urls; @@ -51,7 +52,7 @@ struct URLsModifiedDetails : public HistoryDetails { // Details for NOTIFY_HISTORY_URLS_DELETED. struct URLsDeletedDetails : public HistoryDetails { URLsDeletedDetails(); - ~URLsDeletedDetails(); + virtual ~URLsDeletedDetails(); // Set when all history was deleted. False means just a subset was deleted. bool all_history; @@ -65,7 +66,7 @@ struct URLsDeletedDetails : public HistoryDetails { // Details for NOTIFY_URLS_STARRED. struct URLsStarredDetails : public HistoryDetails { explicit URLsStarredDetails(bool being_starred); - ~URLsStarredDetails(); + virtual ~URLsStarredDetails(); // The new starred state of the list of URLs. True when they are being // starred, false when they are being unstarred. @@ -78,11 +79,21 @@ struct URLsStarredDetails : public HistoryDetails { // Details for NOTIFY_FAVICON_CHANGED. struct FavIconChangeDetails : public HistoryDetails { FavIconChangeDetails(); - ~FavIconChangeDetails(); + virtual ~FavIconChangeDetails(); std::set<GURL> urls; }; +// Details for HISTORY_KEYWORD_SEARCH_TERM_UPDATED. +struct KeywordSearchTermDetails : public HistoryDetails { + KeywordSearchTermDetails(); + ~KeywordSearchTermDetails(); + + GURL url; + TemplateURLID keyword_id; + string16 term; +}; + } // namespace history #endif // CHROME_BROWSER_HISTORY_HISTORY_NOTIFICATIONS_H__ diff --git a/chrome/browser/history/history_types.cc b/chrome/browser/history/history_types.cc index 408bb93..eebe5a7 100644 --- a/chrome/browser/history/history_types.cc +++ b/chrome/browser/history/history_types.cc @@ -366,4 +366,7 @@ HistoryAddPageArgs* HistoryAddPageArgs::Clone() const { visit_source, did_replace_entry); } +MostVisitedThumbnails::MostVisitedThumbnails() { +} + } // namespace history diff --git a/chrome/browser/history/history_types.h b/chrome/browser/history/history_types.h index 831c0b0..536064a 100644 --- a/chrome/browser/history/history_types.h +++ b/chrome/browser/history/history_types.h @@ -544,20 +544,6 @@ struct MostVisitedURL { } }; -// Used by TopSites to store the thumbnails. -struct Images { - Images(); - ~Images(); - - scoped_refptr<RefCountedBytes> thumbnail; - ThumbnailScore thumbnail_score; - - // TODO(brettw): this will eventually store the favicon. - // scoped_refptr<RefCountedBytes> favicon; -}; - -typedef std::vector<MostVisitedURL> MostVisitedURLList; - // Navigation ----------------------------------------------------------------- // Marshalling structure for AddPage. @@ -598,6 +584,61 @@ class HistoryAddPageArgs DISALLOW_COPY_AND_ASSIGN(HistoryAddPageArgs); }; +// TopSites ------------------------------------------------------------------- + +typedef std::vector<MostVisitedURL> MostVisitedURLList; + +// Used by TopSites to store the thumbnails. +struct Images { + Images(); + ~Images(); + + scoped_refptr<RefCountedBytes> thumbnail; + ThumbnailScore thumbnail_score; + + // TODO(brettw): this will eventually store the favicon. + // scoped_refptr<RefCountedBytes> favicon; +}; + +typedef std::vector<MostVisitedURL> MostVisitedURLList; + +struct MostVisitedURLWithRank { + MostVisitedURL url; + int rank; +}; + +typedef std::vector<MostVisitedURLWithRank> MostVisitedURLWithRankList; + +struct TopSitesDelta { + MostVisitedURLList deleted; + MostVisitedURLWithRankList added; + MostVisitedURLWithRankList moved; +}; + +typedef std::map<GURL, scoped_refptr<RefCountedBytes> > URLToThumbnailMap; + +// Used when migrating most visited thumbnails out of history and into topsites. +struct ThumbnailMigration { + MostVisitedURLList most_visited; + URLToThumbnailMap url_to_thumbnail_map; +}; + +typedef std::map<GURL, Images> URLToImagesMap; + +class MostVisitedThumbnails + : public base::RefCountedThreadSafe<MostVisitedThumbnails> { + public: + MostVisitedThumbnails(); + + MostVisitedURLList most_visited; + URLToImagesMap url_to_images_map; + + private: + friend class base::RefCountedThreadSafe<MostVisitedThumbnails>; + + DISALLOW_COPY_AND_ASSIGN(MostVisitedThumbnails); +}; + } // namespace history #endif // CHROME_BROWSER_HISTORY_HISTORY_TYPES_H_ diff --git a/chrome/browser/history/in_memory_database.cc b/chrome/browser/history/in_memory_database.cc index 3227a7a..168a544 100644 --- a/chrome/browser/history/in_memory_database.cc +++ b/chrome/browser/history/in_memory_database.cc @@ -41,6 +41,13 @@ bool InMemoryDatabase::InitDB() { return false; } + // Create the keyword search terms table. + if (!InitKeywordSearchTermsTable()) { + NOTREACHED() << "Unable to create keyword search terms"; + db_.Close(); + return false; + } + return true; } @@ -51,6 +58,7 @@ bool InMemoryDatabase::InitFromScratch() { // InitDB doesn't create the index so in the disk-loading case, it can be // added afterwards. CreateMainURLIndex(); + CreateKeywordSearchTermsIndices(); return true; } @@ -87,6 +95,36 @@ bool InMemoryDatabase::InitFromDisk(const FilePath& history_name) { end_load - begin_load); UMA_HISTOGRAM_COUNTS("History.InMemoryDBItemCount", db_.GetLastChangeCount()); + // Insert keyword search related URLs. + begin_load = base::TimeTicks::Now(); + if (!db_.Execute( + "INSERT INTO urls SELECT u.id, u.url, u.title, u.visit_count, " + "u.typed_count, u.last_visit_time, u.hidden, u.favicon_id " + "FROM history.urls u JOIN history.keyword_search_terms kst " + "WHERE u.typed_count = 0 AND u.id = kst.url_id")) { + // Unable to get data from the history database. This is OK, the file may + // just not exist yet. + } + end_load = base::TimeTicks::Now(); + UMA_HISTOGRAM_MEDIUM_TIMES("History.InMemoryDBKeywordURLPopulate", + end_load - begin_load); + UMA_HISTOGRAM_COUNTS("History.InMemoryDBKeywordURLItemCount", + db_.GetLastChangeCount()); + + // Copy search terms to memory. + begin_load = base::TimeTicks::Now(); + if (!db_.Execute( + "INSERT INTO keyword_search_terms SELECT * FROM " + "history.keyword_search_terms")) { + // Unable to get data from the history database. This is OK, the file may + // just not exist yet. + } + end_load = base::TimeTicks::Now(); + UMA_HISTOGRAM_MEDIUM_TIMES("History.InMemoryDBKeywordTermsPopulate", + end_load - begin_load); + UMA_HISTOGRAM_COUNTS("History.InMemoryDBKeywordTermsCount", + db_.GetLastChangeCount()); + // Detach from the history database on disk. if (!db_.Execute("DETACH history")) { NOTREACHED() << "Unable to detach from history database."; @@ -96,6 +134,7 @@ bool InMemoryDatabase::InitFromDisk(const FilePath& history_name) { // Index the table, this is faster than creating the index first and then // inserting into it. CreateMainURLIndex(); + CreateKeywordSearchTermsIndices(); return true; } diff --git a/chrome/browser/history/in_memory_history_backend.cc b/chrome/browser/history/in_memory_history_backend.cc index 6827a78..be82f58 100644 --- a/chrome/browser/history/in_memory_history_backend.cc +++ b/chrome/browser/history/in_memory_history_backend.cc @@ -72,6 +72,9 @@ void InMemoryHistoryBackend::AttachToHistoryService(Profile* profile) { registrar_.Add(this, NotificationType::HISTORY_URL_VISITED, source); registrar_.Add(this, NotificationType::HISTORY_TYPED_URLS_MODIFIED, source); registrar_.Add(this, NotificationType::HISTORY_URLS_DELETED, source); + registrar_.Add(this, NotificationType::HISTORY_KEYWORD_SEARCH_TERM_UPDATED, + source); + registrar_.Add(this, NotificationType::TEMPLATE_URL_REMOVED, source); } void InMemoryHistoryBackend::Observe(NotificationType type, @@ -80,13 +83,20 @@ void InMemoryHistoryBackend::Observe(NotificationType type, switch (type.value) { case NotificationType::HISTORY_URL_VISITED: { Details<history::URLVisitedDetails> visited_details(details); - if (visited_details->row.typed_count() > 0) { + PageTransition::Type primary_type = + PageTransition::StripQualifier(visited_details->transition); + if (visited_details->row.typed_count() > 0 || + primary_type == PageTransition::KEYWORD) { URLsModifiedDetails modified_details; modified_details.changed_urls.push_back(visited_details->row); OnTypedURLsModified(modified_details); } break; } + case NotificationType::HISTORY_KEYWORD_SEARCH_TERM_UPDATED: + OnKeywordSearchTermUpdated( + *Details<history::KeywordSearchTermDetails>(details).ptr()); + break; case NotificationType::HISTORY_TYPED_URLS_MODIFIED: OnTypedURLsModified( *Details<history::URLsModifiedDetails>(details).ptr()); @@ -94,6 +104,10 @@ void InMemoryHistoryBackend::Observe(NotificationType type, case NotificationType::HISTORY_URLS_DELETED: OnURLsDeleted(*Details<history::URLsDeletedDetails>(details).ptr()); break; + case NotificationType::TEMPLATE_URL_REMOVED: + db_->DeleteAllSearchTermsForKeyword( + *(Details<TemplateURLID>(details).ptr())); + break; default: // For simplicity, the unit tests send us all notifications, even when // we haven't registered for them, so don't assert here. @@ -145,4 +159,27 @@ void InMemoryHistoryBackend::OnURLsDeleted(const URLsDeletedDetails& details) { } } +void InMemoryHistoryBackend::OnKeywordSearchTermUpdated( + const KeywordSearchTermDetails& details) { + // The url won't exist for new search terms (as the user hasn't typed it), so + // we force it to be added. If we end up adding a URL it won't be + // autocompleted as the typed count is 0. + URLRow url_row; + URLID url_id; + if (!db_->GetRowForURL(details.url, &url_row)) { + // Because this row won't have a typed count the title and other stuff + // doesn't matter. If the user ends up typing the url we'll update the title + // in OnTypedURLsModified. + URLRow new_row(details.url); + new_row.set_last_visit(base::Time::Now()); + url_id = db_->AddURL(new_row); + if (!url_id) + return; // Error adding. + } else { + url_id = url_row.id(); + } + + db_->SetKeywordSearchTermsForURL(url_id, details.keyword_id, details.term); +} + } // namespace history diff --git a/chrome/browser/history/in_memory_history_backend.h b/chrome/browser/history/in_memory_history_backend.h index 4fdc3a5..c775800 100644 --- a/chrome/browser/history/in_memory_history_backend.h +++ b/chrome/browser/history/in_memory_history_backend.h @@ -30,6 +30,7 @@ namespace history { class InMemoryDatabase; class InMemoryURLIndex; +struct KeywordSearchTermDetails; class URLDatabase; struct URLsDeletedDetails; struct URLsModifiedDetails; @@ -73,6 +74,9 @@ class InMemoryHistoryBackend : public NotificationObserver { // Handler for NOTIFY_HISTORY_URLS_DELETED. void OnURLsDeleted(const URLsDeletedDetails& details); + // Handler for HISTORY_KEYWORD_SEARCH_TERM_UPDATED. + void OnKeywordSearchTermUpdated(const KeywordSearchTermDetails& details); + NotificationRegistrar registrar_; scoped_ptr<InMemoryDatabase> db_; diff --git a/chrome/browser/history/in_memory_url_index.cc b/chrome/browser/history/in_memory_url_index.cc index 654afdc..98c2acd 100644 --- a/chrome/browser/history/in_memory_url_index.cc +++ b/chrome/browser/history/in_memory_url_index.cc @@ -34,7 +34,9 @@ ScoredHistoryMatch::ScoredHistoryMatch(const URLRow& url_info, } struct InMemoryURLIndex::TermCharWordSet { - TermCharWordSet(Char16Set char_set, WordIDSet word_id_set, bool used) + TermCharWordSet(const Char16Set& char_set, + const WordIDSet& word_id_set, + bool used) : char_set_(char_set), word_id_set_(word_id_set), used_(used) {} @@ -81,7 +83,7 @@ bool InMemoryURLIndex::Init(history::URLDatabase* history_db, return true; } -bool InMemoryURLIndex::IndexRow(URLRow row) { +bool InMemoryURLIndex::IndexRow(const URLRow& row) { const GURL& gurl(row.url()); string16 url(net::FormatUrl(gurl, languages_, net::kFormatUrlOmitUsernamePassword, diff --git a/chrome/browser/history/in_memory_url_index.h b/chrome/browser/history/in_memory_url_index.h index a2ac0f3..81336f1 100644 --- a/chrome/browser/history/in_memory_url_index.h +++ b/chrome/browser/history/in_memory_url_index.h @@ -159,7 +159,7 @@ class InMemoryURLIndex { // URL History indexing support functions. // Index one URL history item. - bool IndexRow(URLRow row); + bool IndexRow(const URLRow& row); // Break a string down into its individual characters. // Note that this is temporarily intended to work on a single word, but diff --git a/chrome/browser/history/text_database.cc b/chrome/browser/history/text_database.cc index 84367d3..60aa7fd 100644 --- a/chrome/browser/history/text_database.cc +++ b/chrome/browser/history/text_database.cc @@ -112,8 +112,8 @@ TextDatabase::DBIdent TextDatabase::FileNameToID(const FilePath& file_path) { } int year, month; - base::StringToInt(suffix.substr(0, 4), &year); - base::StringToInt(suffix.substr(5, 2), &month); + base::StringToInt(suffix.begin(), suffix.begin() + 4, &year); + base::StringToInt(suffix.begin() + 5, suffix.begin() + 7, &month); return year * 100 + month; } diff --git a/chrome/browser/history/thumbnail_database.cc b/chrome/browser/history/thumbnail_database.cc index 9cd3f9c..e1054ac 100644 --- a/chrome/browser/history/thumbnail_database.cc +++ b/chrome/browser/history/thumbnail_database.cc @@ -104,21 +104,10 @@ sql::InitStatus ThumbnailDatabase::OpenDatabase(sql::Connection* db, // Set the exceptional sqlite error handler. db->set_error_delegate(GetErrorHandlerForThumbnailDb()); - // Set the database page size to something larger to give us - // better performance (we're typically seek rather than bandwidth limited). - // This only has an effect before any tables have been created, otherwise - // this is a NOP. Must be a power of 2 and a max of 8192. We use a bigger - // one because we're storing larger data (4-16K) in it, so we want a few - // blocks per element. - db->set_page_size(4096); - - // The UI is generally designed to work well when the thumbnail database is - // slow, so we can tolerate much less caching. The file is also very large - // and so caching won't save a significant percentage of it for us, - // reducing the benefit of caching in the first place. With the default cache - // size of 2000 pages, it will take >8MB of memory, so reducing it can be a - // big savings. - db->set_cache_size(64); + // Thumbnails db now only stores favicons, so we don't need that big a page + // size or cache. + db->set_page_size(2048); + db->set_cache_size(32); // Run the database in exclusive mode. Nobody else should be accessing the // database while we're running, and this will give somewhat improved perf. @@ -310,7 +299,6 @@ bool ThumbnailDatabase::GetPageThumbnail(URLID id, bool ThumbnailDatabase::DeleteThumbnail(URLID id) { if (use_top_sites_) { - LOG(WARNING) << "Use TopSites instead."; return true; // Not possible after migration to TopSites. } diff --git a/chrome/browser/history/top_sites.cc b/chrome/browser/history/top_sites.cc index 31ce4ac..8bdfe34 100644 --- a/chrome/browser/history/top_sites.cc +++ b/chrome/browser/history/top_sites.cc @@ -5,10 +5,10 @@ #include "chrome/browser/history/top_sites.h" #include <algorithm> +#include <set> #include "app/l10n_util.h" #include "base/command_line.h" -#include "base/file_util.h" #include "base/logging.h" #include "base/md5.h" #include "base/string_util.h" @@ -16,14 +16,17 @@ #include "base/values.h" #include "chrome/browser/browser_thread.h" #include "chrome/browser/dom_ui/most_visited_handler.h" +#include "chrome/browser/history/history_backend.h" #include "chrome/browser/history/history_notifications.h" #include "chrome/browser/history/page_usage_data.h" -#include "chrome/browser/history/top_sites_database.h" +#include "chrome/browser/history/top_sites_backend.h" +#include "chrome/browser/history/top_sites_cache.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profile.h" #include "chrome/browser/tab_contents/navigation_controller.h" #include "chrome/browser/tab_contents/navigation_entry.h" #include "chrome/common/chrome_switches.h" +#include "chrome/common/notification_service.h" #include "chrome/common/pref_names.h" #include "chrome/common/thumbnail_score.h" #include "gfx/codec/jpeg_codec.h" @@ -36,6 +39,11 @@ namespace history { // How many top sites to store in the cache. static const size_t kTopSitesNumber = 20; + +// Max number of temporary images we'll cache. See comment above +// temp_images_ for details. +static const size_t kMaxTempTopImages = 8; + static const size_t kTopSitesShown = 8; static const int kDaysOfHistory = 90; // Time from startup to first HistoryService query. @@ -44,14 +52,91 @@ static const int64 kUpdateIntervalSecs = 15; static const int64 kMinUpdateIntervalMinutes = 1; static const int64 kMaxUpdateIntervalMinutes = 60; +// IDs of the sites we force into top sites. +static const int kPrepopulatePageIDs[] = + { IDS_CHROME_WELCOME_URL, IDS_THEMES_GALLERY_URL }; + +// Favicons of the sites we force into top sites. +static const char kPrepopulateFaviconURLs[][54] = + { "chrome://theme/IDR_NEWTAB_CHROME_WELCOME_PAGE_FAVICON", + "chrome://theme/IDR_NEWTAB_THEMES_GALLERY_FAVICON" }; + +static const int kPrepopulateTitleIDs[] = + { IDS_NEW_TAB_CHROME_WELCOME_PAGE_TITLE, + IDS_NEW_TAB_THEMES_GALLERY_PAGE_TITLE }; + +namespace { + +// HistoryDBTask used during migration of thumbnails from history to top sites. +// When run on the history thread it collects the top sites and the +// corresponding thumbnails. When run back on the ui thread it calls into +// TopSites::FinishHistoryMigration. +class LoadThumbnailsFromHistoryTask : public HistoryDBTask { + public: + LoadThumbnailsFromHistoryTask(TopSites* top_sites, + int result_count) + : top_sites_(top_sites), + result_count_(result_count) { + // l10n_util isn't thread safe, so cache for use on the db thread. + ignore_urls_.insert(l10n_util::GetStringUTF8(IDS_CHROME_WELCOME_URL)); + ignore_urls_.insert(l10n_util::GetStringUTF8(IDS_THEMES_GALLERY_URL)); + } + + virtual bool RunOnDBThread(history::HistoryBackend* backend, + history::HistoryDatabase* db) { + // Get the most visited urls. + backend->QueryMostVisitedURLsImpl(result_count_, + kDaysOfHistory, + &data_.most_visited); + + // And fetch the thumbnails. + for (size_t i = 0; i < data_.most_visited.size(); ++i) { + const GURL& url = data_.most_visited[i].url; + if (ShouldFetchThumbnailFor(url)) { + scoped_refptr<RefCountedBytes> data; + backend->GetPageThumbnailDirectly(url, &data); + data_.url_to_thumbnail_map[url] = data; + } + } + return true; + } + + virtual void DoneRunOnMainThread() { + top_sites_->FinishHistoryMigration(data_); + } + + private: + bool ShouldFetchThumbnailFor(const GURL& url) { + return ignore_urls_.find(url.spec()) == ignore_urls_.end(); + } + + // Set of URLs we don't load thumbnails for. This is created on the UI thread + // and used on the history thread. + std::set<std::string> ignore_urls_; + + scoped_refptr<TopSites> top_sites_; + + // Number of results to request from history. + const int result_count_; + + ThumbnailMigration data_; + + DISALLOW_COPY_AND_ASSIGN(LoadThumbnailsFromHistoryTask); +}; + +} // namespace -TopSites::TopSites(Profile* profile) : profile_(profile), - mock_history_service_(NULL), - last_num_urls_changed_(0), - migration_in_progress_(false), - waiting_for_results_(true), - blacklist_(NULL), - pinned_urls_(NULL) { +TopSites::TopSites(Profile* profile) + : backend_(new TopSitesBackend()), + cache_(new TopSitesCache()), + thread_safe_cache_(new TopSitesCache()), + profile_(profile), + last_num_urls_changed_(0), + blacklist_(NULL), + pinned_urls_(NULL), + history_state_(HISTORY_LOADING), + top_sites_state_(TOP_SITES_LOADING), + loaded_(false) { if (!profile_) return; @@ -70,61 +155,42 @@ TopSites::TopSites(Profile* profile) : profile_(profile), // static bool TopSites::IsEnabled() { - return CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableTopSites); -} - -TopSites::~TopSites() { - timer_.Stop(); + std::string switch_value = + CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kEnableTopSites); + return switch_value.empty() || switch_value == "true"; } void TopSites::Init(const FilePath& db_name) { - db_path_ = db_name; - db_.reset(new TopSitesDatabaseImpl()); - if (!db_->Init(db_name)) { - NOTREACHED() << "Failed to initialize database."; - return; - } + backend_->Init(db_name); + backend_->GetMostVisitedThumbnails( + &cancelable_consumer_, + NewCallback(this, &TopSites::OnGotMostVisitedThumbnails)); - BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, NewRunnableMethod( - this, &TopSites::ReadDatabase)); - - // Start the one-shot timer. - timer_.Start(base::TimeDelta::FromSeconds(kUpdateIntervalSecs), this, - &TopSites::StartQueryForMostVisited); -} - -void TopSites::ReadDatabase() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); - std::map<GURL, Images> thumbnails; - - DCHECK(db_.get()); - MostVisitedURLList top_urls; - db_->GetPageThumbnails(&top_urls, &thumbnails); - MostVisitedURLList copy(top_urls); // StoreMostVisited destroys the list. - StoreMostVisited(&top_urls); - if (AddPrepopulatedPages(©)) - UpdateMostVisited(copy); - - AutoLock lock(lock_); - for (size_t i = 0; i < top_sites_.size(); i++) { - GURL url = top_sites_[i].url; - Images thumbnail = thumbnails[url]; - if (thumbnail.thumbnail.get() && thumbnail.thumbnail->size()) { - SetPageThumbnailNoDB(url, thumbnail.thumbnail, - thumbnail.thumbnail_score); - } + // History may have already finished loading by the time we're created. + HistoryService* history = profile_->GetHistoryServiceWithoutCreating(); + if (history && history->backend_loaded()) { + if (history->needs_top_sites_migration()) + MigrateFromHistory(); + else + history_state_ = HISTORY_LOADED; } } -// Public function that encodes the bitmap into RefCountedBytes and -// updates the database. bool TopSites::SetPageThumbnail(const GURL& url, const SkBitmap& thumbnail, const ThumbnailScore& score) { - AutoLock lock(lock_); + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + if (!loaded_) { + // TODO(sky): I need to cache these and apply them after the load + // completes. + return false; + } + bool add_temp_thumbnail = false; - if (canonical_urls_.find(url) == canonical_urls_.end()) { - if (top_sites_.size() < kTopSitesNumber) { + if (!cache_->IsKnownURL(url)) { + if (cache_->top_sites().size() < kTopSitesNumber) { add_temp_thumbnail = true; } else { return false; // This URL is not known to us. @@ -134,18 +200,14 @@ bool TopSites::SetPageThumbnail(const GURL& url, if (!HistoryService::CanAddURL(url)) return false; // It's not a real webpage. - scoped_refptr<RefCountedBytes> thumbnail_data = new RefCountedBytes; - SkAutoLockPixels thumbnail_lock(thumbnail); - bool encoded = gfx::JPEGCodec::Encode( - reinterpret_cast<unsigned char*>(thumbnail.getAddr32(0, 0)), - gfx::JPEGCodec::FORMAT_SkBitmap, thumbnail.width(), - thumbnail.height(), - static_cast<int>(thumbnail.rowBytes()), 90, - &thumbnail_data->data); - if (!encoded) + scoped_refptr<RefCountedBytes> thumbnail_data; + if (!EncodeBitmap(thumbnail, &thumbnail_data)) return false; if (add_temp_thumbnail) { + // Always remove the existing entry and then add it back. That way if we end + // up with too many temp thumbnails we'll prune the oldest first. + RemoveTemporaryThumbnailByURL(url); AddTemporaryThumbnail(url, thumbnail_data, score); return true; } @@ -153,59 +215,243 @@ bool TopSites::SetPageThumbnail(const GURL& url, return SetPageThumbnailEncoded(url, thumbnail_data, score); } -bool TopSites::SetPageThumbnailEncoded(const GURL& url, - const RefCountedBytes* thumbnail, - const ThumbnailScore& score) { - lock_.AssertAcquired(); - if (!SetPageThumbnailNoDB(url, thumbnail, score)) - return false; +void TopSites::GetMostVisitedURLs(CancelableRequestConsumer* consumer, + GetTopSitesCallback* callback) { + // WARNING: this may be invoked on any thread. + scoped_refptr<CancelableRequest<GetTopSitesCallback> > request( + new CancelableRequest<GetTopSitesCallback>(callback)); + // This ensures cancelation of requests when either the consumer or the + // provider is deleted. Deletion of requests is also guaranteed. + AddRequest(request, consumer); + MostVisitedURLList filtered_urls; + { + AutoLock lock(lock_); + if (!loaded_) { + // A request came in before we finished loading. Put the request in + // pending_callbacks_ and we'll notify it when we finish loading. + pending_callbacks_.insert(request); + return; + } - // Update the database. - if (!db_.get()) - return true; - std::map<GURL, size_t>::iterator found = canonical_urls_.find(url); - if (found == canonical_urls_.end()) - return false; - size_t index = found->second; + filtered_urls = thread_safe_cache_->top_sites(); + } + request->ForwardResult(GetTopSitesCallback::TupleType(filtered_urls)); +} - MostVisitedURL& most_visited = top_sites_[index]; - BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, NewRunnableMethod( - this, &TopSites::WriteThumbnailToDB, - most_visited, index, top_images_[most_visited.url])); - return true; +bool TopSites::GetPageThumbnail(const GURL& url, + scoped_refptr<RefCountedBytes>* bytes) { + // WARNING: this may be invoked on any thread. + AutoLock lock(lock_); + return thread_safe_cache_->GetPageThumbnail(url, bytes); +} + +// Returns the index of |url| in |urls|, or -1 if not found. +static int IndexOf(const MostVisitedURLList& urls, const GURL& url) { + for (size_t i = 0; i < urls.size(); i++) { + if (urls[i].url == url) + return i; + } + return -1; } -void TopSites::WriteThumbnailToDB(const MostVisitedURL& url, - int url_rank, - const Images& thumbnail) { - DCHECK(db_.get()); - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); - db_->SetPageThumbnail(url, url_rank, thumbnail); +void TopSites::MigrateFromHistory() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK_EQ(history_state_, HISTORY_LOADING); + + history_state_ = HISTORY_MIGRATING; + profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)->ScheduleDBTask( + new LoadThumbnailsFromHistoryTask( + this, + num_results_to_request_from_history()), + &cancelable_consumer_); + MigratePinnedURLs(); } -// private -bool TopSites::SetPageThumbnailNoDB(const GURL& url, - const RefCountedBytes* thumbnail_data, - const ThumbnailScore& score) { - lock_.AssertAcquired(); +void TopSites::FinishHistoryMigration(const ThumbnailMigration& data) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK_EQ(history_state_, HISTORY_MIGRATING); - std::map<GURL, size_t>::iterator found = canonical_urls_.find(url); - if (found == canonical_urls_.end()) { - if (top_sites_.size() >= kTopSitesNumber) - return false; // This URL is not known to us. + history_state_ = HISTORY_LOADED; - // We don't have enough Top Sites - add this one too. - MostVisitedURL mv; - mv.url = url; - mv.redirects.push_back(url); - top_sites_.push_back(mv); - size_t index = top_sites_.size() - 1; - StoreRedirectChain(top_sites_[index].redirects, index); - found = canonical_urls_.find(url); + SetTopSites(data.most_visited); + + for (size_t i = 0; i < data.most_visited.size(); ++i) { + URLToThumbnailMap::const_iterator image_i = + data.url_to_thumbnail_map.find(data.most_visited[i].url); + if (image_i != data.url_to_thumbnail_map.end()) { + SetPageThumbnailEncoded(data.most_visited[i].url, + image_i->second, + ThumbnailScore()); + } } - MostVisitedURL& most_visited = top_sites_[found->second]; - Images& image = top_images_[most_visited.url]; + MoveStateToLoaded(); + + ResetThreadSafeImageCache(); + + // We've scheduled all the thumbnails and top sites to be written to the top + // sites db, but it hasn't happened yet. Schedule a request on the db thread + // that notifies us when done. When done we'll know everything was written and + // we can tell history to finish its part of migration. + backend_->DoEmptyRequest( + &cancelable_consumer_, + NewCallback(this, &TopSites::OnHistoryMigrationWrittenToDisk)); +} + +void TopSites::HistoryLoaded() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK_NE(history_state_, HISTORY_LOADED); + + if (history_state_ != HISTORY_MIGRATING) { + // No migration from history is needed. + history_state_ = HISTORY_LOADED; + if (top_sites_state_ == TOP_SITES_LOADED_WAITING_FOR_HISTORY) { + // TopSites thought it needed migration, but it really didn't. This + // typically happens the first time a profile is run with Top Sites + // enabled + SetTopSites(MostVisitedURLList()); + MoveStateToLoaded(); + } + } +} + +bool TopSites::HasBlacklistedItems() const { + return !blacklist_->empty(); +} + +void TopSites::AddBlacklistedURL(const GURL& url) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + RemovePinnedURL(url); + Value* dummy = Value::CreateNullValue(); + blacklist_->SetWithoutPathExpansion(GetURLHash(url), dummy); + + ResetThreadSafeCache(); +} + +void TopSites::RemoveBlacklistedURL(const GURL& url) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + blacklist_->RemoveWithoutPathExpansion(GetURLHash(url), NULL); + ResetThreadSafeCache(); +} + +bool TopSites::IsBlacklisted(const GURL& url) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + return blacklist_->HasKey(GetURLHash(url)); +} + +void TopSites::ClearBlacklistedURLs() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + blacklist_->Clear(); + ResetThreadSafeCache(); +} + +void TopSites::AddPinnedURL(const GURL& url, size_t pinned_index) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + GURL old; + if (GetPinnedURLAtIndex(pinned_index, &old)) + RemovePinnedURL(old); + + if (IsURLPinned(url)) + RemovePinnedURL(url); + + Value* index = Value::CreateIntegerValue(pinned_index); + pinned_urls_->SetWithoutPathExpansion(GetURLString(url), index); + + ResetThreadSafeCache(); +} + +bool TopSites::IsURLPinned(const GURL& url) { + int tmp; + return pinned_urls_->GetIntegerWithoutPathExpansion(GetURLString(url), &tmp); +} + +void TopSites::RemovePinnedURL(const GURL& url) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + pinned_urls_->RemoveWithoutPathExpansion(GetURLString(url), NULL); + + ResetThreadSafeCache(); +} + +bool TopSites::GetPinnedURLAtIndex(size_t index, GURL* url) { + for (DictionaryValue::key_iterator it = pinned_urls_->begin_keys(); + it != pinned_urls_->end_keys(); ++it) { + int current_index; + if (pinned_urls_->GetIntegerWithoutPathExpansion(*it, ¤t_index)) { + if (static_cast<size_t>(current_index) == index) { + *url = GURL(*it); + return true; + } + } + } + return false; +} + +void TopSites::Shutdown() { + profile_ = NULL; + // Cancel all requests so that the service doesn't callback to us after we've + // invoked Shutdown (this could happen if we have a pending request and + // Shutdown is invoked). + cancelable_consumer_.CancelAllRequests(); + backend_->Shutdown(); +} + +// static +void TopSites::DiffMostVisited(const MostVisitedURLList& old_list, + const MostVisitedURLList& new_list, + TopSitesDelta* delta) { + // Add all the old URLs for quick lookup. This maps URLs to the corresponding + // index in the input. + std::map<GURL, size_t> all_old_urls; + for (size_t i = 0; i < old_list.size(); i++) + all_old_urls[old_list[i].url] = i; + + // Check all the URLs in the new set to see which ones are new or just moved. + // When we find a match in the old set, we'll reset its index to our special + // marker. This allows us to quickly identify the deleted ones in a later + // pass. + const size_t kAlreadyFoundMarker = static_cast<size_t>(-1); + for (size_t i = 0; i < new_list.size(); i++) { + std::map<GURL, size_t>::iterator found = all_old_urls.find(new_list[i].url); + if (found == all_old_urls.end()) { + MostVisitedURLWithRank added; + added.url = new_list[i]; + added.rank = i; + delta->added.push_back(added); + } else { + if (found->second != i) { + MostVisitedURLWithRank moved; + moved.url = new_list[i]; + moved.rank = i; + delta->moved.push_back(moved); + } + found->second = kAlreadyFoundMarker; + } + } + + // Any member without the special marker in the all_old_urls list means that + // there wasn't a "new" URL that mapped to it, so it was deleted. + for (std::map<GURL, size_t>::const_iterator i = all_old_urls.begin(); + i != all_old_urls.end(); ++i) { + if (i->second != kAlreadyFoundMarker) + delta->deleted.push_back(old_list[i->second]); + } +} + +TopSites::~TopSites() { +} + +bool TopSites::SetPageThumbnailNoDB(const GURL& url, + const RefCountedBytes* thumbnail_data, + const ThumbnailScore& score) { + // This should only be invoked when we know about the url. + DCHECK(cache_->IsKnownURL(url)); + + const MostVisitedURL& most_visited = + cache_->top_sites()[cache_->GetURLIndex(url)]; + Images* image = cache_->GetImage(url); // When comparing the thumbnail scores, we need to take into account the // redirect hops, which are not generated when the thumbnail is because the @@ -214,95 +460,136 @@ bool TopSites::SetPageThumbnailNoDB(const GURL& url, new_score_with_redirects.redirect_hops_from_dest = GetRedirectDistanceForURL(most_visited, url); - if (!ShouldReplaceThumbnailWith(image.thumbnail_score, + if (!ShouldReplaceThumbnailWith(image->thumbnail_score, new_score_with_redirects) && - image.thumbnail.get()) + image->thumbnail.get()) return false; // The one we already have is better. - // Take ownership of the thumbnail data. - image.thumbnail = const_cast<RefCountedBytes*>(thumbnail_data); - image.thumbnail_score = new_score_with_redirects; + image->thumbnail = const_cast<RefCountedBytes*>(thumbnail_data); + image->thumbnail_score = new_score_with_redirects; + ResetThreadSafeImageCache(); return true; } -void TopSites::GetMostVisitedURLs(CancelableRequestConsumer* consumer, - GetTopSitesCallback* callback) { - scoped_refptr<CancelableRequest<GetTopSitesCallback> > request( - new CancelableRequest<GetTopSitesCallback>(callback)); - // This ensures cancelation of requests when either the consumer or the - // provider is deleted. Deletion of requests is also guaranteed. - AddRequest(request, consumer); - MostVisitedURLList filtered_urls; - { - AutoLock lock(lock_); - if (waiting_for_results_) { - // A request came in before we have any top sites. - // We have to keep track of the requests ourselves. - pending_callbacks_.insert(request); +bool TopSites::SetPageThumbnailEncoded(const GURL& url, + const RefCountedBytes* thumbnail, + const ThumbnailScore& score) { + if (!SetPageThumbnailNoDB(url, thumbnail, score)) + return false; + + // Update the database. + if (!cache_->IsKnownURL(url)) + return false; + + size_t index = cache_->GetURLIndex(url); + const MostVisitedURL& most_visited = cache_->top_sites()[index]; + backend_->SetPageThumbnail(most_visited, + index, + *(cache_->GetImage(most_visited.url))); + return true; +} + +// static +bool TopSites::EncodeBitmap(const SkBitmap& bitmap, + scoped_refptr<RefCountedBytes>* bytes) { + *bytes = new RefCountedBytes(); + SkAutoLockPixels bitmap_lock(bitmap); + std::vector<unsigned char> data; + if (!gfx::JPEGCodec::Encode( + reinterpret_cast<unsigned char*>(bitmap.getAddr32(0, 0)), + gfx::JPEGCodec::FORMAT_BGRA, bitmap.width(), + bitmap.height(), + static_cast<int>(bitmap.rowBytes()), 90, + &data)) { + return false; + } + // As we're going to cache this data, make sure the vector is only as big as + // it needs to be. + (*bytes)->data = data; + return true; +} + +void TopSites::RemoveTemporaryThumbnailByURL(const GURL& url) { + for (TempImages::iterator i = temp_images_.begin(); i != temp_images_.end(); + ++i) { + if (i->first == url) { + temp_images_.erase(i); return; } - if (request->canceled()) - return; - - ApplyBlacklistAndPinnedURLs(top_sites_, &filtered_urls); } - request->ForwardResult(GetTopSitesCallback::TupleType(filtered_urls)); } -bool TopSites::GetPageThumbnail(const GURL& url, RefCountedBytes** data) const { - AutoLock lock(lock_); - std::map<GURL, Images>::const_iterator found = - top_images_.find(GetCanonicalURL(url)); - if (found == top_images_.end()) { - found = temp_thumbnails_map_.find(url); - if (found == temp_thumbnails_map_.end()) - return false; // No thumbnail for this URL. +void TopSites::AddTemporaryThumbnail(const GURL& url, + const RefCountedBytes* thumbnail, + const ThumbnailScore& score) { + if (temp_images_.size() == kMaxTempTopImages) + temp_images_.erase(temp_images_.begin()); + + TempImage image; + image.first = url; + image.second.thumbnail = const_cast<RefCountedBytes*>(thumbnail); + image.second.thumbnail_score = score; + temp_images_.push_back(image); +} + +void TopSites::StartQueryForMostVisited() { + if (!profile_) + return; + + HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + // |hs| may be null during unit tests. + if (hs) { + hs->QueryMostVisitedURLs( + num_results_to_request_from_history(), + kDaysOfHistory, + &cancelable_consumer_, + NewCallback(this, &TopSites::OnTopSitesAvailableFromHistory)); } +} - Images image = found->second; - *data = image.thumbnail.get(); - return true; +// static +int TopSites::GetRedirectDistanceForURL(const MostVisitedURL& most_visited, + const GURL& url) { + for (size_t i = 0; i < most_visited.redirects.size(); i++) { + if (most_visited.redirects[i] == url) + return static_cast<int>(most_visited.redirects.size() - i - 1); + } + NOTREACHED() << "URL should always be found."; + return 0; } -static int IndexOf(const MostVisitedURLList& urls, const GURL& url) { - for (size_t i = 0; i < urls.size(); i++) { - if (urls[i].url == url) - return i; +// static +MostVisitedURLList TopSites::GetPrepopulatePages() { + MostVisitedURLList urls; + urls.resize(arraysize(kPrepopulatePageIDs)); + for (size_t i = 0; i < arraysize(kPrepopulatePageIDs); ++i) { + MostVisitedURL& url = urls[i]; + url.url = GURL(l10n_util::GetStringUTF8(kPrepopulatePageIDs[i])); + url.redirects.push_back(url.url); + url.favicon_url = GURL(kPrepopulateFaviconURLs[i]); + url.title = l10n_util::GetStringUTF16(kPrepopulateTitleIDs[i]); } - return -1; + return urls; } +// static bool TopSites::AddPrepopulatedPages(MostVisitedURLList* urls) { - // TODO(arv): This needs to get the data from some configurable place. - // http://crbug.com/17630 bool added = false; - GURL welcome_url(l10n_util::GetStringUTF8(IDS_CHROME_WELCOME_URL)); - if (urls->size() < kTopSitesNumber && IndexOf(*urls, welcome_url) == -1) { - MostVisitedURL url( - welcome_url, - GURL("chrome://theme/IDR_NEWTAB_CHROME_WELCOME_PAGE_FAVICON"), - l10n_util::GetStringUTF16(IDS_NEW_TAB_CHROME_WELCOME_PAGE_TITLE)); - url.redirects.push_back(welcome_url); - urls->push_back(url); - added = true; - } - - GURL themes_url(l10n_util::GetStringUTF8(IDS_THEMES_GALLERY_URL)); - if (urls->size() < kTopSitesNumber && IndexOf(*urls, themes_url) == -1) { - MostVisitedURL url( - themes_url, - GURL("chrome://theme/IDR_NEWTAB_THEMES_GALLERY_FAVICON"), - l10n_util::GetStringUTF16(IDS_NEW_TAB_THEMES_GALLERY_PAGE_TITLE)); - url.redirects.push_back(themes_url); - urls->push_back(url); - added = true; + MostVisitedURLList prepopulate_urls = GetPrepopulatePages(); + for (size_t i = 0; i < prepopulate_urls.size(); ++i) { + if (urls->size() < kTopSitesNumber && + IndexOf(*urls, prepopulate_urls[i].url) == -1) { + urls->push_back(prepopulate_urls[i]); + added = true; + } } - return added; } void TopSites::MigratePinnedURLs() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + std::map<GURL, size_t> tmp_map; for (DictionaryValue::key_iterator it = pinned_urls_->begin_keys(); it != pinned_urls_->end_keys(); ++it) { @@ -327,7 +614,6 @@ void TopSites::MigratePinnedURLs() { void TopSites::ApplyBlacklistAndPinnedURLs(const MostVisitedURLList& urls, MostVisitedURLList* out) { - lock_.AssertAcquired(); MostVisitedURLList urls_copy; for (size_t i = 0; i < urls.size(); i++) { if (!IsBlacklisted(urls[i].url)) @@ -375,521 +661,247 @@ void TopSites::ApplyBlacklistAndPinnedURLs(const MostVisitedURLList& urls, } std::string TopSites::GetURLString(const GURL& url) { - lock_.AssertAcquired(); - return GetCanonicalURL(url).spec(); + return cache_->GetCanonicalURL(url).spec(); } std::string TopSites::GetURLHash(const GURL& url) { - lock_.AssertAcquired(); // We don't use canonical URLs here to be able to blacklist only one of // the two 'duplicate' sites, e.g. 'gmail.com' and 'mail.google.com'. return MD5String(url.spec()); } -void TopSites::UpdateMostVisited(MostVisitedURLList most_visited) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); +base::TimeDelta TopSites::GetUpdateDelay() { + if (cache_->top_sites().size() <= arraysize(kPrepopulateTitleIDs)) + return base::TimeDelta::FromSeconds(30); - std::vector<size_t> added; // Indices into most_visited. - std::vector<size_t> deleted; // Indices into top_sites_. - std::vector<size_t> moved; // Indices into most_visited. + int64 range = kMaxUpdateIntervalMinutes - kMinUpdateIntervalMinutes; + int64 minutes = kMaxUpdateIntervalMinutes - + last_num_urls_changed_ * range / cache_->top_sites().size(); + return base::TimeDelta::FromMinutes(minutes); +} - DiffMostVisited(top_sites_, most_visited, &added, &deleted, &moved); +// static +void TopSites::ProcessPendingCallbacks( + const PendingCallbackSet& pending_callbacks, + const MostVisitedURLList& urls) { + PendingCallbackSet::const_iterator i; + for (i = pending_callbacks.begin(); + i != pending_callbacks.end(); ++i) { + scoped_refptr<CancelableRequest<GetTopSitesCallback> > request = *i; + if (!request->canceled()) + request->ForwardResult(GetTopSitesCallback::TupleType(urls)); + } +} - // #added == #deleted; #added + #moved = total. - last_num_urls_changed_ = added.size() + moved.size(); +void TopSites::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (!loaded_) + return; - { - AutoLock lock(lock_); + if (type == NotificationType::HISTORY_URLS_DELETED) { + Details<history::URLsDeletedDetails> deleted_details(details); + if (deleted_details->all_history) { + SetTopSites(MostVisitedURLList()); + backend_->ResetDatabase(); + } else { + std::set<size_t> indices_to_delete; // Indices into top_sites_. + for (std::set<GURL>::iterator i = deleted_details->urls.begin(); + i != deleted_details->urls.end(); ++i) { + if (cache_->IsKnownURL(*i)) + indices_to_delete.insert(cache_->GetURLIndex(*i)); + } - // Process the diff: delete from images and disk, add to disk. - // Delete all the thumbnails associated with URLs that were deleted. - for (size_t i = 0; i < deleted.size(); i++) { - const MostVisitedURL& deleted_url = top_sites_[deleted[i]]; - std::map<GURL, Images>::iterator found = - top_images_.find(deleted_url.url); - if (found != top_images_.end()) - top_images_.erase(found); - } - } + if (indices_to_delete.empty()) + return; - // Write the updates to the DB. - if (db_.get()) { - for (size_t i = 0; i < deleted.size(); i++) { - const MostVisitedURL& deleted_url = top_sites_[deleted[i]]; - if (db_.get()) - db_->RemoveURL(deleted_url); - } - for (size_t i = 0; i < added.size(); i++) { - const MostVisitedURL& added_url = most_visited[added[i]]; - db_->SetPageThumbnail(added_url, added[i], Images()); - } - for (size_t i = 0; i < moved.size(); i++) { - const MostVisitedURL& moved_url = most_visited[moved[i]]; - db_->UpdatePageRank(moved_url, moved[i]); + MostVisitedURLList new_top_sites(cache_->top_sites()); + for (std::set<size_t>::reverse_iterator i = indices_to_delete.rbegin(); + i != indices_to_delete.rend(); i++) { + size_t index = *i; + RemovePinnedURL(new_top_sites[index].url); + new_top_sites.erase(new_top_sites.begin() + index); + } + SetTopSites(new_top_sites); } - } - - StoreMostVisited(&most_visited); - if (migration_in_progress_) { - // Copy all thumnbails from the history service. - for (size_t i = 0; i < top_sites_.size(); i++) { - GURL& url = top_sites_[i].url; - Images& img = top_images_[url]; - if (!img.thumbnail.get() || !img.thumbnail->size()) { - StartQueryForThumbnail(i); + StartQueryForMostVisited(); + } else if (type == NotificationType::NAV_ENTRY_COMMITTED) { + if (cache_->top_sites().size() < kTopSitesNumber) { + NavigationController::LoadCommittedDetails* load_details = + Details<NavigationController::LoadCommittedDetails>(details).ptr(); + if (!load_details) + return; + const GURL& url = load_details->entry->url(); + if (!cache_->IsKnownURL(url) && HistoryService::CanAddURL(url)) { + // To avoid slamming history we throttle requests when the url updates. + // To do otherwise negatively impacts perf tests. + RestartQueryForTopSitesTimer(GetUpdateDelay()); } } } - - // If we are not expecting any thumbnails, migration is done. - if (migration_in_progress_ && migration_pending_urls_.empty()) - OnMigrationDone(); - - timer_.Stop(); - timer_.Start(GetUpdateDelay(), this, - &TopSites::StartQueryForMostVisited); } -void TopSites::OnMigrationDone() { - migration_in_progress_ = false; - if (!profile_) - return; +void TopSites::SetTopSites(const MostVisitedURLList& new_top_sites) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); - // |hs| may be null during unit tests. - if (!hs) - return; - hs->OnTopSitesReady(); -} - -void TopSites::AddTemporaryThumbnail(const GURL& url, - const RefCountedBytes* thumbnail, - const ThumbnailScore& score) { - Images& img = temp_thumbnails_map_[url]; - img.thumbnail = const_cast<RefCountedBytes*>(thumbnail); - img.thumbnail_score = score; -} - -void TopSites::StartQueryForThumbnail(size_t index) { - DCHECK(migration_in_progress_); - if (top_sites_[index].url.spec() == - l10n_util::GetStringUTF8(IDS_CHROME_WELCOME_URL) || - top_sites_[index].url.spec() == - l10n_util::GetStringUTF8(IDS_THEMES_GALLERY_URL)) - return; // Don't need thumbnails for prepopulated URLs. - - migration_pending_urls_.insert(top_sites_[index].url); - - if (mock_history_service_) { - // Testing with a mockup. - // QueryMostVisitedURLs is not virtual, so we have to duplicate the code. - // This calls SetClientData. - mock_history_service_->GetPageThumbnail( - top_sites_[index].url, - &cancelable_consumer_, - NewCallback(this, &TopSites::OnThumbnailAvailable), - index); - return; - } + MostVisitedURLList top_sites(new_top_sites); + AddPrepopulatedPages(&top_sites); - if (!profile_) - return; + TopSitesDelta delta; + DiffMostVisited(cache_->top_sites(), top_sites, &delta); + if (!delta.deleted.empty() || !delta.added.empty() || !delta.moved.empty()) + backend_->UpdateTopSites(delta); - HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); - // |hs| may be null during unit tests. - if (!hs) - return; - HistoryService::Handle handle = - hs->GetPageThumbnail(top_sites_[index].url, - &cancelable_consumer_, - NewCallback(this, &TopSites::OnThumbnailAvailable)); - cancelable_consumer_.SetClientData(hs, handle, index); -} + last_num_urls_changed_ = delta.added.size() + delta.moved.size(); -void TopSites::GenerateCanonicalURLs() { - lock_.AssertAcquired(); - canonical_urls_.clear(); - for (size_t i = 0; i < top_sites_.size(); i++) { - const MostVisitedURL& mv = top_sites_[i]; - StoreRedirectChain(mv.redirects, i); - } -} + // We always do the following steps (setting top sites in cache, and resetting + // thread safe cache ...) as this method is invoked during startup at which + // point the caches haven't been updated yet. + cache_->SetTopSites(top_sites); -void TopSites::StoreMostVisited(MostVisitedURLList* most_visited) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); - MostVisitedURLList filtered_urls; - PendingCallbackSet callbacks; - { - AutoLock lock(lock_); - top_sites_.clear(); - // Take ownership of the most visited data. - top_sites_.swap(*most_visited); - waiting_for_results_ = false; - - // Save the redirect information for quickly mapping to the canonical URLs. - GenerateCanonicalURLs(); - - for (size_t i = 0; i < top_sites_.size(); i++) { - const MostVisitedURL& mv = top_sites_[i]; - std::map<GURL, Images>::iterator it = temp_thumbnails_map_.begin(); - GURL canonical_url = GetCanonicalURL(mv.url); - for (; it != temp_thumbnails_map_.end(); it++) { - // Must map all temp URLs to canonical ones. - // temp_thumbnails_map_ contains non-canonical URLs, because - // when we add a temp thumbnail, redirect chain is not known. - // This is slow, but temp_thumbnails_map_ should have very few URLs. - if (canonical_url == GetCanonicalURL(it->first)) { - SetPageThumbnailEncoded(mv.url, it->second.thumbnail, + // See if we have any tmp thumbnails for the new sites. + if (!temp_images_.empty()) { + for (size_t i = 0; i < top_sites.size(); ++i) { + const MostVisitedURL& mv = top_sites[i]; + GURL canonical_url = cache_->GetCanonicalURL(mv.url); + // At the time we get the thumbnail redirects aren't known, so we have to + // iterate through all the images. + for (TempImages::iterator it = temp_images_.begin(); + it != temp_images_.end(); ++it) { + if (canonical_url == cache_->GetCanonicalURL(it->first)) { + SetPageThumbnailEncoded(mv.url, + it->second.thumbnail, it->second.thumbnail_score); - temp_thumbnails_map_.erase(it); + temp_images_.erase(it); break; } } } - if (top_sites_.size() >= kTopSitesNumber) - temp_thumbnails_map_.clear(); + } - if (pending_callbacks_.empty()) - return; + if (top_sites.size() >= kTopSitesNumber) + temp_images_.clear(); - ApplyBlacklistAndPinnedURLs(top_sites_, &filtered_urls); - callbacks.swap(pending_callbacks_); - } // lock_ is released. - // Process callbacks outside the lock - ForwardResults may cause - // thread switches. - ProcessPendingCallbacks(callbacks, filtered_urls); -} + ResetThreadSafeCache(); + ResetThreadSafeImageCache(); -void TopSites::StoreRedirectChain(const RedirectList& redirects, - size_t destination) { - lock_.AssertAcquired(); - if (redirects.empty()) { - NOTREACHED(); - return; - } - - // Map all the redirected URLs to the destination. - for (size_t i = 0; i < redirects.size(); i++) { - // If this redirect is already known, don't replace it with a new one. - if (canonical_urls_.find(redirects[i]) == canonical_urls_.end()) - canonical_urls_[redirects[i]] = destination; - } + // Restart the timer that queries history for top sites. This is done to + // ensure we stay in sync with history. + RestartQueryForTopSitesTimer(GetUpdateDelay()); } -GURL TopSites::GetCanonicalURL(const GURL& url) const { - lock_.AssertAcquired(); - std::map<GURL, size_t>::const_iterator found = canonical_urls_.find(url); - if (found == canonical_urls_.end()) - return url; // Unknown URL - return unchanged. - return top_sites_[found->second].url; -} +int TopSites::num_results_to_request_from_history() const { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); -// static -int TopSites::GetRedirectDistanceForURL(const MostVisitedURL& most_visited, - const GURL& url) { - for (size_t i = 0; i < most_visited.redirects.size(); i++) { - if (most_visited.redirects[i] == url) - return static_cast<int>(most_visited.redirects.size() - i - 1); - } - NOTREACHED() << "URL should always be found."; - return 0; + return kTopSitesNumber + blacklist_->size(); } -// static -void TopSites::DiffMostVisited(const MostVisitedURLList& old_list, - const MostVisitedURLList& new_list, - std::vector<size_t>* added_urls, - std::vector<size_t>* deleted_urls, - std::vector<size_t>* moved_urls) { - added_urls->clear(); - deleted_urls->clear(); - moved_urls->clear(); - - // Add all the old URLs for quick lookup. This maps URLs to the corresponding - // index in the input. - std::map<GURL, size_t> all_old_urls; - for (size_t i = 0; i < old_list.size(); i++) - all_old_urls[old_list[i].url] = i; +void TopSites::MoveStateToLoaded() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - // Check all the URLs in the new set to see which ones are new or just moved. - // When we find a match in the old set, we'll reset its index to our special - // marker. This allows us to quickly identify the deleted ones in a later - // pass. - const size_t kAlreadyFoundMarker = static_cast<size_t>(-1); - for (size_t i = 0; i < new_list.size(); i++) { - std::map<GURL, size_t>::iterator found = all_old_urls.find(new_list[i].url); - if (found == all_old_urls.end()) { - added_urls->push_back(i); - } else { - if (found->second != i) - moved_urls->push_back(i); - found->second = kAlreadyFoundMarker; - } - } + MostVisitedURLList filtered_urls; + PendingCallbackSet pending_callbacks; + { + AutoLock lock(lock_); - // Any member without the special marker in the all_old_urls list means that - // there wasn't a "new" URL that mapped to it, so it was deleted. - for (std::map<GURL, size_t>::const_iterator i = all_old_urls.begin(); - i != all_old_urls.end(); ++i) { - if (i->second != kAlreadyFoundMarker) - deleted_urls->push_back(i->second); - } -} + if (loaded_) + return; // Don't do anything if we're already loaded. + loaded_ = true; -void TopSites::StartQueryForMostVisited() { - if (mock_history_service_) { - // Testing with a mockup. - // QueryMostVisitedURLs is not virtual, so we have to duplicate the code. - mock_history_service_->QueryMostVisitedURLs( - kTopSitesNumber + blacklist_->size(), - kDaysOfHistory, - &cancelable_consumer_, - NewCallback(this, &TopSites::OnTopSitesAvailable)); - } else { - if (!profile_) - return; - - HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); - // |hs| may be null during unit tests. - if (hs) { - hs->QueryMostVisitedURLs( - kTopSitesNumber + blacklist_->size(), - kDaysOfHistory, - &cancelable_consumer_, - NewCallback(this, &TopSites::OnTopSitesAvailable)); - } else { - VLOG(1) << "History Service not available."; + // Now that we're loaded we can service the queued up callbacks. Copy them + // here and service them outside the lock. + if (!pending_callbacks_.empty()) { + filtered_urls = thread_safe_cache_->top_sites(); + pending_callbacks.swap(pending_callbacks_); } } -} -void TopSites::StartMigration() { - VLOG(1) << "Starting migration to TopSites."; - migration_in_progress_ = true; - StartQueryForMostVisited(); - MigratePinnedURLs(); -} + ProcessPendingCallbacks(pending_callbacks, filtered_urls); -bool TopSites::HasBlacklistedItems() const { - AutoLock lock(lock_); - return !blacklist_->empty(); + NotificationService::current()->Notify(NotificationType::TOP_SITES_LOADED, + Source<Profile>(profile_), + Details<TopSites>(this)); } -void TopSites::AddBlacklistedURL(const GURL& url) { +void TopSites::ResetThreadSafeCache() { AutoLock lock(lock_); - RemovePinnedURLLocked(url); - Value* dummy = Value::CreateNullValue(); - blacklist_->SetWithoutPathExpansion(GetURLHash(url), dummy); + MostVisitedURLList cached; + ApplyBlacklistAndPinnedURLs(cache_->top_sites(), &cached); + thread_safe_cache_->SetTopSites(cached); } -bool TopSites::IsBlacklisted(const GURL& url) { - lock_.AssertAcquired(); - bool result = blacklist_->HasKey(GetURLHash(url)); - return result; -} - -void TopSites::RemoveBlacklistedURL(const GURL& url) { +void TopSites::ResetThreadSafeImageCache() { AutoLock lock(lock_); - blacklist_->RemoveWithoutPathExpansion(GetURLHash(url), NULL); -} - -void TopSites::ClearBlacklistedURLs() { - blacklist_->Clear(); + thread_safe_cache_->SetThumbnails(cache_->images()); + thread_safe_cache_->RemoveUnreferencedThumbnails(); } -void TopSites::AddPinnedURL(const GURL& url, size_t pinned_index) { - GURL old; - if (GetPinnedURLAtIndex(pinned_index, &old)) { - RemovePinnedURL(old); - } - - if (IsURLPinned(url)) { - RemovePinnedURL(url); +void TopSites::RestartQueryForTopSitesTimer(base::TimeDelta delta) { + if (timer_.IsRunning() && ((timer_start_time_ + timer_.GetCurrentDelay()) < + (base::TimeTicks::Now() + delta))) { + return; } - Value* index = Value::CreateIntegerValue(pinned_index); - AutoLock lock(lock_); - pinned_urls_->SetWithoutPathExpansion(GetURLString(url), index); -} - -void TopSites::RemovePinnedURL(const GURL& url) { - AutoLock lock(lock_); - RemovePinnedURLLocked(url); -} - -void TopSites::RemovePinnedURLLocked(const GURL& url) { - lock_.AssertAcquired(); - pinned_urls_->RemoveWithoutPathExpansion(GetURLString(url), NULL); + timer_start_time_ = base::TimeTicks::Now(); + timer_.Stop(); + timer_.Start(delta, this, &TopSites::StartQueryForMostVisited); } -bool TopSites::IsURLPinned(const GURL& url) { - AutoLock lock(lock_); - int tmp; - bool result = pinned_urls_->GetIntegerWithoutPathExpansion( - GetURLString(url), &tmp); - return result; -} +void TopSites::OnHistoryMigrationWrittenToDisk(TopSitesBackend::Handle handle) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); -bool TopSites::GetPinnedURLAtIndex(size_t index, GURL* url) { - for (DictionaryValue::key_iterator it = pinned_urls_->begin_keys(); - it != pinned_urls_->end_keys(); ++it) { - int current_index; - if (pinned_urls_->GetIntegerWithoutPathExpansion(*it, ¤t_index)) { - if (static_cast<size_t>(current_index) == index) { - *url = GURL(*it); - return true; - } - } - } - return false; -} - -// static -void TopSites::DeleteTopSites(scoped_refptr<TopSites>& ptr) { - if (!ptr.get() || !MessageLoop::current()) + if (!profile_) return; - if (BrowserThread::IsWellKnownThread(BrowserThread::UI)) { - ptr = NULL; - } else { - // Need to roll our own UI thread. - BrowserThread ui_loop(BrowserThread::UI, MessageLoop::current()); - ptr = NULL; - MessageLoop::current()->RunAllPending(); - } -} - -void TopSites::ClearProfile() { - profile_ = NULL; -} - -base::TimeDelta TopSites::GetUpdateDelay() { - AutoLock lock(lock_); - if (top_sites_.size() == 0) - return base::TimeDelta::FromSeconds(30); - int64 range = kMaxUpdateIntervalMinutes - kMinUpdateIntervalMinutes; - int64 minutes = kMaxUpdateIntervalMinutes - - last_num_urls_changed_ * range / top_sites_.size(); - return base::TimeDelta::FromMinutes(minutes); + HistoryService* history = + profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (history) + history->OnTopSitesReady(); } -void TopSites::OnTopSitesAvailable( +void TopSites::OnGotMostVisitedThumbnails( CancelableRequestProvider::Handle handle, - MostVisitedURLList pages) { - AddPrepopulatedPages(&pages); - BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, NewRunnableMethod( - this, &TopSites::UpdateMostVisited, pages)); -} + scoped_refptr<MostVisitedThumbnails> data, + bool may_need_history_migration) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK_EQ(top_sites_state_, TOP_SITES_LOADING); -// static -void TopSites::ProcessPendingCallbacks(PendingCallbackSet pending_callbacks, - const MostVisitedURLList& urls) { - PendingCallbackSet::iterator i; - for (i = pending_callbacks.begin(); - i != pending_callbacks.end(); ++i) { - scoped_refptr<CancelableRequest<GetTopSitesCallback> > request = *i; - if (!request->canceled()) - request->ForwardResult(GetTopSitesCallback::TupleType(urls)); - } - pending_callbacks.clear(); -} + if (!may_need_history_migration) { + top_sites_state_ = TOP_SITES_LOADED; -void TopSites::OnThumbnailAvailable(CancelableRequestProvider::Handle handle, - scoped_refptr<RefCountedBytes> thumbnail) { - size_t index; - if (mock_history_service_) { - index = handle; - } else { - if (!profile_) - return; + // Set the top sites directly in the cache so that SetTopSites diffs + // correctly. + cache_->SetTopSites(data->most_visited); + SetTopSites(data->most_visited); + cache_->SetThumbnails(data->url_to_images_map); - HistoryService* hs = profile_ ->GetHistoryService(Profile::EXPLICIT_ACCESS); - if (!hs) - return; - index = cancelable_consumer_.GetClientData(hs, handle); - } - DCHECK(static_cast<size_t>(index) < top_sites_.size()); + ResetThreadSafeImageCache(); - if (migration_in_progress_) - migration_pending_urls_.erase(top_sites_[index].url); + MoveStateToLoaded(); - if (thumbnail.get() && thumbnail->size()) { - const MostVisitedURL& url = top_sites_[index]; - AutoLock lock(lock_); - SetPageThumbnailEncoded(url.url, thumbnail, ThumbnailScore()); - } - - if (migration_in_progress_ && migration_pending_urls_.empty() && - !mock_history_service_) - OnMigrationDone(); -} - -void TopSites::SetMockHistoryService(MockHistoryService* mhs) { - mock_history_service_ = mhs; -} - -void TopSites::Observe(NotificationType type, - const NotificationSource& source, - const NotificationDetails& details) { - AutoLock lock(lock_); - if (type == NotificationType::HISTORY_URLS_DELETED) { - Details<history::URLsDeletedDetails> deleted_details(details); - if (deleted_details->all_history) { - top_sites_.clear(); - BrowserThread::PostTask( - BrowserThread::DB, FROM_HERE, - NewRunnableMethod(this, &TopSites::ResetDatabase)); + // Start a timer that refreshes top sites from history. + RestartQueryForTopSitesTimer( + base::TimeDelta::FromSeconds(kUpdateIntervalSecs)); + } else { + // The top sites file didn't exist or is the wrong version. We need to wait + // for history to finish loading to know if we really needed to migrate. + if (history_state_ == HISTORY_LOADED) { + top_sites_state_ = TOP_SITES_LOADED; + SetTopSites(MostVisitedURLList()); + MoveStateToLoaded(); } else { - std::set<size_t> indices_to_delete; // Indices into top_sites_. - std::set<GURL>::iterator it; - for (it = deleted_details->urls.begin(); - it != deleted_details->urls.end(); ++it) { - std::map<GURL,size_t>::const_iterator found = canonical_urls_.find(*it); - if (found != canonical_urls_.end()) - indices_to_delete.insert(found->second); - } - - for (std::set<size_t>::reverse_iterator i = indices_to_delete.rbegin(); - i != indices_to_delete.rend(); i++) { - size_t index = *i; - RemovePinnedURLLocked(top_sites_[index].url); - top_sites_.erase(top_sites_.begin() + index); - } - } - // Canonical URLs are not valid any more. - GenerateCanonicalURLs(); - StartQueryForMostVisited(); - } else if (type == NotificationType::NAV_ENTRY_COMMITTED) { - if (top_sites_.size() < kTopSitesNumber) { - NavigationController::LoadCommittedDetails* load_details = - Details<NavigationController::LoadCommittedDetails>(details).ptr(); - if (!load_details) - return; - GURL url = load_details->entry->url(); - if (canonical_urls_.find(url) == canonical_urls_.end() && - HistoryService::CanAddURL(url)) { - // Add this page to the known pages in case the thumbnail comes - // in before we get the results. - MostVisitedURL mv; - mv.url = url; - mv.redirects.push_back(url); - top_sites_.push_back(mv); - size_t index = top_sites_.size() - 1; - StoreRedirectChain(top_sites_[index].redirects, index); - } - StartQueryForMostVisited(); + top_sites_state_ = TOP_SITES_LOADED_WAITING_FOR_HISTORY; + // Ask for history just in case it hasn't been loaded yet. When history + // finishes loading we'll do migration and/or move to loaded. + profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); } } } -void TopSites::ResetDatabase() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); - db_.reset(new TopSitesDatabaseImpl()); - file_util::Delete(db_path_, false); - if (!db_->Init(db_path_)) { - NOTREACHED() << "Failed to initialize database."; - return; - } +void TopSites::OnTopSitesAvailableFromHistory( + CancelableRequestProvider::Handle handle, + MostVisitedURLList pages) { + SetTopSites(pages); } } // namespace history diff --git a/chrome/browser/history/top_sites.h b/chrome/browser/history/top_sites.h index f080c05..bd406f1 100644 --- a/chrome/browser/history/top_sites.h +++ b/chrome/browser/history/top_sites.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -6,14 +6,14 @@ #define CHROME_BROWSER_HISTORY_TOP_SITES_H_ #pragma once -#include <map> +#include <list> #include <set> #include <string> -#include <vector> #include "base/basictypes.h" #include "base/gtest_prod_util.h" #include "base/lock.h" +#include "base/time.h" #include "base/timer.h" #include "base/ref_counted.h" #include "base/ref_counted_memory.h" @@ -27,30 +27,26 @@ #include "googleurl/src/gurl.h" class DictionaryValue; +class FilePath; class SkBitmap; class Profile; namespace history { +class TopSitesCache; class TopSitesBackend; -class TopSitesDatabase; class TopSitesTest; -typedef std::vector<MostVisitedURL> MostVisitedURLList; - // Stores the data for the top "most visited" sites. This includes a cache of // the most visited data from history, as well as the corresponding thumbnails // of those sites. // -// This class IS threadsafe. It is designed to be used from the UI thread of -// the browser (where history requests must be kicked off and received from) -// and from the I/O thread (where new tab page requests come in). Handling the -// new tab page requests on the I/O thread without proxying to the UI thread is -// a nontrivial performance win, especially when the browser is starting and -// the UI thread is busy. -class TopSites : - public base::RefCountedThreadSafe<TopSites, - BrowserThread::DeleteOnUIThread>, +// This class allows requests for most visited urls and thumbnails on any +// thread. All other methods must be invoked on the UI thread. All mutations +// to internal state happen on the UI thread and are scheduled to update the +// db using TopSitesBackend. +class TopSites + : public base::RefCountedThreadSafe<TopSites>, public NotificationObserver, public CancelableRequestProvider { public: @@ -59,21 +55,6 @@ class TopSites : // Returns whether top sites is enabled. static bool IsEnabled(); - class MockHistoryService { - // A mockup of a HistoryService used for testing TopSites. - public: - virtual HistoryService::Handle QueryMostVisitedURLs( - int result_count, int days_back, - CancelableRequestConsumerBase* consumer, - HistoryService::QueryMostVisitedURLsCallback* callback) = 0; - virtual ~MockHistoryService() {} - virtual void GetPageThumbnail( - const GURL& page_url, - CancelableRequestConsumerTSimple<size_t>* consumer, - HistoryService::ThumbnailDataCallback* callback, - size_t index) = 0; - }; - // Initializes TopSites. void Init(const FilePath& db_name); @@ -85,23 +66,33 @@ class TopSites : const ThumbnailScore& score); // Callback for GetMostVisitedURLs. - typedef Callback1<MostVisitedURLList>::Type GetTopSitesCallback; + typedef Callback1<const MostVisitedURLList&>::Type GetTopSitesCallback; typedef std::set<scoped_refptr<CancelableRequest<GetTopSitesCallback> > > PendingCallbackSet; // Returns a list of most visited URLs via a callback. + // This may be invoked on any thread. // NOTE: the callback may be called immediately if we have the data cached. void GetMostVisitedURLs(CancelableRequestConsumer* consumer, GetTopSitesCallback* callback); // Get a thumbnail for a given page. Returns true iff we have the thumbnail. - bool GetPageThumbnail(const GURL& url, RefCountedBytes** data) const; + // This may be invoked on any thread. + // As this method may be invoked on any thread the ref count needs to be + // upped before this method returns, so this takes a scoped_refptr*. + bool GetPageThumbnail(const GURL& url, + scoped_refptr<RefCountedBytes>* bytes); + + // Invoked from History if migration is needed. If this is invoked it will + // be before HistoryLoaded is invoked. + void MigrateFromHistory(); - // For testing with a HistoryService mock. - void SetMockHistoryService(MockHistoryService* mhs); + // Invoked with data from migrating thumbnails out of history. + void FinishHistoryMigration(const ThumbnailMigration& data); - // Start reading thumbnails from the ThumbnailDatabase. - void StartMigration(); + // Invoked from history when it finishes loading. If MigrateFromHistory was + // not invoked at this point then we load from the top sites service. + void HistoryLoaded(); // Blacklisted URLs @@ -114,6 +105,9 @@ class TopSites : // Removes a URL from the blacklist. void RemoveBlacklistedURL(const GURL& url); + // Returns true if the URL is blacklisted. + bool IsBlacklisted(const GURL& url); + // Clear the blacklist. void ClearBlacklistedURLs(); @@ -132,34 +126,53 @@ class TopSites : // is a URL pinned at |index|. bool GetPinnedURLAtIndex(size_t index, GURL* out); - // TopSites must be deleted on a UI thread. This happens - // automatically in a real browser, but in unit_tests we don't have - // a real UI thread. Use this function to delete a TopSites object. - static void DeleteTopSites(scoped_refptr<TopSites>& ptr); + // Shuts down top sites. + void Shutdown(); - // Sets the profile pointer to NULL. This is for the case where - // TopSites outlives the profile, since TopSites is refcounted. - void ClearProfile(); + // Generates the diff of things that happened between "old" and "new." + // + // The URLs that are in "new" but not "old" will be have their index into + // "new" put in |added_urls|. The URLs that are in "old" but not "new" will + // have their index into "old" put into |deleted_urls|. + // + // URLs appearing in both old and new lists but having different indices will + // have their index into "new" be put into |moved_urls|. + static void DiffMostVisited(const MostVisitedURLList& old_list, + const MostVisitedURLList& new_list, + TopSitesDelta* delta); private: - friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>; - friend class DeleteTask<TopSites>; + friend class base::RefCountedThreadSafe<TopSites>; friend class TopSitesTest; - FRIEND_TEST_ALL_PREFIXES(TopSitesTest, GetMostVisited); - FRIEND_TEST_ALL_PREFIXES(TopSitesTest, RealDatabase); - FRIEND_TEST_ALL_PREFIXES(TopSitesTest, MockDatabase); - FRIEND_TEST_ALL_PREFIXES(TopSitesTest, DeleteNotifications); - FRIEND_TEST_ALL_PREFIXES(TopSitesTest, PinnedURLsDeleted); - FRIEND_TEST_ALL_PREFIXES(TopSitesTest, GetUpdateDelay); - FRIEND_TEST_ALL_PREFIXES(TopSitesTest, Migration); - FRIEND_TEST_ALL_PREFIXES(TopSitesTest, QueueingRequestsForTopSites); - FRIEND_TEST_ALL_PREFIXES(TopSitesTest, CancelingRequestsForTopSites); - FRIEND_TEST_ALL_PREFIXES(TopSitesTest, AddTemporaryThumbnail); - FRIEND_TEST_ALL_PREFIXES(TopSitesTest, Blacklisting); - FRIEND_TEST_ALL_PREFIXES(TopSitesTest, PinnedURLs); - FRIEND_TEST_ALL_PREFIXES(TopSitesTest, BlacklistingAndPinnedURLs); - FRIEND_TEST_ALL_PREFIXES(TopSitesTest, AddPrepopulatedPages); - FRIEND_TEST_ALL_PREFIXES(TopSitesTest, GetPageThumbnail); + + typedef std::pair<GURL, Images> TempImage; + typedef std::list<TempImage> TempImages; + + // Enumeration of the possible states history can be in. + enum HistoryLoadState { + // We're waiting for history to finish loading. + HISTORY_LOADING, + + // History finished loading and we need to migrate top sites out of history. + HISTORY_MIGRATING, + + // History is loaded. + HISTORY_LOADED + }; + + // Enumeration of possible states the top sites backend can be in. + enum TopSitesLoadState { + // We're waiting for the backend to finish loading. + TOP_SITES_LOADING, + + // The backend finished loading, but we may need to migrate. This is true if + // the top sites db didn't exist, or if the db existed but is from an old + // version. + TOP_SITES_LOADED_WAITING_FOR_HISTORY, + + // Top sites is loaded. + TOP_SITES_LOADED + }; ~TopSites(); @@ -176,44 +189,22 @@ class TopSites : const RefCountedBytes* thumbnail, const ThumbnailScore& score); - // Query history service for the list of available thumbnails. - void StartQueryForMostVisited(); - - // Query history service for the thumbnail for a given url. |index| - // is the index into top_sites_. - void StartQueryForThumbnail(size_t index); - - // Called when history service returns a list of top URLs. - void OnTopSitesAvailable(CancelableRequestProvider::Handle handle, - MostVisitedURLList data); - - // Returns a list of urls to each pending callback. - static void ProcessPendingCallbacks(PendingCallbackSet pending_callbacks, - const MostVisitedURLList& urls); - - // Called when history service returns a thumbnail. - void OnThumbnailAvailable(CancelableRequestProvider::Handle handle, - scoped_refptr<RefCountedBytes> thumbnail); + // Encodes the bitmap to bytes for storage to the db. Returns true if the + // bitmap was successfully encoded. + static bool EncodeBitmap(const SkBitmap& bitmap, + scoped_refptr<RefCountedBytes>* bytes); - // Sets canonical_urls_ from top_sites_. - void GenerateCanonicalURLs(); + // Removes the cached thumbnail for url. Does nothing if |url| if not cached + // in |temp_images_|. + void RemoveTemporaryThumbnailByURL(const GURL& url); - // Saves the set of the top URLs visited by this user. The 0th item is the - // most popular. - // DANGER! This will clear all data from the input argument. - void StoreMostVisited(MostVisitedURLList* most_visited); - - // Saves the given set of redirects. The redirects are in order of the - // given vector, so [0] -> [1] -> [2]. - void StoreRedirectChain(const RedirectList& redirects, - size_t destination); + // Add a thumbnail for an unknown url. See temp_thumbnails_map_. + void AddTemporaryThumbnail(const GURL& url, + const RefCountedBytes* thumbnail, + const ThumbnailScore& score); - // Each item in the most visited view can redirect elsewhere. This returns - // the canonical URL one identifying the site if the given URL does appear - // in the "top sites" list. - // - // If the given URL is not in the top sites, this will return an empty GURL. - GURL GetCanonicalURL(const GURL& url) const; + // Query history service for the list of available thumbnails. + void StartQueryForMostVisited(); // Finds the given URL in the redirect chain for the given TopSite, and // returns the distance from the destination in hops that the given URL is. @@ -221,131 +212,114 @@ class TopSites : static int GetRedirectDistanceForURL(const MostVisitedURL& most_visited, const GURL& url); - // Generates the diff of things that happened between "old" and "new." - // - // The URLs that are in "new" but not "old" will be have their index into - // "new" put in |added_urls|. The URLs that are in "old" but not "new" will - // have their index into "old" put into |deleted_urls|. - // - // URLs appearing in both old and new lists but having different indices will - // have their index into "new" be put into |moved_urls|. - static void DiffMostVisited(const MostVisitedURLList& old_list, - const MostVisitedURLList& new_list, - std::vector<size_t>* added_urls, - std::vector<size_t>* deleted_urls, - std::vector<size_t>* moved_urls); + // Returns the set of prepopulate pages. + static MostVisitedURLList GetPrepopulatePages(); - // Implementation of NotificationObserver. - virtual void Observe(NotificationType type, - const NotificationSource& source, - const NotificationDetails& details); + // Add prepopulated pages: 'welcome to Chrome' and themes gallery to |urls|. + // Returns true if any pages were added. + static bool AddPrepopulatedPages(MostVisitedURLList* urls); - // Returns true if the URL is blacklisted. - bool IsBlacklisted(const GURL& url); + // Convert pinned_urls_ dictionary to the new format. Use URLs as + // dictionary keys. + void MigratePinnedURLs(); + + // Takes |urls|, produces it's copy in |out| after removing + // blacklisted URLs and reordering pinned URLs. + void ApplyBlacklistAndPinnedURLs(const MostVisitedURLList& urls, + MostVisitedURLList* out); - // A variant of RemovePinnedURL that must be called within a lock. - void RemovePinnedURLLocked(const GURL& url); + // Converts a url into a canonical string representation. + std::string GetURLString(const GURL& url); + + // Returns an MD5 hash of the URL. Hashing is required for blacklisted URLs. + std::string GetURLHash(const GURL& url); // Returns the delay until the next update of history is needed. // Uses num_urls_changed base::TimeDelta GetUpdateDelay(); - // The following methods must be run on the DB thread since they - // access the database. + // Executes all of the callbacks in |pending_callbacks|. This is used after + // we finish loading if any requests came in before we loaded. + static void ProcessPendingCallbacks( + const PendingCallbackSet& pending_callbacks, + const MostVisitedURLList& urls); - // Reads the database from disk. Called on startup to get the last - // known top sites. - void ReadDatabase(); + // Implementation of NotificationObserver. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); - // Write a thumbnail to database. - void WriteThumbnailToDB(const MostVisitedURL& url, - int url_rank, - const Images& thumbnail); + // Resets top_sites_ and updates the db (in the background). All mutations to + // top_sites_ *must* go through this. + void SetTopSites(const MostVisitedURLList& new_top_sites); - // Updates the top sites list and writes the difference to disk. - void UpdateMostVisited(MostVisitedURLList most_visited); + // Returns the number of most visted results to request from history. This + // changes depending upon how many urls have been blacklisted. + int num_results_to_request_from_history() const; - // Deletes the database file, then reinitializes the database. - void ResetDatabase(); + // Invoked when transitioning to LOADED. Notifies any queued up callbacks. + void MoveStateToLoaded(); - // Called after TopSites completes migration. - void OnMigrationDone(); + void ResetThreadSafeCache(); - // Add a thumbnail for an unknown url. See temp_thumbnails_map_. - void AddTemporaryThumbnail(const GURL& url, - const RefCountedBytes* thumbnail, - const ThumbnailScore& score); + void ResetThreadSafeImageCache(); - // Add prepopulated pages: 'welcome to Chrome' and themes gallery. - // Returns true if any pages were added. - bool AddPrepopulatedPages(MostVisitedURLList* urls); + // Stops and starts timer with a delay of |delta|. + void RestartQueryForTopSitesTimer(base::TimeDelta delta); - // Convert pinned_urls_ dictionary to the new format. Use URLs as - // dictionary keys. - void MigratePinnedURLs(); + // Callback after TopSitesBackend has finished migration. This tells history + // to finish it's side of migration (nuking thumbnails on disk). + void OnHistoryMigrationWrittenToDisk( + CancelableRequestProvider::Handle handle); - // Takes |urls|, produces it's copy in |out| after removing - // blacklisted URLs and reordering pinned URLs. - void ApplyBlacklistAndPinnedURLs(const MostVisitedURLList& urls, - MostVisitedURLList* out); + // Callback from TopSites with the top sites/thumbnails. + void OnGotMostVisitedThumbnails(CancelableRequestProvider::Handle handle, + scoped_refptr<MostVisitedThumbnails> data, + bool may_need_history_migration); - // Converts a url into a canonical string representation. - std::string GetURLString(const GURL& url); + // Called when history service returns a list of top URLs. + void OnTopSitesAvailableFromHistory(CancelableRequestProvider::Handle handle, + MostVisitedURLList data); - // Returns an MD5 hash of the URL. Hashing is required for blacklisted URLs. - std::string GetURLHash(const GURL& url); + scoped_refptr<TopSitesBackend> backend_; - Profile* profile_; - // A mockup to use for testing. If NULL, use the real HistoryService - // from the profile_. See SetMockHistoryService. - MockHistoryService* mock_history_service_; - CancelableRequestConsumerTSimple<size_t> cancelable_consumer_; - mutable Lock lock_; + // The top sites data. + scoped_ptr<TopSitesCache> cache_; - // The cached version of the top sites. The 0th item in this vector is the - // #1 site. - MostVisitedURLList top_sites_; + // Copy of the top sites data that may be accessed on any thread (assuming + // you hold |lock_|). The data in |thread_safe_cache_| has blacklisted and + // pinned urls applied (|cache_| does not). + scoped_ptr<TopSitesCache> thread_safe_cache_; - // The images corresponding to the top_sites. This is indexed by the URL of - // the top site, so this doesn't have to be shuffled around when the ordering - // changes of the top sites. Some top_sites_ entries may not have images. - std::map<GURL, Images> top_images_; + Profile* profile_; + + // Lock used to access |thread_safe_cache_|. + mutable Lock lock_; - // Generated from the redirects to and from the most visited pages, this - // maps the redirects to the index into top_sites_ that contains it. - std::map<GURL, size_t> canonical_urls_; + CancelableRequestConsumer cancelable_consumer_; - // Timer for updating TopSites data. + // Timer that asks history for the top sites. This is used to make sure our + // data stays in sync with history. base::OneShotTimer<TopSites> timer_; - scoped_ptr<TopSitesDatabase> db_; - FilePath db_path_; + // The time we started |timer_| at. Only valid if |timer_| is running. + base::TimeTicks timer_start_time_; NotificationRegistrar registrar_; // The number of URLs changed on the last update. size_t last_num_urls_changed_; - // Are we in the middle of migration from ThumbnailsDatabase to - // TopSites? - bool migration_in_progress_; - - // URLs for which we are expecting thumbnails. - std::set<GURL> migration_pending_urls_; - // The map of requests for the top sites list. Can only be // non-empty at startup. After we read the top sites from the DB, we'll // always have a cached list. PendingCallbackSet pending_callbacks_; - // Are we waiting for the top sites from HistoryService? - bool waiting_for_results_; - // Stores thumbnails for unknown pages. When SetPageThumbnail is // called, if we don't know about that URL yet and we don't have // enough Top Sites (new profile), we store it until the next - // UpdateMostVisitedURLs call. - std::map<GURL, Images> temp_thumbnails_map_; + // SetTopSites call. + TempImages temp_images_; // Blacklisted and pinned URLs are stored in Preferences. @@ -356,12 +330,20 @@ class TopSites : // PrefService. DictionaryValue* blacklist_; - // This is a dictionary for the pinned URLs for the the most visited - // part of the new tab page. Key is the URL, value is - // index where it is pinned at (may be the same as key). This is - // owned by the PrefService. + // This is a dictionary for the pinned URLs for the the most visited part of + // the new tab page. Key is the URL, value is index where it is pinned at (may + // be the same as key). This is owned by the PrefService. DictionaryValue* pinned_urls_; + // See description above HistoryLoadState. + HistoryLoadState history_state_; + + // See description above TopSitesLoadState. + TopSitesLoadState top_sites_state_; + + // Are we loaded? + bool loaded_; + DISALLOW_COPY_AND_ASSIGN(TopSites); }; diff --git a/chrome/browser/history/top_sites_backend.cc b/chrome/browser/history/top_sites_backend.cc new file mode 100644 index 0000000..eb27a5e --- /dev/null +++ b/chrome/browser/history/top_sites_backend.cc @@ -0,0 +1,153 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/history/top_sites_backend.h" + +#include "base/file_path.h" +#include "base/file_util.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/browser/history/top_sites_database.h" + +namespace history { + +TopSitesBackend::TopSitesBackend() + : db_(new TopSitesDatabase()) { +} + +void TopSitesBackend::Init(const FilePath& path) { + db_path_ = path; + BrowserThread::PostTask( + BrowserThread::DB, FROM_HERE, NewRunnableMethod( + this, &TopSitesBackend::InitDBOnDBThread, path)); +} + +void TopSitesBackend::Shutdown() { + BrowserThread::PostTask( + BrowserThread::DB, FROM_HERE, NewRunnableMethod( + this, &TopSitesBackend::ShutdownDBOnDBThread)); +} + +TopSitesBackend::Handle TopSitesBackend::GetMostVisitedThumbnails( + CancelableRequestConsumerBase* consumer, + GetMostVisitedThumbnailsCallback* callback) { + GetMostVisitedThumbnailsRequest* request = + new GetMostVisitedThumbnailsRequest(callback); + request->value = new MostVisitedThumbnails; + AddRequest(request, consumer); + BrowserThread::PostTask( + BrowserThread::DB, FROM_HERE, NewRunnableMethod( + this, + &TopSitesBackend::GetMostVisitedThumbnailsOnDBThread, + scoped_refptr<GetMostVisitedThumbnailsRequest>(request))); + return request->handle(); +} + +void TopSitesBackend::UpdateTopSites(const TopSitesDelta& delta) { + BrowserThread::PostTask( + BrowserThread::DB, FROM_HERE, NewRunnableMethod( + this, &TopSitesBackend::UpdateTopSitesOnDBThread, delta)); +} + +void TopSitesBackend::SetPageThumbnail(const MostVisitedURL& url, + int url_rank, + const Images& thumbnail) { + BrowserThread::PostTask( + BrowserThread::DB, FROM_HERE, NewRunnableMethod( + this, &TopSitesBackend::SetPageThumbnailOnDBThread, url, + url_rank, thumbnail)); +} + +void TopSitesBackend::ResetDatabase() { + BrowserThread::PostTask( + BrowserThread::DB, FROM_HERE, NewRunnableMethod( + this, &TopSitesBackend::ResetDatabaseOnDBThread, db_path_)); +} + +TopSitesBackend::Handle TopSitesBackend::DoEmptyRequest( + CancelableRequestConsumerBase* consumer, + EmptyRequestCallback* callback) { + EmptyRequestRequest* request = new EmptyRequestRequest(callback); + AddRequest(request, consumer); + BrowserThread::PostTask( + BrowserThread::DB, FROM_HERE, NewRunnableMethod( + this, + &TopSitesBackend::DoEmptyRequestOnDBThread, + scoped_refptr<EmptyRequestRequest>(request))); + return request->handle(); +} + +TopSitesBackend::~TopSitesBackend() { + DCHECK(!db_.get()); // Shutdown should have happened first (which results in + // nulling out db). +} + +void TopSitesBackend::InitDBOnDBThread(const FilePath& path) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + if (!db_->Init(path)) { + NOTREACHED() << "Failed to initialize database."; + db_.reset(); + } +} + +void TopSitesBackend::ShutdownDBOnDBThread() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + db_.reset(); +} + +void TopSitesBackend::GetMostVisitedThumbnailsOnDBThread( + scoped_refptr<GetMostVisitedThumbnailsRequest> request) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + + if (request->canceled()) + return; + + bool may_need_history_migration = false; + if (db_.get()) { + db_->GetPageThumbnails(&(request->value->most_visited), + &(request->value->url_to_images_map)); + may_need_history_migration = db_->may_need_history_migration(); + } + request->ForwardResult(GetMostVisitedThumbnailsRequest::TupleType( + request->handle(), + request->value, + may_need_history_migration)); +} + +void TopSitesBackend::UpdateTopSitesOnDBThread(const TopSitesDelta& delta) { + if (!db_.get()) + return; + + for (size_t i = 0; i < delta.deleted.size(); ++i) + db_->RemoveURL(delta.deleted[i]); + + for (size_t i = 0; i < delta.added.size(); ++i) + db_->SetPageThumbnail(delta.added[i].url, delta.added[i].rank, Images()); + + for (size_t i = 0; i < delta.moved.size(); ++i) + db_->UpdatePageRank(delta.moved[i].url, delta.moved[i].rank); +} + +void TopSitesBackend::SetPageThumbnailOnDBThread(const MostVisitedURL& url, + int url_rank, + const Images& thumbnail) { + if (!db_.get()) + return; + + db_->SetPageThumbnail(url, url_rank, thumbnail); +} + +void TopSitesBackend::ResetDatabaseOnDBThread(const FilePath& file_path) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + db_.reset(NULL); + file_util::Delete(db_path_, false); + db_.reset(new TopSitesDatabase()); + InitDBOnDBThread(db_path_); +} + +void TopSitesBackend::DoEmptyRequestOnDBThread( + scoped_refptr<EmptyRequestRequest> request) { + request->ForwardResult(EmptyRequestRequest::TupleType(request->handle())); +} + +} // namespace history diff --git a/chrome/browser/history/top_sites_backend.h b/chrome/browser/history/top_sites_backend.h new file mode 100644 index 0000000..50d0867 --- /dev/null +++ b/chrome/browser/history/top_sites_backend.h @@ -0,0 +1,106 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_HISTORY_TOP_SITES_BACKEND_H_ +#define CHROME_BROWSER_HISTORY_TOP_SITES_BACKEND_H_ +#pragma once + +#include "base/file_path.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/cancelable_request.h" +#include "chrome/browser/history/history_types.h" + +class FilePath; + +namespace history { + +class TopSitesDatabase; + +// Service used by TopSites to have db interaction happen on the DB thread. All +// public methods are invoked on the ui thread and get funneled to the DB +// thread. +class TopSitesBackend + : public base::RefCountedThreadSafe<TopSitesBackend>, + public CancelableRequestProvider { + public: + TopSitesBackend(); + + void Init(const FilePath& path); + + // Schedules the db to be shutdown. + void Shutdown(); + + // The boolean parameter indicates if the DB existed on disk or needs to be + // migrated. + typedef Callback3<Handle, scoped_refptr<MostVisitedThumbnails>, bool >::Type + GetMostVisitedThumbnailsCallback; + typedef CancelableRequest1<TopSitesBackend::GetMostVisitedThumbnailsCallback, + scoped_refptr<MostVisitedThumbnails> > + GetMostVisitedThumbnailsRequest; + + // Fetches MostVisitedThumbnails. + Handle GetMostVisitedThumbnails(CancelableRequestConsumerBase* consumer, + GetMostVisitedThumbnailsCallback* callback); + + // Updates top sites database from the specified delta. + void UpdateTopSites(const TopSitesDelta& delta); + + // Sets the thumbnail. + void SetPageThumbnail(const MostVisitedURL& url, + int url_rank, + const Images& thumbnail); + + // Deletes the database and recreates it. + void ResetDatabase(); + + typedef Callback1<Handle>::Type EmptyRequestCallback; + typedef CancelableRequest<TopSitesBackend::EmptyRequestCallback> + EmptyRequestRequest; + + // Schedules a request that does nothing on the DB thread, but then notifies + // the callback on the calling thread. This is used to make sure the db has + // finished processing a request. + Handle DoEmptyRequest(CancelableRequestConsumerBase* consumer, + EmptyRequestCallback* callback); + + private: + friend class base::RefCountedThreadSafe<TopSitesBackend>; + + ~TopSitesBackend(); + + // Invokes Init on the db_. + void InitDBOnDBThread(const FilePath& path); + + // Shuts down the db. + void ShutdownDBOnDBThread(); + + // Does the work of getting the most visted thumbnails. + void GetMostVisitedThumbnailsOnDBThread( + scoped_refptr<GetMostVisitedThumbnailsRequest> request); + + // Updates top sites. + void UpdateTopSitesOnDBThread(const TopSitesDelta& delta); + + // Sets the thumbnail. + void SetPageThumbnailOnDBThread(const MostVisitedURL& url, + int url_rank, + const Images& thumbnail); + + // Resets the database. + void ResetDatabaseOnDBThread(const FilePath& file_path); + + // Notifies the request. + void DoEmptyRequestOnDBThread(scoped_refptr<EmptyRequestRequest> request); + + FilePath db_path_; + + scoped_ptr<TopSitesDatabase> db_; + + DISALLOW_COPY_AND_ASSIGN(TopSitesBackend); +}; + +} // namespace history + +#endif // CHROME_BROWSER_HISTORY_TOP_SITES_BACKEND_H_ diff --git a/chrome/browser/history/top_sites_cache.cc b/chrome/browser/history/top_sites_cache.cc new file mode 100644 index 0000000..828b701 --- /dev/null +++ b/chrome/browser/history/top_sites_cache.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/history/top_sites_cache.h" + +#include "base/logging.h" +#include "base/ref_counted_memory.h" + +namespace history { + +TopSitesCache::TopSitesCache() { +} + +TopSitesCache::~TopSitesCache() { +} + +void TopSitesCache::SetTopSites(const MostVisitedURLList& top_sites) { + top_sites_ = top_sites; + GenerateCanonicalURLs(); +} + +void TopSitesCache::SetThumbnails(const URLToImagesMap& images) { + images_ = images; +} + +void TopSitesCache::SetPageThumbnail(const GURL& url, + RefCountedBytes* thumbnail, + const ThumbnailScore& score) { + Images& img = images_[GetCanonicalURL(url)]; + img.thumbnail = thumbnail; + img.thumbnail_score = score; +} + +Images* TopSitesCache::GetImage(const GURL& url) { + return &images_[GetCanonicalURL(url)]; +} + +bool TopSitesCache::GetPageThumbnail(const GURL& url, + scoped_refptr<RefCountedBytes>* bytes) { + std::map<GURL, Images>::const_iterator found = + images_.find(GetCanonicalURL(url)); + if (found != images_.end()) { + *bytes = found->second.thumbnail.get(); + return true; + } + return false; +} + +GURL TopSitesCache::GetCanonicalURL(const GURL& url) { + CanonicalURLs::iterator i = TopSitesCache::GetCanonicalURLsIterator(url); + return i == canonical_urls_.end() ? url : i->first.first->url; +} + +bool TopSitesCache::IsKnownURL(const GURL& url) { + return GetCanonicalURLsIterator(url) != canonical_urls_.end(); +} + +size_t TopSitesCache::GetURLIndex(const GURL& url) { + DCHECK(IsKnownURL(url)); + return GetCanonicalURLsIterator(url)->second; +} + +void TopSitesCache::RemoveUnreferencedThumbnails() { + for (URLToImagesMap::iterator i = images_.begin(); i != images_.end(); ) { + if (IsKnownURL(i->first)) { + ++i; + } else { + URLToImagesMap::iterator next_i = i; + ++next_i; + images_.erase(i); + i = next_i; + } + } +} + +void TopSitesCache::GenerateCanonicalURLs() { + canonical_urls_.clear(); + for (size_t i = 0; i < top_sites_.size(); i++) + StoreRedirectChain(top_sites_[i].redirects, i); +} + +void TopSitesCache::StoreRedirectChain(const RedirectList& redirects, + size_t destination) { + // redirects is empty if the user pinned a site and there are not enough top + // sites before the pinned site. + + // Map all the redirected URLs to the destination. + for (size_t i = 0; i < redirects.size(); i++) { + // If this redirect is already known, don't replace it with a new one. + if (!IsKnownURL(redirects[i])) { + CanonicalURLEntry entry; + entry.first = &(top_sites_[destination]); + entry.second = i; + canonical_urls_[entry] = destination; + } + } +} + +TopSitesCache::CanonicalURLs::iterator TopSitesCache::GetCanonicalURLsIterator( + const GURL& url) { + MostVisitedURL most_visited_url; + most_visited_url.redirects.push_back(url); + CanonicalURLEntry entry; + entry.first = &most_visited_url; + entry.second = 0u; + return canonical_urls_.find(entry); +} + +} // namespace history diff --git a/chrome/browser/history/top_sites_cache.h b/chrome/browser/history/top_sites_cache.h new file mode 100644 index 0000000..4c5d79a --- /dev/null +++ b/chrome/browser/history/top_sites_cache.h @@ -0,0 +1,108 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_HISTORY_TOP_SITES_CACHE_H_ +#define CHROME_BROWSER_HISTORY_TOP_SITES_CACHE_H_ +#pragma once + +#include <algorithm> +#include <map> +#include <string> + +#include "base/ref_counted.h" +#include "chrome/browser/history/history_types.h" + +class RefCountedBytes; + +namespace history { + +// TopSitesCache caches the top sites and thumbnails for TopSites. +class TopSitesCache { + public: + TopSitesCache(); + ~TopSitesCache(); + + // The top sites. + void SetTopSites(const MostVisitedURLList& top_sites); + const MostVisitedURLList& top_sites() const { return top_sites_; } + + // The thumbnails. + void SetThumbnails(const URLToImagesMap& images); + const URLToImagesMap& images() const { return images_; } + + // Set a thumbnail. + void SetPageThumbnail(const GURL& url, + RefCountedBytes* thumbnail, + const ThumbnailScore& score); + + // Returns the thumbnail as an Image for the specified url. This adds an entry + // for |url| if one has not yet been added. + Images* GetImage(const GURL& url); + + // Fetches the thumbnail for the specified url. Returns true if there is a + // thumbnail for the specified url. + bool GetPageThumbnail(const GURL& url, + scoped_refptr<RefCountedBytes>* bytes); + + // Returns the canonical URL for |url|. + GURL GetCanonicalURL(const GURL& url); + + // Returns true if |url| is known. + bool IsKnownURL(const GURL& url); + + // Returns the index into |top_sites_| for |url|. + size_t GetURLIndex(const GURL& url); + + // Removes any thumbnails that are no longer referenced by the top sites. + void RemoveUnreferencedThumbnails(); + + private: + // The entries in CanonicalURLs, see CanonicalURLs for details. The second + // argument gives the index of the URL into MostVisitedURLs redirects. + typedef std::pair<MostVisitedURL*, size_t> CanonicalURLEntry; + + // Comparator used for CanonicalURLs. + class CanonicalURLComparator { + public: + bool operator()(const CanonicalURLEntry& e1, + const CanonicalURLEntry& e2) const { + return e1.first->redirects[e1.second] < e2.first->redirects[e2.second]; + } + }; + + // This is used to map from redirect url to the MostVisitedURL the redirect is + // from. Ideally this would be map<GURL, size_t> (second param indexing into + // top_sites_), but this results in duplicating all redirect urls. As some + // sites have a lot of redirects, we instead use the MostVisitedURL* and the + // index of the redirect as the key, and the index into top_sites_ as the + // value. This way we aren't duplicating GURLs. CanonicalURLComparator + // enforces the ordering as if we were using GURLs. + typedef std::map<CanonicalURLEntry, size_t, + CanonicalURLComparator> CanonicalURLs; + + // Generates the set of canonical urls from |top_sites_|. + void GenerateCanonicalURLs(); + + // Stores a set of redirects. This is used by GenerateCanonicalURLs. + void StoreRedirectChain(const RedirectList& redirects, size_t destination); + + // Returns the iterator into canconical_urls_ for the specified url. + CanonicalURLs::iterator GetCanonicalURLsIterator(const GURL& url); + + // The top sites. + MostVisitedURLList top_sites_; + + // The images. These map from canonical url to image. + URLToImagesMap images_; + + // Generated from the redirects to and from the most visited pages. See + // description above typedef for details. + CanonicalURLs canonical_urls_; + + DISALLOW_COPY_AND_ASSIGN(TopSitesCache); +}; + +} // namespace history + +#endif // CHROME_BROWSER_HISTORY_TOP_SITES_CACHE_H_ diff --git a/chrome/browser/history/top_sites_database.cc b/chrome/browser/history/top_sites_database.cc index 05a588a..8bfbcaf 100644 --- a/chrome/browser/history/top_sites_database.cc +++ b/chrome/browser/history/top_sites_database.cc @@ -2,7 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "app/sql/connection.h" #include "app/sql/transaction.h" +#include "base/file_util.h" #include "base/string_util.h" #include "chrome/browser/diagnostics/sqlite_diagnostics.h" #include "chrome/browser/history/history_types.h" @@ -11,56 +13,85 @@ namespace history { -TopSitesDatabaseImpl::TopSitesDatabaseImpl() { +static const int kVersionNumber = 1; + +TopSitesDatabase::TopSitesDatabase() : may_need_history_migration_(false) { } -TopSitesDatabaseImpl::~TopSitesDatabaseImpl() { +TopSitesDatabase::~TopSitesDatabase() { } -bool TopSitesDatabaseImpl::Init(const FilePath& db_name) { - // Settings copied from ThumbnailDatabase. - db_.set_error_delegate(GetErrorHandlerForThumbnailDb()); - db_.set_page_size(4096); - db_.set_cache_size(64); +bool TopSitesDatabase::Init(const FilePath& db_name) { + bool file_existed = file_util::PathExists(db_name); + + if (!file_existed) + may_need_history_migration_ = true; - if (!db_.Open(db_name)) { - LOG(WARNING) << db_.GetErrorMessage(); + db_.reset(CreateDB(db_name)); + if (!db_.get()) return false; + + bool does_meta_exist = sql::MetaTable::DoesTableExist(db_.get()); + if (!does_meta_exist && file_existed) { + may_need_history_migration_ = true; + + // If the meta file doesn't exist, this version is old. We could remove all + // the entries as they are no longer applicable, but it's safest to just + // remove the file and start over. + db_.reset(NULL); + if (!file_util::Delete(db_name, false) && + !file_util::Delete(db_name, false)) { + // Try to delete twice. If we can't, fail. + LOG(ERROR) << "unable to delete old TopSites file"; + return false; + } + db_.reset(CreateDB(db_name)); + if (!db_.get()) + return false; } - return InitThumbnailTable(); + if (!meta_table_.Init(db_.get(), kVersionNumber, kVersionNumber)) + return false; + + if (!InitThumbnailTable()) + return false; + + // Version check. + if (meta_table_.GetVersionNumber() != kVersionNumber) + return false; + + return true; } -bool TopSitesDatabaseImpl::InitThumbnailTable() { - if (!db_.DoesTableExist("thumbnails")) { - if (!db_.Execute("CREATE TABLE thumbnails (" - "url LONGVARCHAR PRIMARY KEY," - "url_rank INTEGER ," - "title LONGVARCHAR," - "thumbnail BLOB," - "redirects LONGVARCHAR," - "boring_score DOUBLE DEFAULT 1.0, " - "good_clipping INTEGER DEFAULT 0, " - "at_top INTEGER DEFAULT 0, " - "last_updated INTEGER DEFAULT 0) ")) { - LOG(WARNING) << db_.GetErrorMessage(); +bool TopSitesDatabase::InitThumbnailTable() { + if (!db_->DoesTableExist("thumbnails")) { + if (!db_->Execute("CREATE TABLE thumbnails (" + "url LONGVARCHAR PRIMARY KEY," + "url_rank INTEGER ," + "title LONGVARCHAR," + "thumbnail BLOB," + "redirects LONGVARCHAR," + "boring_score DOUBLE DEFAULT 1.0, " + "good_clipping INTEGER DEFAULT 0, " + "at_top INTEGER DEFAULT 0, " + "last_updated INTEGER DEFAULT 0) ")) { + LOG(WARNING) << db_->GetErrorMessage(); return false; } } return true; } -void TopSitesDatabaseImpl::GetPageThumbnails(MostVisitedURLList* urls, - std::map<GURL, - Images>* thumbnails) { - sql::Statement statement(db_.GetCachedStatement( +void TopSitesDatabase::GetPageThumbnails(MostVisitedURLList* urls, + URLToImagesMap* thumbnails) { + sql::Statement statement(db_->GetCachedStatement( SQL_FROM_HERE, "SELECT url, url_rank, title, thumbnail, redirects, " "boring_score, good_clipping, at_top, last_updated " "FROM thumbnails ORDER BY url_rank ")); if (!statement) { - LOG(WARNING) << db_.GetErrorMessage(); + LOG(WARNING) << db_->GetErrorMessage(); return; } @@ -92,7 +123,7 @@ void TopSitesDatabaseImpl::GetPageThumbnails(MostVisitedURLList* urls, } // static -std::string TopSitesDatabaseImpl::GetRedirects(const MostVisitedURL& url) { +std::string TopSitesDatabase::GetRedirects(const MostVisitedURL& url) { std::vector<std::string> redirects; for (size_t i = 0; i < url.redirects.size(); i++) redirects.push_back(url.redirects[i].spec()); @@ -100,18 +131,18 @@ std::string TopSitesDatabaseImpl::GetRedirects(const MostVisitedURL& url) { } // static -void TopSitesDatabaseImpl::SetRedirects(const std::string& redirects, - MostVisitedURL* url) { +void TopSitesDatabase::SetRedirects(const std::string& redirects, + MostVisitedURL* url) { std::vector<std::string> redirects_vector; SplitStringAlongWhitespace(redirects, &redirects_vector); for (size_t i = 0; i < redirects_vector.size(); i++) url->redirects.push_back(GURL(redirects_vector[i])); } -void TopSitesDatabaseImpl::SetPageThumbnail(const MostVisitedURL& url, +void TopSitesDatabase::SetPageThumbnail(const MostVisitedURL& url, int new_rank, const Images& thumbnail) { - sql::Transaction transaction(&db_); + sql::Transaction transaction(db_.get()); transaction.Begin(); int rank = GetURLRank(url); @@ -125,9 +156,9 @@ void TopSitesDatabaseImpl::SetPageThumbnail(const MostVisitedURL& url, transaction.Commit(); } -void TopSitesDatabaseImpl::UpdatePageThumbnail( +void TopSitesDatabase::UpdatePageThumbnail( const MostVisitedURL& url, const Images& thumbnail) { - sql::Statement statement(db_.GetCachedStatement( + sql::Statement statement(db_->GetCachedStatement( SQL_FROM_HERE, "UPDATE thumbnails SET " "title = ?, thumbnail = ?, redirects = ?, " @@ -137,9 +168,9 @@ void TopSitesDatabaseImpl::UpdatePageThumbnail( return; statement.BindString16(0, url.title); - if (thumbnail.thumbnail.get()) { - statement.BindBlob(1, &thumbnail.thumbnail->data.front(), - static_cast<int>(thumbnail.thumbnail->data.size())); + if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) { + statement.BindBlob(1, thumbnail.thumbnail->front(), + static_cast<int>(thumbnail.thumbnail->size())); } statement.BindString(2, GetRedirects(url)); const ThumbnailScore& score = thumbnail.thumbnail_score; @@ -149,15 +180,15 @@ void TopSitesDatabaseImpl::UpdatePageThumbnail( statement.BindInt64(6, score.time_at_snapshot.ToInternalValue()); statement.BindString(7, url.url.spec()); if (!statement.Run()) - NOTREACHED() << db_.GetErrorMessage(); + NOTREACHED() << db_->GetErrorMessage(); } -void TopSitesDatabaseImpl::AddPageThumbnail(const MostVisitedURL& url, +void TopSitesDatabase::AddPageThumbnail(const MostVisitedURL& url, int new_rank, const Images& thumbnail) { int count = GetRowCount(); - sql::Statement statement(db_.GetCachedStatement( + sql::Statement statement(db_->GetCachedStatement( SQL_FROM_HERE, "INSERT OR REPLACE INTO thumbnails " "(url, url_rank, title, thumbnail, redirects, " @@ -169,9 +200,9 @@ void TopSitesDatabaseImpl::AddPageThumbnail(const MostVisitedURL& url, statement.BindString(0, url.url.spec()); statement.BindInt(1, count); // Make it the last url. statement.BindString16(2, url.title); - if (thumbnail.thumbnail.get()) { - statement.BindBlob(3, &thumbnail.thumbnail->data.front(), - static_cast<int>(thumbnail.thumbnail->data.size())); + if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) { + statement.BindBlob(3, thumbnail.thumbnail->front(), + static_cast<int>(thumbnail.thumbnail->size())); } statement.BindString(4, GetRedirects(url)); const ThumbnailScore& score = thumbnail.thumbnail_score; @@ -180,21 +211,21 @@ void TopSitesDatabaseImpl::AddPageThumbnail(const MostVisitedURL& url, statement.BindBool(7, score.at_top); statement.BindInt64(8, score.time_at_snapshot.ToInternalValue()); if (!statement.Run()) - NOTREACHED() << db_.GetErrorMessage(); + NOTREACHED() << db_->GetErrorMessage(); UpdatePageRankNoTransaction(url, new_rank); } -void TopSitesDatabaseImpl::UpdatePageRank(const MostVisitedURL& url, +void TopSitesDatabase::UpdatePageRank(const MostVisitedURL& url, int new_rank) { - sql::Transaction transaction(&db_); + sql::Transaction transaction(db_.get()); transaction.Begin(); UpdatePageRankNoTransaction(url, new_rank); transaction.Commit(); } // Caller should have a transaction open. -void TopSitesDatabaseImpl::UpdatePageRankNoTransaction( +void TopSitesDatabase::UpdatePageRankNoTransaction( const MostVisitedURL& url, int new_rank) { int prev_rank = GetURLRank(url); if (prev_rank == -1) { @@ -205,7 +236,7 @@ void TopSitesDatabaseImpl::UpdatePageRankNoTransaction( // Shift the ranks. if (prev_rank > new_rank) { // Shift up - sql::Statement shift_statement(db_.GetCachedStatement( + sql::Statement shift_statement(db_->GetCachedStatement( SQL_FROM_HERE, "UPDATE thumbnails " "SET url_rank = url_rank + 1 " @@ -216,7 +247,7 @@ void TopSitesDatabaseImpl::UpdatePageRankNoTransaction( shift_statement.Run(); } else if (prev_rank < new_rank) { // Shift down - sql::Statement shift_statement(db_.GetCachedStatement( + sql::Statement shift_statement(db_->GetCachedStatement( SQL_FROM_HERE, "UPDATE thumbnails " "SET url_rank = url_rank - 1 " @@ -228,7 +259,7 @@ void TopSitesDatabaseImpl::UpdatePageRankNoTransaction( } // Set the url's rank. - sql::Statement set_statement(db_.GetCachedStatement( + sql::Statement set_statement(db_->GetCachedStatement( SQL_FROM_HERE, "UPDATE thumbnails " "SET url_rank = ? " @@ -239,15 +270,15 @@ void TopSitesDatabaseImpl::UpdatePageRankNoTransaction( set_statement.Run(); } -bool TopSitesDatabaseImpl::GetPageThumbnail(const GURL& url, +bool TopSitesDatabase::GetPageThumbnail(const GURL& url, Images* thumbnail) { - sql::Statement statement(db_.GetCachedStatement( + sql::Statement statement(db_->GetCachedStatement( SQL_FROM_HERE, "SELECT thumbnail, boring_score, good_clipping, at_top, last_updated " "FROM thumbnails WHERE url=?")); if (!statement) { - LOG(WARNING) << db_.GetErrorMessage(); + LOG(WARNING) << db_->GetErrorMessage(); return false; } @@ -266,13 +297,13 @@ bool TopSitesDatabaseImpl::GetPageThumbnail(const GURL& url, return true; } -int TopSitesDatabaseImpl::GetRowCount() { +int TopSitesDatabase::GetRowCount() { int result = 0; - sql::Statement select_statement(db_.GetCachedStatement( + sql::Statement select_statement(db_->GetCachedStatement( SQL_FROM_HERE, "SELECT COUNT (url) FROM thumbnails")); if (!select_statement) { - LOG(WARNING) << db_.GetErrorMessage(); + LOG(WARNING) << db_->GetErrorMessage(); return result; } @@ -282,14 +313,14 @@ int TopSitesDatabaseImpl::GetRowCount() { return result; } -int TopSitesDatabaseImpl::GetURLRank(const MostVisitedURL& url) { +int TopSitesDatabase::GetURLRank(const MostVisitedURL& url) { int result = -1; - sql::Statement select_statement(db_.GetCachedStatement( + sql::Statement select_statement(db_->GetCachedStatement( SQL_FROM_HERE, "SELECT url_rank " "FROM thumbnails WHERE url=?")); if (!select_statement) { - LOG(WARNING) << db_.GetErrorMessage(); + LOG(WARNING) << db_->GetErrorMessage(); return result; } @@ -301,15 +332,15 @@ int TopSitesDatabaseImpl::GetURLRank(const MostVisitedURL& url) { } // Remove the record for this URL. Returns true iff removed successfully. -bool TopSitesDatabaseImpl::RemoveURL(const MostVisitedURL& url) { +bool TopSitesDatabase::RemoveURL(const MostVisitedURL& url) { int old_rank = GetURLRank(url); if (old_rank < 0) return false; - sql::Transaction transaction(&db_); + sql::Transaction transaction(db_.get()); transaction.Begin(); // Decrement all following ranks. - sql::Statement shift_statement(db_.GetCachedStatement( + sql::Statement shift_statement(db_->GetCachedStatement( SQL_FROM_HERE, "UPDATE thumbnails " "SET url_rank = url_rank - 1 " @@ -320,8 +351,8 @@ bool TopSitesDatabaseImpl::RemoveURL(const MostVisitedURL& url) { shift_statement.Run(); sql::Statement delete_statement( - db_.GetCachedStatement(SQL_FROM_HERE, - "DELETE FROM thumbnails WHERE url = ?")); + db_->GetCachedStatement(SQL_FROM_HERE, + "DELETE FROM thumbnails WHERE url = ?")); if (!delete_statement) return false; delete_statement.BindString(0, url.url.spec()); @@ -330,4 +361,19 @@ bool TopSitesDatabaseImpl::RemoveURL(const MostVisitedURL& url) { return transaction.Commit(); } +sql::Connection* TopSitesDatabase::CreateDB(const FilePath& db_name) { + scoped_ptr<sql::Connection> db(new sql::Connection()); + // Settings copied from ThumbnailDatabase. + db->set_error_delegate(GetErrorHandlerForThumbnailDb()); + db->set_page_size(4096); + db->set_cache_size(32); + + if (!db->Open(db_name)) { + LOG(ERROR) << db->GetErrorMessage(); + return NULL; + } + + return db.release(); +} + } // namespace history diff --git a/chrome/browser/history/top_sites_database.h b/chrome/browser/history/top_sites_database.h index edfb4e5..8450889 100644 --- a/chrome/browser/history/top_sites_database.h +++ b/chrome/browser/history/top_sites_database.h @@ -9,94 +9,61 @@ #include <map> #include <string> -#include "app/sql/connection.h" +#include "app/sql/meta_table.h" #include "base/ref_counted.h" +#include "chrome/browser/history/history_types.h" #include "chrome/browser/history/url_database.h" // For DBCloseScoper. class FilePath; class RefCountedMemory; class SkBitmap; -class Images; -namespace base { -class Time; +namespace app { +class Connection; } namespace history { -// Interface to be implemented by the real storage layer as well as -// the mockup database for testing. class TopSitesDatabase { public: - virtual ~TopSitesDatabase() {} - virtual bool Init(const FilePath& filename) { - return true; - } - - // Returns a list of all URLs currently in the table. - virtual void GetPageThumbnails(MostVisitedURLList* urls, - std::map<GURL, - Images>* thumbnails) = 0; - - // Set a thumbnail for a URL. |url_rank| is the position of the URL - // in the list of TopURLs, zero-based. - // If the URL is not in the table, add it. If it is, replace its - // thumbnail. - virtual void SetPageThumbnail(const MostVisitedURL& url, - int url_rank, - const Images& thumbnail) = 0; - - // Update rank of a URL that's already in the database. - virtual void UpdatePageRank(const MostVisitedURL& url, int new_rank) = 0; - - // Convenience wrapper. - bool GetPageThumbnail(const MostVisitedURL& url, - Images* thumbnail) { - return GetPageThumbnail(url.url, thumbnail); - } - - // Get a thumbnail for a given page. Returns true iff we have the thumbnail. - virtual bool GetPageThumbnail(const GURL& url, - Images* thumbnail) = 0; - - // Remove the record for this URL. Returns true iff removed successfully. - virtual bool RemoveURL(const MostVisitedURL& url) = 0; -}; - -class TopSitesDatabaseImpl : public TopSitesDatabase { - public: - TopSitesDatabaseImpl(); - virtual ~TopSitesDatabaseImpl(); + TopSitesDatabase(); + ~TopSitesDatabase(); // Must be called after creation but before any other methods are called. // Returns true on success. If false, no other functions should be called. - virtual bool Init(const FilePath& db_name); + bool Init(const FilePath& db_name); + + // Returns true if migration of top sites from history may be needed. A value + // of true means either migration is definitely needed (the top sites file is + // old) or doesn't exist (as would happen for a new user). + bool may_need_history_migration() const { + return may_need_history_migration_; + } // Thumbnails ---------------------------------------------------------------- // Returns a list of all URLs currently in the table. // WARNING: clears both input arguments. - virtual void GetPageThumbnails(MostVisitedURLList* urls, - std::map<GURL, Images>* thumbnails); + void GetPageThumbnails(MostVisitedURLList* urls, + std::map<GURL, Images>* thumbnails); // Set a thumbnail for a URL. |url_rank| is the position of the URL // in the list of TopURLs, zero-based. // If the URL is not in the table, add it. If it is, replace its // thumbnail and rank. Shift the ranks of other URLs if necessary. - virtual void SetPageThumbnail(const MostVisitedURL& url, - int new_rank, - const Images& thumbnail); + void SetPageThumbnail(const MostVisitedURL& url, + int new_rank, + const Images& thumbnail); // Sets the rank for a given URL. The URL must be in the database. // Use SetPageThumbnail if it's not. - virtual void UpdatePageRank(const MostVisitedURL& url, int new_rank); + void UpdatePageRank(const MostVisitedURL& url, int new_rank); // Get a thumbnail for a given page. Returns true iff we have the thumbnail. - virtual bool GetPageThumbnail(const GURL& url, - Images* thumbnail); + bool GetPageThumbnail(const GURL& url, Images* thumbnail); // Remove the record for this URL. Returns true iff removed successfully. - virtual bool RemoveURL(const MostVisitedURL& url); + bool RemoveURL(const MostVisitedURL& url); private: // Creates the thumbnail table, returning true if the table already exists @@ -121,13 +88,21 @@ class TopSitesDatabaseImpl : public TopSitesDatabase { // Returns the number of URLs (rows) in the database. int GetRowCount(); + sql::Connection* CreateDB(const FilePath& db_name); + // Encodes redirects into a string. static std::string GetRedirects(const MostVisitedURL& url); // Decodes redirects from a string and sets them for the url. static void SetRedirects(const std::string& redirects, MostVisitedURL* url); - sql::Connection db_; + scoped_ptr<sql::Connection> db_; + sql::MetaTable meta_table_; + + // See description above class. + bool may_need_history_migration_; + + DISALLOW_COPY_AND_ASSIGN(TopSitesDatabase); }; } // namespace history diff --git a/chrome/browser/history/top_sites_unittest.cc b/chrome/browser/history/top_sites_unittest.cc index 160b275..f908646 100644 --- a/chrome/browser/history/top_sites_unittest.cc +++ b/chrome/browser/history/top_sites_unittest.cc @@ -3,18 +3,26 @@ // found in the LICENSE file. #include "app/l10n_util.h" +#include "base/command_line.h" #include "base/file_util.h" +#include "base/path_service.h" #include "base/scoped_temp_dir.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/browser_thread.h" -#include "chrome/browser/history/top_sites.h" #include "chrome/browser/dom_ui/most_visited_handler.h" +#include "chrome/browser/history/history_backend.h" +#include "chrome/browser/history/history_database.h" #include "chrome/browser/history/history_marshaling.h" -#include "chrome/browser/history/top_sites_database.h" #include "chrome/browser/history/history_notifications.h" +#include "chrome/browser/history/top_sites.h" +#include "chrome/browser/history/top_sites_backend.h" +#include "chrome/browser/history/top_sites_cache.h" +#include "chrome/browser/history/top_sites_database.h" +#include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" #include "chrome/test/testing_profile.h" #include "chrome/tools/profiles/thumbnail-inl.h" #include "gfx/codec/jpeg_codec.h" @@ -25,246 +33,362 @@ #include "testing/gtest/include/gtest/gtest.h" #include "third_party/skia/include/core/SkBitmap.h" - namespace history { -static const unsigned char kBlob[] = - "12346102356120394751634516591348710478123649165419234519234512349134"; +namespace { -class TopSitesTest : public testing::Test { +// Used by WaitForHistory, see it for details. +class WaitForHistoryTask : public HistoryDBTask { public: - TopSitesTest() : number_of_callbacks_(0) { + WaitForHistoryTask() {} + + virtual bool RunOnDBThread(HistoryBackend* backend, HistoryDatabase* db) { + return true; } - ~TopSitesTest() { + + virtual void DoneRunOnMainThread() { + MessageLoop::current()->Quit(); } - TopSites& top_sites() { return *top_sites_; } - MostVisitedURLList& urls() { return urls_; } - Profile& profile() {return *profile_;} - FilePath& file_name() { return file_name_; } - RefCountedBytes* google_thumbnail() { return google_thumbnail_; } - RefCountedBytes* random_thumbnail() { return random_thumbnail_; } - RefCountedBytes* weewar_thumbnail() { return weewar_thumbnail_; } - CancelableRequestConsumer* consumer() { return &consumer_; } - size_t number_of_callbacks() {return number_of_callbacks_; } - // Prepopulated URLs - added at the back of TopSites. - GURL welcome_url() { - return GURL(l10n_util::GetStringUTF8(IDS_CHROME_WELCOME_URL)); + private: + DISALLOW_COPY_AND_ASSIGN(WaitForHistoryTask); +}; + +// Used for querying top sites. Either runs sequentially, or runs a nested +// nested message loop until the response is complete. The later is used when +// TopSites is queried before it finishes loading. +class TopSitesQuerier { + public: + TopSitesQuerier() : number_of_callbacks_(0), waiting_(false) {} + + // Queries top sites. If |wait| is true a nested message loop is run until the + // callback is notified. + void QueryTopSites(TopSites* top_sites, bool wait) { + int start_number_of_callbacks = number_of_callbacks_; + top_sites->GetMostVisitedURLs( + &consumer_, + NewCallback(this, &TopSitesQuerier::OnTopSitesAvailable)); + if (wait && start_number_of_callbacks == number_of_callbacks_) { + waiting_ = true; + MessageLoop::current()->Run(); + } } - GURL themes_url() { - return GURL(l10n_util::GetStringUTF8(IDS_THEMES_GALLERY_URL)); + + void CancelRequest() { + consumer_.CancelAllRequests(); + } + + void set_urls(const MostVisitedURLList& urls) { urls_ = urls; } + const MostVisitedURLList& urls() const { return urls_; } + + int number_of_callbacks() const { return number_of_callbacks_; } + + private: + // Callback for TopSites::GetMostVisitedURLs. + void OnTopSitesAvailable(const history::MostVisitedURLList& data) { + urls_ = data; + number_of_callbacks_++; + if (waiting_) { + MessageLoop::current()->Quit(); + waiting_ = false; + } + } + + CancelableRequestConsumer consumer_; + MostVisitedURLList urls_; + int number_of_callbacks_; + bool waiting_; + + DISALLOW_COPY_AND_ASSIGN(TopSitesQuerier); +}; + +// Extracts the data from |t1| into a SkBitmap. This is intended for usage of +// thumbnail data, which is stored as jpgs. +SkBitmap ExtractThumbnail(const RefCountedBytes& t1) { + scoped_ptr<SkBitmap> image(gfx::JPEGCodec::Decode(t1.front(), + t1.data.size())); + return image.get() ? *image : SkBitmap(); +} + +// Returns true if t1 and t2 contain the same data. +bool ThumbnailsAreEqual(RefCountedBytes* t1, RefCountedBytes* t2) { + if (!t1 || !t2) + return false; + if (t1->data.size() != t2->data.size()) + return false; + return std::equal(t1->data.begin(), + t1->data.end(), + t2->data.begin()); +} + +} // namespace + +class TopSitesTest : public testing::Test { + public: + TopSitesTest() + : ui_thread_(BrowserThread::UI, &message_loop_), + db_thread_(BrowserThread::DB, &message_loop_), + original_command_line_(*CommandLine::ForCurrentProcess()) { } virtual void SetUp() { + CommandLine::ForCurrentProcess()->AppendSwitch(switches::kEnableTopSites); profile_.reset(new TestingProfile); - top_sites_ = new TopSites(profile_.get()); - - ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - file_name_ = temp_dir_.path().AppendASCII("TopSites.db"); - EXPECT_TRUE(file_util::Delete(file_name_, false)); - - std::vector<unsigned char> random_data(kBlob, kBlob + sizeof(kBlob)); - std::vector<unsigned char> google_data(kGoogleThumbnail, - kGoogleThumbnail + - sizeof(kGoogleThumbnail)); - std::vector<unsigned char> weewar_data(kWeewarThumbnail, - kWeewarThumbnail + - sizeof(kWeewarThumbnail)); - random_thumbnail_ = new RefCountedBytes(random_data); - google_thumbnail_ = new RefCountedBytes(google_data); - weewar_thumbnail_ = new RefCountedBytes(weewar_data); + if (CreateHistoryAndTopSites()) { + profile_->CreateHistoryService(false, false); + profile_->CreateTopSites(); + profile_->BlockUntilTopSitesLoaded(); + } } virtual void TearDown() { profile_.reset(); - TopSites::DeleteTopSites(top_sites_); - EXPECT_TRUE(file_util::Delete(file_name_, false)); + *CommandLine::ForCurrentProcess() = original_command_line_; } - // Callback for TopSites::GetMostVisitedURLs. - void OnTopSitesAvailable(history::MostVisitedURLList data) { - urls_ = data; - number_of_callbacks_++; + // Returns true if history and top sites should be created in SetUp. + virtual bool CreateHistoryAndTopSites() { + return true; } - // Wrappers that allow private TopSites functions to be called from the - // individual tests without making them all be friends. - GURL GetCanonicalURL(const GURL& url) const { - AutoLock lock(top_sites_->lock_); // The function asserts it's in the lock. - return top_sites_->GetCanonicalURL(url); + // Gets the thumbnail for |url| from TopSites. + SkBitmap GetThumbnail(const GURL& url) { + scoped_refptr<RefCountedBytes> data; + return top_sites()->GetPageThumbnail(url, &data) ? + ExtractThumbnail(*data.get()) : SkBitmap(); } - void StoreMostVisited(std::vector<MostVisitedURL>* urls) { - top_sites_->StoreMostVisited(urls); + // Creates a bitmap of the specified color. + SkBitmap CreateBitmap(SkColor color) { + SkBitmap thumbnail; + thumbnail.setConfig(SkBitmap::kARGB_8888_Config, 4, 4); + thumbnail.allocPixels(); + thumbnail.eraseColor(color); + return thumbnail; } - static void DiffMostVisited(const std::vector<MostVisitedURL>& old_list, - const std::vector<MostVisitedURL>& new_list, - std::vector<size_t>* added_urls, - std::vector<size_t>* deleted_urls, - std::vector<size_t>* moved_urls) { - TopSites::DiffMostVisited(old_list, new_list, - added_urls, deleted_urls, moved_urls); + // Forces top sites to load top sites from history, then recreates top sites. + // Recreating top sites makes sure the changes from history are saved and + // loaded from the db. + void RefreshTopSitesAndRecreate() { + StartQueryForMostVisited(); + WaitForHistory(); + RecreateTopSitesAndBlock(); } - Lock& lock() { - return top_sites_->lock_; + // Blocks the caller until history processes a task. This is useful if you + // need to wait until you know history has processed a task. + void WaitForHistory() { + history_service()->ScheduleDBTask(new WaitForHistoryTask(), &consumer_); + MessageLoop::current()->Run(); } - private: - scoped_refptr<TopSites> top_sites_; - MostVisitedURLList urls_; - size_t number_of_callbacks_; - scoped_ptr<TestingProfile> profile_; - ScopedTempDir temp_dir_; - FilePath file_name_; // Database filename. - scoped_refptr<RefCountedBytes> google_thumbnail_; - scoped_refptr<RefCountedBytes> random_thumbnail_; - scoped_refptr<RefCountedBytes> weewar_thumbnail_; - MessageLoop message_loop_; - CancelableRequestConsumer consumer_; + // Waits for top sites to finish processing a task. This is useful if you need + // to wait until top sites finishes processing a task. + void WaitForTopSites() { + top_sites()->backend_->DoEmptyRequest( + &consumer_, + NewCallback(this, &TopSitesTest::QuitCallback)); + MessageLoop::current()->Run(); + } - DISALLOW_COPY_AND_ASSIGN(TopSitesTest); -}; + TopSites* top_sites() { return profile_->GetTopSites(); } + CancelableRequestConsumer* consumer() { return &consumer_; } + TestingProfile* profile() {return profile_.get();} + HistoryService* history_service() { + return profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + } + + MostVisitedURLList GetPrepopulatePages() { + return TopSites::GetPrepopulatePages(); + } + // Returns true if the TopSitesQuerier contains the prepopulate data starting + // at |start_index|. + void ContainsPrepopulatePages(const TopSitesQuerier& querier, + size_t start_index) { + MostVisitedURLList prepopulate_urls = GetPrepopulatePages(); + ASSERT_LE(start_index + prepopulate_urls.size(), querier.urls().size()); + for (size_t i = 0; i < prepopulate_urls.size(); ++i) { + EXPECT_EQ(prepopulate_urls[i].url.spec(), + querier.urls()[start_index + i].url.spec()) << " @ index " << + i; + } + } -// A mockup of a HistoryService used for testing TopSites. -class MockHistoryServiceImpl : public TopSites::MockHistoryService { - public: - MockHistoryServiceImpl() : num_thumbnail_requests_(0) {} - - // Calls the callback directly with the results. - HistoryService::Handle QueryMostVisitedURLs( - int result_count, int days_back, - CancelableRequestConsumerBase* consumer, - HistoryService::QueryMostVisitedURLsCallback* callback) { - callback->Run(CancelableRequestProvider::Handle(0), // Handle is unused. - most_visited_urls_); - delete callback; - return 0; + // Used for callbacks from history. + void EmptyCallback() { } - // Add a page to the end of the pages list. - void AppendMockPage(const GURL& url, - const string16& title) { - MostVisitedURL page; - page.url = url; - page.title = title; - page.redirects = RedirectList(); - page.redirects.push_back(url); - most_visited_urls_.push_back(page); + // Quit the current message loop when invoked. Useful when running a nested + // message loop. + void QuitCallback(TopSitesBackend::Handle handle) { + MessageLoop::current()->Quit(); } - // Removes the last URL in the list. - void RemoveMostVisitedURL() { - most_visited_urls_.pop_back(); + // Adds a page to history. + void AddPageToHistory(const GURL& url) { + RedirectList redirects; + redirects.push_back(url); + history_service()->AddPage( + url, static_cast<void*>(this), 0, GURL(), PageTransition::TYPED, + redirects, history::SOURCE_BROWSED, false); } - virtual void GetPageThumbnail( - const GURL& url, - CancelableRequestConsumerTSimple<size_t>* consumer, - HistoryService::ThumbnailDataCallback* callback, - size_t index) { - num_thumbnail_requests_++; - MostVisitedURL mvu; - mvu.url = url; - MostVisitedURLList::iterator pos = std::find(most_visited_urls_.begin(), - most_visited_urls_.end(), - mvu); - EXPECT_TRUE(pos != most_visited_urls_.end()) << url.spec(); - scoped_refptr<RefCountedBytes> thumbnail; - callback->Run(index, thumbnail); - delete callback; + // Adds a page to history. + void AddPageToHistory(const GURL& url, const string16& title) { + RedirectList redirects; + redirects.push_back(url); + history_service()->AddPage( + url, static_cast<void*>(this), 0, GURL(), PageTransition::TYPED, + redirects, history::SOURCE_BROWSED, false); + history_service()->SetPageTitle(url, title); } - void ResetNumberOfThumbnailRequests() { - num_thumbnail_requests_ = 0; + // Adds a page to history. + void AddPageToHistory(const GURL& url, + const string16& title, + const history::RedirectList& redirects, + base::Time time) { + history_service()->AddPage( + url, time, static_cast<void*>(this), 0, GURL(), PageTransition::TYPED, + redirects, history::SOURCE_BROWSED, false); + history_service()->SetPageTitle(url, title); } - int GetNumberOfThumbnailRequests() { - return num_thumbnail_requests_; + // Delets a url. + void DeleteURL(const GURL& url) { + history_service()->DeleteURL(url); } - private: - MostVisitedURLList most_visited_urls_; - int num_thumbnail_requests_; // Number of calls to GetPageThumbnail. -}; + // Returns true if the thumbnail equals the specified bytes. + bool ThumbnailEqualsBytes(const SkBitmap& image, RefCountedBytes* bytes) { + scoped_refptr<RefCountedBytes> encoded_image; + EncodeBitmap(image, &encoded_image); + return ThumbnailsAreEqual(encoded_image, bytes); + } + // Recreates top sites. This forces top sites to reread from the db. + void RecreateTopSitesAndBlock() { + // Recreate TopSites and wait for it to load. + profile()->CreateTopSites(); + // As history already loaded we have to fake this call. + profile()->BlockUntilTopSitesLoaded(); + } -// A mockup of a TopSitesDatabase used for testing TopSites. -class MockTopSitesDatabaseImpl : public TopSitesDatabase { - public: - virtual void GetPageThumbnails(MostVisitedURLList* urls, - std::map<GURL, Images>* thumbnails) { - // Return a copy of the vector. - *urls = top_sites_list_; - *thumbnails = thumbnails_map_; + // Wrappers that allow private TopSites functions to be called from the + // individual tests without making them all be friends. + GURL GetCanonicalURL(const GURL& url) { + return top_sites()->cache_->GetCanonicalURL(url); } - virtual void SetPageThumbnail(const MostVisitedURL& url, int url_rank, - const Images& thumbnail) { - SetPageRank(url, url_rank); - // Update thubmnail - thumbnails_map_[url.url] = thumbnail; + void SetTopSites(const MostVisitedURLList& new_top_sites) { + top_sites()->SetTopSites(new_top_sites); } - virtual void UpdatePageRank(const MostVisitedURL& url, int new_rank) { - MostVisitedURLList::iterator pos = std::find(top_sites_list_.begin(), - top_sites_list_.end(), - url); - // Is it in the right position? - int rank = pos - top_sites_list_.begin(); - if (rank != new_rank) { - // Move the URL to a new position. - top_sites_list_.erase(pos); - top_sites_list_.insert(top_sites_list_.begin() + new_rank, url); - } + void StartQueryForMostVisited() { + top_sites()->StartQueryForMostVisited(); } - virtual void SetPageRank(const MostVisitedURL& url, int url_rank) { - // Check if this url is in the list, and at which position. - MostVisitedURLList::iterator pos = std::find(top_sites_list_.begin(), - top_sites_list_.end(), - url); - if (pos == top_sites_list_.end()) { - // Add it to the list. - top_sites_list_.insert(top_sites_list_.begin() + url_rank, url); - } else { - UpdatePageRank(url, url_rank); - } + bool EncodeBitmap(const SkBitmap& image, + scoped_refptr<RefCountedBytes>* bytes) { + return TopSites::EncodeBitmap(image, bytes); + } + + void SetLastNumUrlsChanged(size_t value) { + top_sites()->last_num_urls_changed_ = value; } - // Get a thumbnail for a given page. Returns true iff we have the thumbnail. - virtual bool GetPageThumbnail(const GURL& url, - Images* thumbnail) { - std::map<GURL, Images>::const_iterator found = - thumbnails_map_.find(url); - if (found == thumbnails_map_.end()) - return false; // No thumbnail for this URL. + size_t last_num_urls_changed() { return top_sites()->last_num_urls_changed_; } - thumbnail->thumbnail = found->second.thumbnail; - thumbnail->thumbnail_score = found->second.thumbnail_score; - return true; + base::TimeDelta GetUpdateDelay() { + return top_sites()->GetUpdateDelay(); } - virtual bool RemoveURL(const MostVisitedURL& url) { - // Comparison by url. - MostVisitedURLList::iterator pos = std::find(top_sites_list_.begin(), - top_sites_list_.end(), - url); - if (pos == top_sites_list_.end()) { - return false; - } - top_sites_list_.erase(pos); - thumbnails_map_.erase(url.url); - return true; + bool IsTopSitesLoaded() { return top_sites()->loaded_; } + + bool AddPrepopulatedPages(MostVisitedURLList* urls) { + return TopSites::AddPrepopulatedPages(urls); } private: - MostVisitedURLList top_sites_list_; // Keeps the URLs sorted by score (rank). - std::map<GURL, Images> thumbnails_map_; -}; + MessageLoopForUI message_loop_; + BrowserThread ui_thread_; + BrowserThread db_thread_; + scoped_ptr<TestingProfile> profile_; + CancelableRequestConsumer consumer_; + CommandLine original_command_line_; + + DISALLOW_COPY_AND_ASSIGN(TopSitesTest); +}; // Class TopSitesTest +class TopSitesMigrationTest : public TopSitesTest { + public: + TopSitesMigrationTest() {} + + virtual void SetUp() { + TopSitesTest::SetUp(); + + FilePath data_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path)); + data_path = data_path.AppendASCII("top_sites"); + + // Set up history and thumbnails as they would be before migration. + ASSERT_NO_FATAL_FAILURE( + ExecuteSQL(data_path.AppendASCII("history.19.sql"), + profile()->GetPath().Append(chrome::kHistoryFilename))); + ASSERT_NO_FATAL_FAILURE( + ExecuteSQL(data_path.AppendASCII("thumbnails.3.sql"), + profile()->GetPath().Append(chrome::kThumbnailsFilename))); + + profile()->CreateHistoryService(false, false); + profile()->CreateTopSites(); + profile()->BlockUntilTopSitesLoaded(); + } + + // Returns true if history and top sites should be created in SetUp. + virtual bool CreateHistoryAndTopSites() { + return false; + } + + protected: + // Assertions for the migration test. This is extracted into a standalone + // method so that it can be invoked twice. + void MigrationAssertions() { + TopSitesQuerier querier; + querier.QueryTopSites(top_sites(), false); + + // We shouldn't have gotten a callback. + EXPECT_EQ(1, querier.number_of_callbacks()); + + // The data we loaded should contain google and yahoo. + ASSERT_EQ(2u + GetPrepopulatePages().size(), querier.urls().size()); + EXPECT_EQ(GURL("http://google.com/"), querier.urls()[0].url); + EXPECT_EQ(GURL("http://yahoo.com/"), querier.urls()[1].url); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 2)); + + SkBitmap goog_thumbnail = GetThumbnail(GURL("http://google.com/")); + EXPECT_EQ(1, goog_thumbnail.width()); + + SkBitmap yahoo_thumbnail = GetThumbnail(GURL("http://yahoo.com/")); + EXPECT_EQ(2, yahoo_thumbnail.width()); + + // Favicon assertions are handled in ThumbnailDatabase. + } + + private: + // Executes the sql from the file |sql_path| in the database at |db_path|. + void ExecuteSQL(const FilePath& sql_path, + const FilePath& db_path) { + std::string sql; + ASSERT_TRUE(file_util::ReadFileToString(sql_path, &sql)); + sql::Connection connection; + ASSERT_TRUE(connection.Open(db_path)); + ASSERT_TRUE(connection.Execute(sql.c_str())); + } + + DISALLOW_COPY_AND_ASSIGN(TopSitesMigrationTest); +}; // Helper function for appending a URL to a vector of "most visited" URLs, // using the default values for everything but the URL. @@ -276,18 +400,6 @@ static void AppendMostVisitedURL(std::vector<MostVisitedURL>* list, list->push_back(mv); } -// Returns true if t1 and t2 contain the same data. -static bool ThumbnailsAreEqual(RefCountedBytes* t1, - RefCountedBytes* t2) { - if (!t1 || !t2) - return false; - if (t1->data.size() != t2->data.size()) - return false; - return std::equal(t1->data.begin(), - t1->data.end(), - t2->data.begin()); -} - // Same as AppendMostVisitedURL except that it adds a redirect from the first // URL to the second. static void AppendMostVisitedURLWithRedirect( @@ -300,8 +412,8 @@ static void AppendMostVisitedURLWithRedirect( list->push_back(mv); } +// Tests GetCanonicalURL. TEST_F(TopSitesTest, GetCanonicalURL) { - BrowserThread db_loop(BrowserThread::DB, MessageLoop::current()); // Have two chains: // google.com -> www.google.com // news.google.com (no redirects) @@ -312,7 +424,7 @@ TEST_F(TopSitesTest, GetCanonicalURL) { std::vector<MostVisitedURL> most_visited; AppendMostVisitedURLWithRedirect(&most_visited, source, dest); AppendMostVisitedURL(&most_visited, news); - StoreMostVisited(&most_visited); + SetTopSites(most_visited); // Random URLs not in the database are returned unchanged. GURL result = GetCanonicalURL(GURL("http://fark.com/")); @@ -331,6 +443,7 @@ TEST_F(TopSitesTest, GetCanonicalURL) { EXPECT_EQ(dest, result); } +// Tests DiffMostVisited. TEST_F(TopSitesTest, DiffMostVisited) { GURL stays_the_same("http://staysthesame/"); GURL gets_added_1("http://getsadded1/"); @@ -349,26 +462,25 @@ TEST_F(TopSitesTest, DiffMostVisited) { AppendMostVisitedURL(&new_list, gets_added_2); // 2 (added) AppendMostVisitedURL(&new_list, gets_moved_1); // 3 (moved from 2) - std::vector<size_t> added; - std::vector<size_t> deleted; - std::vector<size_t> moved; - DiffMostVisited(old_list, new_list, &added, &deleted, &moved); + history::TopSitesDelta delta; + history::TopSites::DiffMostVisited(old_list, new_list, &delta); - ASSERT_EQ(2u, added.size()); - ASSERT_EQ(1u, deleted.size()); - ASSERT_EQ(1u, moved.size()); + ASSERT_EQ(2u, delta.added.size()); + ASSERT_TRUE(gets_added_1 == delta.added[0].url.url); + ASSERT_EQ(1, delta.added[0].rank); + ASSERT_TRUE(gets_added_2 == delta.added[1].url.url); + ASSERT_EQ(2, delta.added[1].rank); - // There should be 2 URLs added, we don't assume what order they're in inside - // the result vector. - EXPECT_TRUE(added[0] == 1 || added[1] == 1); - EXPECT_TRUE(added[0] == 2 || added[1] == 2); + ASSERT_EQ(1u, delta.deleted.size()); + ASSERT_TRUE(gets_deleted_1 == delta.deleted[0].url); - EXPECT_EQ(1u, deleted[0]); - EXPECT_EQ(3u, moved[0]); + ASSERT_EQ(1u, delta.moved.size()); + ASSERT_TRUE(gets_moved_1 == delta.moved[0].url.url); + ASSERT_EQ(3, delta.moved[0].rank); } +// Tests SetPageThumbnail. TEST_F(TopSitesTest, SetPageThumbnail) { - BrowserThread db_loop(BrowserThread::DB, MessageLoop::current()); GURL url1a("http://google.com/"); GURL url1b("http://www.google.com/"); GURL url2("http://images.google.com/"); @@ -384,13 +496,10 @@ TEST_F(TopSitesTest, SetPageThumbnail) { list.push_back(mv); // Save our most visited data containing that one site. - StoreMostVisited(&list); + SetTopSites(list); // Create a dummy thumbnail. - SkBitmap thumbnail; - thumbnail.setConfig(SkBitmap::kARGB_8888_Config, 4, 4); - thumbnail.allocPixels(); - thumbnail.eraseRGB(0x00, 0x00, 0x00); + SkBitmap thumbnail(CreateBitmap(SK_ColorWHITE)); base::Time now = base::Time::Now(); ThumbnailScore low_score(1.0, true, true, now); @@ -398,96 +507,117 @@ TEST_F(TopSitesTest, SetPageThumbnail) { ThumbnailScore high_score(0.0, true, true, now); // Setting the thumbnail for invalid pages should fail. - EXPECT_FALSE(top_sites().SetPageThumbnail(invalid_url, - thumbnail, medium_score)); + EXPECT_FALSE(top_sites()->SetPageThumbnail(invalid_url, + thumbnail, medium_score)); // Setting the thumbnail for url2 should succeed, lower scores shouldn't // replace it, higher scores should. - EXPECT_TRUE(top_sites().SetPageThumbnail(url2, thumbnail, medium_score)); - EXPECT_FALSE(top_sites().SetPageThumbnail(url2, thumbnail, low_score)); - EXPECT_TRUE(top_sites().SetPageThumbnail(url2, thumbnail, high_score)); + EXPECT_TRUE(top_sites()->SetPageThumbnail(url2, thumbnail, medium_score)); + EXPECT_FALSE(top_sites()->SetPageThumbnail(url2, thumbnail, low_score)); + EXPECT_TRUE(top_sites()->SetPageThumbnail(url2, thumbnail, high_score)); // Set on the redirect source should succeed. It should be replacable by // the same score on the redirect destination, which in turn should not // be replaced by the source again. - EXPECT_TRUE(top_sites().SetPageThumbnail(url1a, thumbnail, medium_score)); - EXPECT_TRUE(top_sites().SetPageThumbnail(url1b, thumbnail, medium_score)); - EXPECT_FALSE(top_sites().SetPageThumbnail(url1a, thumbnail, medium_score)); + EXPECT_TRUE(top_sites()->SetPageThumbnail(url1a, thumbnail, medium_score)); + EXPECT_TRUE(top_sites()->SetPageThumbnail(url1b, thumbnail, medium_score)); + EXPECT_FALSE(top_sites()->SetPageThumbnail(url1a, thumbnail, medium_score)); +} + +// Makes sure a thumbnail is correctly removed when the page is removed. +TEST_F(TopSitesTest, ThumbnailRemoved) { + GURL url("http://google.com/"); + + // Configure top sites with 'google.com'. + std::vector<MostVisitedURL> list; + AppendMostVisitedURL(&list, url); + SetTopSites(list); + + // Create a dummy thumbnail. + SkBitmap thumbnail(CreateBitmap(SK_ColorRED)); + + base::Time now = base::Time::Now(); + ThumbnailScore low_score(1.0, true, true, now); + ThumbnailScore medium_score(0.5, true, true, now); + ThumbnailScore high_score(0.0, true, true, now); + + // Set the thumbnail. + EXPECT_TRUE(top_sites()->SetPageThumbnail(url, thumbnail, medium_score)); + + // Make sure the thumbnail was actually set. + scoped_refptr<RefCountedBytes> result; + EXPECT_TRUE(top_sites()->GetPageThumbnail(url, &result)); + EXPECT_TRUE(ThumbnailEqualsBytes(thumbnail, result.get())); + + // Reset the thumbnails and make sure we don't get it back. + SetTopSites(MostVisitedURLList()); + EXPECT_FALSE(top_sites()->GetPageThumbnail(url, &result)); } +// Tests GetPageThumbnail. TEST_F(TopSitesTest, GetPageThumbnail) { - BrowserThread db_loop(BrowserThread::DB, MessageLoop::current()); MostVisitedURLList url_list; - MostVisitedURL url1(GURL("http://asdf.com"), GURL(), string16()); + MostVisitedURL url1; + url1.url = GURL("http://asdf.com"); url1.redirects.push_back(url1.url); url_list.push_back(url1); - MostVisitedURL url2(GURL("http://gmail.com"), GURL(), string16()); + MostVisitedURL url2; + url2.url = GURL("http://gmail.com"); url2.redirects.push_back(url2.url); url2.redirects.push_back(GURL("http://mail.google.com")); url_list.push_back(url2); - top_sites().UpdateMostVisited(url_list); - MessageLoop::current()->RunAllPending(); + SetTopSites(url_list); // Create a dummy thumbnail. - SkBitmap thumbnail; - thumbnail.setConfig(SkBitmap::kARGB_8888_Config, 4, 4); - thumbnail.allocPixels(); - thumbnail.eraseRGB(0x00, 0x00, 0x00); + SkBitmap thumbnail(CreateBitmap(SK_ColorWHITE)); ThumbnailScore score(0.5, true, true, base::Time::Now()); - RefCountedBytes* result = NULL; - EXPECT_TRUE(top_sites().SetPageThumbnail(url1.url, thumbnail, score)); - EXPECT_TRUE(top_sites().GetPageThumbnail(url1.url, &result)); + scoped_refptr<RefCountedBytes> result; + EXPECT_TRUE(top_sites()->SetPageThumbnail(url1.url, thumbnail, score)); + EXPECT_TRUE(top_sites()->GetPageThumbnail(url1.url, &result)); - EXPECT_TRUE(top_sites().SetPageThumbnail(GURL("http://gmail.com"), + EXPECT_TRUE(top_sites()->SetPageThumbnail(GURL("http://gmail.com"), thumbnail, score)); - EXPECT_TRUE(top_sites().GetPageThumbnail(GURL("http://gmail.com"), - &result)); + EXPECT_TRUE(top_sites()->GetPageThumbnail(GURL("http://gmail.com"), + &result)); // Get a thumbnail via a redirect. - EXPECT_TRUE(top_sites().GetPageThumbnail(GURL("http://mail.google.com"), - &result)); + EXPECT_TRUE(top_sites()->GetPageThumbnail(GURL("http://mail.google.com"), + &result)); - EXPECT_TRUE(top_sites().SetPageThumbnail(GURL("http://mail.google.com"), + EXPECT_TRUE(top_sites()->SetPageThumbnail(GURL("http://mail.google.com"), thumbnail, score)); - EXPECT_TRUE(top_sites().GetPageThumbnail(url2.url, &result)); + EXPECT_TRUE(top_sites()->GetPageThumbnail(url2.url, &result)); - scoped_ptr<SkBitmap> out_bitmap(gfx::JPEGCodec::Decode(result->front(), - result->size())); - EXPECT_EQ(0, memcmp(thumbnail.getPixels(), out_bitmap->getPixels(), - thumbnail.getSize())); + EXPECT_TRUE(ThumbnailEqualsBytes(thumbnail, result.get())); } +// Tests GetMostVisitedURLs. TEST_F(TopSitesTest, GetMostVisited) { - BrowserThread db_loop(BrowserThread::DB, MessageLoop::current()); GURL news("http://news.google.com/"); GURL google("http://google.com/"); - MockHistoryServiceImpl hs; - hs.AppendMockPage(news, ASCIIToUTF16("Google News")); - hs.AppendMockPage(google, ASCIIToUTF16("Google")); - top_sites().SetMockHistoryService(&hs); - - top_sites().StartQueryForMostVisited(); - MessageLoop::current()->RunAllPending(); - top_sites().GetMostVisitedURLs( - consumer(), - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); + AddPageToHistory(news); + AddPageToHistory(google); + + StartQueryForMostVisited(); + WaitForHistory(); + + TopSitesQuerier querier; + querier.QueryTopSites(top_sites(), false); + + ASSERT_EQ(1, querier.number_of_callbacks()); + // 2 extra prepopulated URLs. - ASSERT_EQ(4u, urls().size()); - EXPECT_EQ(news, urls()[0].url); - EXPECT_EQ(google, urls()[1].url); - EXPECT_EQ(welcome_url(), urls()[2].url); - EXPECT_EQ(themes_url(), urls()[3].url); + ASSERT_EQ(2u + GetPrepopulatePages().size(), querier.urls().size()); + EXPECT_EQ(news, querier.urls()[0].url); + EXPECT_EQ(google, querier.urls()[1].url); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 2)); } -TEST_F(TopSitesTest, MockDatabase) { - BrowserThread db_loop(BrowserThread::DB, MessageLoop::current()); - MockTopSitesDatabaseImpl* db = new MockTopSitesDatabaseImpl; - // |db| is destroyed when the top_sites is destroyed in TearDown. - top_sites().db_.reset(db); +// Makes sure changes done to top sites get mirrored to the db. +TEST_F(TopSitesTest, SaveToDB) { MostVisitedURL url; GURL asdf_url("http://asdf.com"); string16 asdf_title(ASCIIToUTF16("ASDF")); @@ -496,155 +626,62 @@ TEST_F(TopSitesTest, MockDatabase) { GURL news_url("http://news.google.com"); string16 news_title(ASCIIToUTF16("Google News")); - url.url = asdf_url; - url.title = asdf_title; - url.redirects.push_back(url.url); - Images thumbnail; - db->SetPageThumbnail(url, 0, thumbnail); + // Add asdf_url to history. + AddPageToHistory(asdf_url, asdf_title); + + // Make TopSites reread from the db. + StartQueryForMostVisited(); + WaitForHistory(); - top_sites().ReadDatabase(); + // Add a thumbnail. + SkBitmap tmp_bitmap(CreateBitmap(SK_ColorBLUE)); + ASSERT_TRUE(top_sites()->SetPageThumbnail(asdf_url, tmp_bitmap, + ThumbnailScore())); - top_sites().GetMostVisitedURLs( - consumer(), - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - ASSERT_EQ(3u, urls().size()); - EXPECT_EQ(asdf_url, urls()[0].url); - EXPECT_EQ(asdf_title, urls()[0].title); - EXPECT_EQ(welcome_url(), urls()[1].url); - EXPECT_EQ(themes_url(), urls()[2].url); + RecreateTopSitesAndBlock(); + + { + TopSitesQuerier querier; + querier.QueryTopSites(top_sites(), false); + ASSERT_EQ(1u + GetPrepopulatePages().size(), querier.urls().size()); + EXPECT_EQ(asdf_url, querier.urls()[0].url); + EXPECT_EQ(asdf_title, querier.urls()[0].title); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 1)); + + scoped_refptr<RefCountedBytes> read_data; + EXPECT_TRUE(top_sites()->GetPageThumbnail(asdf_url, &read_data)); + EXPECT_TRUE(ThumbnailEqualsBytes(tmp_bitmap, read_data.get())); + } MostVisitedURL url2; url2.url = google_url; url2.title = google_title; url2.redirects.push_back(url2.url); - // Add new thumbnail at rank 0 and shift the other result to 1. - db->SetPageThumbnail(url2, 0, thumbnail); - - top_sites().ReadDatabase(); - - top_sites().GetMostVisitedURLs( - consumer(), - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - ASSERT_EQ(4u, urls().size()); - EXPECT_EQ(google_url, urls()[0].url); - EXPECT_EQ(google_title, urls()[0].title); - EXPECT_EQ(asdf_url, urls()[1].url); - EXPECT_EQ(asdf_title, urls()[1].title); - EXPECT_EQ(welcome_url(), urls()[2].url); - EXPECT_EQ(themes_url(), urls()[3].url); - - MockHistoryServiceImpl hs; - // Add one old, one new URL to the history. - hs.AppendMockPage(google_url, google_title); - hs.AppendMockPage(news_url, news_title); - top_sites().SetMockHistoryService(&hs); - - // This writes the new data to the DB. - top_sites().StartQueryForMostVisited(); - MessageLoop::current()->RunAllPending(); - - std::map<GURL, Images> thumbnails; - MostVisitedURLList result; - db->GetPageThumbnails(&result, &thumbnails); - ASSERT_EQ(4u, result.size()); - EXPECT_EQ(google_title, result[0].title); - EXPECT_EQ(news_title, result[1].title); -} - -// Test TopSitesDatabaseImpl. -TEST_F(TopSitesTest, TopSitesDB) { - TopSitesDatabaseImpl db; + AddPageToHistory(url2.url, url2.title); - ASSERT_TRUE(db.Init(file_name())); + // Add new thumbnail at rank 0 and shift the other result to 1. + ASSERT_TRUE(top_sites()->SetPageThumbnail(google_url, + tmp_bitmap, + ThumbnailScore())); - MostVisitedURL url; - GURL asdf_url("http://asdf.com"); - string16 asdf_title(ASCIIToUTF16("ASDF")); - GURL google_url("http://google.com"); - string16 google_title(ASCIIToUTF16("Google")); - GURL news_url("http://news.google.com"); - string16 news_title(ASCIIToUTF16("Google News")); + // Make TopSites reread from the db. + RefreshTopSitesAndRecreate(); - url.url = asdf_url; - url.title = asdf_title; - url.redirects.push_back(url.url); - Images thumbnail; - thumbnail.thumbnail = random_thumbnail(); - // Add asdf at rank 0. - db.SetPageThumbnail(url, 0, thumbnail); - - Images result; - EXPECT_TRUE(db.GetPageThumbnail(url.url, &result)); - EXPECT_EQ(thumbnail.thumbnail->data.size(), result.thumbnail->data.size()); - EXPECT_TRUE(ThumbnailsAreEqual(thumbnail.thumbnail, result.thumbnail)); - - MostVisitedURLList urls; - std::map<GURL, Images> thumbnails; - db.GetPageThumbnails(&urls, &thumbnails); - ASSERT_EQ(1u, urls.size()); - EXPECT_EQ(asdf_url, urls[0].url); - EXPECT_EQ(asdf_title, urls[0].title); - - url.url = google_url; - url.title = google_title; - - // Add google at rank 1 - no rank shifting. - db.SetPageThumbnail(url, 1, thumbnail); - db.GetPageThumbnails(&urls, &thumbnails); - ASSERT_EQ(2u, urls.size()); - EXPECT_EQ(asdf_url, urls[0].url); - EXPECT_EQ(asdf_title, urls[0].title); - EXPECT_EQ(google_url, urls[1].url); - EXPECT_EQ(google_title, urls[1].title); - - url.url = news_url; - url.title = news_title; - - // Add news at rank 1 - shift google to rank 2. - db.SetPageThumbnail(url, 1, thumbnail); - db.GetPageThumbnails(&urls, &thumbnails); - ASSERT_EQ(3u, urls.size()); - EXPECT_EQ(asdf_url, urls[0].url); - EXPECT_EQ(news_url, urls[1].url); - EXPECT_EQ(google_url, urls[2].url); - - // Move news at rank 0 - shift the rest up. - db.SetPageThumbnail(url, 0, thumbnail); - db.GetPageThumbnails(&urls, &thumbnails); - ASSERT_EQ(3u, urls.size()); - EXPECT_EQ(news_url, urls[0].url); - EXPECT_EQ(asdf_url, urls[1].url); - EXPECT_EQ(google_url, urls[2].url); - - // Move news at rank 2 - shift the rest down. - db.SetPageThumbnail(url, 2, thumbnail); - db.GetPageThumbnails(&urls, &thumbnails); - ASSERT_EQ(3u, urls.size()); - EXPECT_EQ(asdf_url, urls[0].url); - EXPECT_EQ(google_url, urls[1].url); - EXPECT_EQ(news_url, urls[2].url); - - // Delete asdf. - url.url = asdf_url; - db.RemoveURL(url); - - db.GetPageThumbnails(&urls, &thumbnails); - ASSERT_EQ(2u, urls.size()); - EXPECT_EQ(google_url, urls[0].url); - EXPECT_EQ(news_url, urls[1].url); + { + TopSitesQuerier querier; + querier.QueryTopSites(top_sites(), false); + ASSERT_EQ(2u + GetPrepopulatePages().size(), querier.urls().size()); + EXPECT_EQ(asdf_url, querier.urls()[0].url); + EXPECT_EQ(asdf_title, querier.urls()[0].title); + EXPECT_EQ(google_url, querier.urls()[1].url); + EXPECT_EQ(google_title, querier.urls()[1].title); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 2)); + } } -// Test TopSites with a real database. +// More permutations of saving to db. TEST_F(TopSitesTest, RealDatabase) { - BrowserThread db_loop(BrowserThread::DB, MessageLoop::current()); - TopSitesDatabaseImpl* db = new TopSitesDatabaseImpl; - - ASSERT_TRUE(db->Init(file_name())); - // |db| is destroyed when the top_sites is destroyed in TearDown. - top_sites().db_.reset(db); MostVisitedURL url; GURL asdf_url("http://asdf.com"); string16 asdf_title(ASCIIToUTF16("ASDF")); @@ -658,130 +695,106 @@ TEST_F(TopSitesTest, RealDatabase) { url.url = asdf_url; url.title = asdf_title; url.redirects.push_back(url.url); - Images thumbnail; - thumbnail.thumbnail = random_thumbnail(); - db->SetPageThumbnail(url, 0, thumbnail); - - top_sites().ReadDatabase(); - - top_sites().GetMostVisitedURLs( - consumer(), - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - ASSERT_EQ(3u, urls().size()); - EXPECT_EQ(asdf_url, urls()[0].url); - EXPECT_EQ(asdf_title, urls()[0].title); - EXPECT_EQ(welcome_url(), urls()[1].url); - EXPECT_EQ(themes_url(), urls()[2].url); - - Images img_result; - db->GetPageThumbnail(asdf_url, &img_result); - EXPECT_TRUE(img_result.thumbnail != NULL); - EXPECT_TRUE(ThumbnailsAreEqual(random_thumbnail(), img_result.thumbnail)); - - RefCountedBytes* thumbnail_result; - EXPECT_TRUE(top_sites().GetPageThumbnail(asdf_url, &thumbnail_result)); - EXPECT_TRUE(thumbnail_result != NULL); - EXPECT_TRUE(ThumbnailsAreEqual(random_thumbnail(), thumbnail_result)); + SkBitmap asdf_thumbnail(CreateBitmap(SK_ColorRED)); + ASSERT_TRUE(top_sites()->SetPageThumbnail( + asdf_url, asdf_thumbnail, ThumbnailScore())); + + base::Time add_time(base::Time::Now()); + AddPageToHistory(url.url, url.title, url.redirects, add_time); + + RefreshTopSitesAndRecreate(); + + { + TopSitesQuerier querier; + querier.QueryTopSites(top_sites(), false); + + ASSERT_EQ(1u + GetPrepopulatePages().size(), querier.urls().size()); + EXPECT_EQ(asdf_url, querier.urls()[0].url); + EXPECT_EQ(asdf_title, querier.urls()[0].title); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 1)); + + scoped_refptr<RefCountedBytes> read_data; + EXPECT_TRUE(top_sites()->GetPageThumbnail(asdf_url, &read_data)); + EXPECT_TRUE(ThumbnailEqualsBytes(asdf_thumbnail, read_data.get())); + } MostVisitedURL url2; - url2.url = google1_url; + url2.url = google3_url; url2.title = google_title; url2.redirects.push_back(google1_url); url2.redirects.push_back(google2_url); url2.redirects.push_back(google3_url); - // Add new thumbnail at rank 0 and shift the other result to 1. - Images g_thumbnail; - g_thumbnail.thumbnail = google_thumbnail(); - db->SetPageThumbnail(url2, 0, g_thumbnail); - - top_sites().ReadDatabase(); - - top_sites().GetMostVisitedURLs( - consumer(), - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - ASSERT_EQ(4u, urls().size()); - EXPECT_EQ(google1_url, urls()[0].url); - EXPECT_EQ(google_title, urls()[0].title); - EXPECT_TRUE(top_sites().GetPageThumbnail(google1_url, &thumbnail_result)); - EXPECT_TRUE(ThumbnailsAreEqual(google_thumbnail(), thumbnail_result)); - ASSERT_EQ(3u, urls()[0].redirects.size()); - EXPECT_EQ(google1_url, urls()[0].redirects[0]); - EXPECT_EQ(google2_url, urls()[0].redirects[1]); - EXPECT_EQ(google3_url, urls()[0].redirects[2]); - - EXPECT_EQ(asdf_url, urls()[1].url); - EXPECT_EQ(asdf_title, urls()[1].title); - EXPECT_EQ(welcome_url(), urls()[2].url); - EXPECT_EQ(themes_url(), urls()[3].url); - - MockHistoryServiceImpl hs; - // Add one old, one new URL to the history. - hs.AppendMockPage(google1_url, google_title); - hs.AppendMockPage(news_url, news_title); - top_sites().SetMockHistoryService(&hs); - - // This requests data from History Service and writes it to the DB. - top_sites().StartQueryForMostVisited(); - MessageLoop::current()->RunAllPending(); - - std::map<GURL, Images> thumbnails; - MostVisitedURLList results; - db->GetPageThumbnails(&results, &thumbnails); - ASSERT_EQ(4u, results.size()); - EXPECT_EQ(google_title, results[0].title); - EXPECT_EQ(news_title, results[1].title); - - scoped_ptr<SkBitmap> weewar_bitmap( - gfx::JPEGCodec::Decode(weewar_thumbnail()->front(), - weewar_thumbnail()->size())); + AddPageToHistory(google3_url, url2.title, url2.redirects, + add_time - base::TimeDelta::FromMinutes(1)); + // Add google twice so that it becomes the first visited site. + AddPageToHistory(google3_url, url2.title, url2.redirects, + add_time - base::TimeDelta::FromMinutes(2)); - base::Time now = base::Time::Now(); - ThumbnailScore low_score(1.0, true, true, now); - ThumbnailScore medium_score(0.5, true, true, now); - ThumbnailScore high_score(0.0, true, true, now); + SkBitmap google_thumbnail(CreateBitmap(SK_ColorBLUE)); + ASSERT_TRUE(top_sites()->SetPageThumbnail( + url2.url, google_thumbnail, ThumbnailScore())); - // 1. Set to weewar. (Writes the thumbnail to the DB.) - EXPECT_TRUE(top_sites().SetPageThumbnail(google1_url, - *weewar_bitmap, - medium_score)); - RefCountedBytes* out_1; - Images out_2; - EXPECT_TRUE(top_sites().GetPageThumbnail(google1_url, &out_1)); + RefreshTopSitesAndRecreate(); - MessageLoop::current()->RunAllPending(); + { + scoped_refptr<RefCountedBytes> read_data; + TopSitesQuerier querier; + querier.QueryTopSites(top_sites(), false); + + ASSERT_EQ(2u + GetPrepopulatePages().size(), querier.urls().size()); + EXPECT_EQ(google1_url, querier.urls()[0].url); + EXPECT_EQ(google_title, querier.urls()[0].title); + ASSERT_EQ(3u, querier.urls()[0].redirects.size()); + EXPECT_TRUE(top_sites()->GetPageThumbnail(google3_url, &read_data)); + EXPECT_TRUE(ThumbnailEqualsBytes(google_thumbnail, read_data.get())); + + EXPECT_EQ(asdf_url, querier.urls()[1].url); + EXPECT_EQ(asdf_title, querier.urls()[1].title); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 2)); + } - db->GetPageThumbnail(url2.url, &out_2); - EXPECT_TRUE(ThumbnailsAreEqual(out_1, out_2.thumbnail)); + SkBitmap weewar_bitmap(CreateBitmap(SK_ColorYELLOW)); - scoped_ptr<SkBitmap> google_bitmap( - gfx::JPEGCodec::Decode(google_thumbnail()->front(), - google_thumbnail()->size())); + base::Time thumbnail_time(base::Time::Now()); + ThumbnailScore low_score(1.0, true, true, thumbnail_time); + ThumbnailScore medium_score(0.5, true, true, thumbnail_time); + ThumbnailScore high_score(0.0, true, true, thumbnail_time); + + // 1. Set to weewar. (Writes the thumbnail to the DB.) + EXPECT_TRUE(top_sites()->SetPageThumbnail(google3_url, + weewar_bitmap, + medium_score)); + RefreshTopSitesAndRecreate(); + { + scoped_refptr<RefCountedBytes> read_data; + EXPECT_TRUE(top_sites()->GetPageThumbnail(google3_url, &read_data)); + EXPECT_TRUE(ThumbnailEqualsBytes(weewar_bitmap, read_data.get())); + } + + SkBitmap google_bitmap(CreateBitmap(SK_ColorGREEN)); // 2. Set to google - low score. - EXPECT_FALSE(top_sites().SetPageThumbnail(google1_url, - *google_bitmap, - low_score)); + EXPECT_FALSE(top_sites()->SetPageThumbnail(google3_url, + google_bitmap, + low_score)); // 3. Set to google - high score. - EXPECT_TRUE(top_sites().SetPageThumbnail(google1_url, - *google_bitmap, - high_score)); + EXPECT_TRUE(top_sites()->SetPageThumbnail(google1_url, + google_bitmap, + high_score)); + // Check that the thumbnail was updated. - EXPECT_TRUE(top_sites().GetPageThumbnail(google1_url, &out_1)); - EXPECT_FALSE(ThumbnailsAreEqual(out_1, out_2.thumbnail)); - MessageLoop::current()->RunAllPending(); - - // Read the new thumbnail from the DB - should match what's in TopSites. - db->GetPageThumbnail(url2.url, &out_2); - EXPECT_TRUE(ThumbnailsAreEqual(out_1, out_2.thumbnail)); - EXPECT_TRUE(high_score.Equals(out_2.thumbnail_score)); + RefreshTopSitesAndRecreate(); + { + scoped_refptr<RefCountedBytes> read_data; + EXPECT_TRUE(top_sites()->GetPageThumbnail(google3_url, &read_data)); + EXPECT_FALSE(ThumbnailEqualsBytes(weewar_bitmap, read_data.get())); + EXPECT_TRUE(ThumbnailEqualsBytes(google_bitmap, read_data.get())); + } } TEST_F(TopSitesTest, DeleteNotifications) { - BrowserThread db_loop(BrowserThread::DB, MessageLoop::current()); GURL google1_url("http://google.com"); GURL google2_url("http://google.com/redirect"); GURL google3_url("http://www.google.com"); @@ -789,61 +802,70 @@ TEST_F(TopSitesTest, DeleteNotifications) { GURL news_url("http://news.google.com"); string16 news_title(ASCIIToUTF16("Google News")); - MockHistoryServiceImpl hs; + AddPageToHistory(google1_url, google_title); + AddPageToHistory(news_url, news_title); - top_sites().Init(file_name()); + RefreshTopSitesAndRecreate(); - hs.AppendMockPage(google1_url, google_title); - hs.AppendMockPage(news_url, news_title); - top_sites().SetMockHistoryService(&hs); + { + TopSitesQuerier querier; + querier.QueryTopSites(top_sites(), false); - top_sites().StartQueryForMostVisited(); - MessageLoop::current()->RunAllPending(); + ASSERT_EQ(4u, querier.urls().size()); + } - top_sites().GetMostVisitedURLs( - consumer(), - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - // 2 extra prepopulated URLs. - ASSERT_EQ(4u, urls().size()); - - hs.RemoveMostVisitedURL(); - - history::URLsDeletedDetails history_details; - history_details.all_history = false; - Details<URLsDeletedDetails> details(&history_details); - top_sites().Observe(NotificationType::HISTORY_URLS_DELETED, - Source<Profile> (&profile()), - details); - MessageLoop::current()->RunAllPending(); - - top_sites().GetMostVisitedURLs( - consumer(), - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - ASSERT_EQ(3u, urls().size()); - EXPECT_EQ(google_title, urls()[0].title); - EXPECT_EQ(welcome_url(), urls()[1].url); - EXPECT_EQ(themes_url(), urls()[2].url); - - hs.RemoveMostVisitedURL(); - history_details.all_history = true; - details = Details<HistoryDetails>(&history_details); - top_sites().Observe(NotificationType::HISTORY_URLS_DELETED, - Source<Profile> (&profile()), - details); - MessageLoop::current()->RunAllPending(); - top_sites().GetMostVisitedURLs( - consumer(), - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - ASSERT_EQ(2u, urls().size()); - EXPECT_EQ(welcome_url(), urls()[0].url); - EXPECT_EQ(themes_url(), urls()[1].url); + DeleteURL(news_url); + + // Wait for history to process the deletion. + WaitForHistory(); + + { + TopSitesQuerier querier; + querier.QueryTopSites(top_sites(), false); + + ASSERT_EQ(1u + GetPrepopulatePages().size(), querier.urls().size()); + EXPECT_EQ(google_title, querier.urls()[0].title); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 1)); + } + + // Now reload. This verifies topsites actually wrote the deletion to disk. + RefreshTopSitesAndRecreate(); + + { + TopSitesQuerier querier; + querier.QueryTopSites(top_sites(), false); + + ASSERT_EQ(1u + GetPrepopulatePages().size(), querier.urls().size()); + EXPECT_EQ(google_title, querier.urls()[0].title); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 1)); + } + + DeleteURL(google1_url); + + // Wait for history to process the deletion. + WaitForHistory(); + + { + TopSitesQuerier querier; + querier.QueryTopSites(top_sites(), false); + + ASSERT_EQ(GetPrepopulatePages().size(), querier.urls().size()); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 0)); + } + + // Now reload. This verifies topsites actually wrote the deletion to disk. + RefreshTopSitesAndRecreate(); + + { + TopSitesQuerier querier; + querier.QueryTopSites(top_sites(), false); + + ASSERT_EQ(GetPrepopulatePages().size(), querier.urls().size()); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 0)); + } } TEST_F(TopSitesTest, PinnedURLsDeleted) { - BrowserThread db_loop(BrowserThread::DB, MessageLoop::current()); GURL google1_url("http://google.com"); GURL google2_url("http://google.com/redirect"); GURL google3_url("http://www.google.com"); @@ -851,132 +873,130 @@ TEST_F(TopSitesTest, PinnedURLsDeleted) { GURL news_url("http://news.google.com"); string16 news_title(ASCIIToUTF16("Google News")); - MockHistoryServiceImpl hs; + AddPageToHistory(google1_url, google_title); + AddPageToHistory(news_url, news_title); + + RefreshTopSitesAndRecreate(); - top_sites().Init(file_name()); + { + TopSitesQuerier querier; + querier.QueryTopSites(top_sites(), false); - hs.AppendMockPage(google1_url, google_title); - hs.AppendMockPage(news_url, news_title); - top_sites().SetMockHistoryService(&hs); + // 2 extra prepopulated URLs. + ASSERT_EQ(4u, querier.urls().size()); + } - top_sites().StartQueryForMostVisited(); - MessageLoop::current()->RunAllPending(); - top_sites().GetMostVisitedURLs( - consumer(), - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - MessageLoop::current()->RunAllPending(); - EXPECT_EQ(1u, number_of_callbacks()); - // 2 extra prepopulated URLs. - ASSERT_EQ(4u, urls().size()); - - top_sites().AddPinnedURL(news_url, 3); - EXPECT_TRUE(top_sites().IsURLPinned(news_url)); - - hs.RemoveMostVisitedURL(); - history::URLsDeletedDetails history_details; - history_details.all_history = false; - history_details.urls.insert(news_url); - Details<URLsDeletedDetails> details(&history_details); - top_sites().Observe(NotificationType::HISTORY_URLS_DELETED, - Source<Profile> (&profile()), - details); - MessageLoop::current()->RunAllPending(); - top_sites().GetMostVisitedURLs( - consumer(), - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - MessageLoop::current()->RunAllPending(); - EXPECT_EQ(2u, number_of_callbacks()); - ASSERT_EQ(3u, urls().size()); - EXPECT_FALSE(top_sites().IsURLPinned(news_url)); - - hs.RemoveMostVisitedURL(); - history_details.all_history = true; - details = Details<HistoryDetails>(&history_details); - top_sites().Observe(NotificationType::HISTORY_URLS_DELETED, - Source<Profile> (&profile()), - details); - MessageLoop::current()->RunAllPending(); - top_sites().GetMostVisitedURLs( - consumer(), - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - ASSERT_EQ(2u, urls().size()); - MessageLoop::current()->RunAllPending(); - - top_sites().StartQueryForMostVisited(); - MessageLoop::current()->RunAllPending(); - top_sites().GetMostVisitedURLs( - consumer(), - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - ASSERT_EQ(2u, urls().size()); - EXPECT_EQ(welcome_url(), urls()[0].url); - EXPECT_EQ(themes_url(), urls()[1].url); + top_sites()->AddPinnedURL(news_url, 3); + EXPECT_TRUE(top_sites()->IsURLPinned(news_url)); + + DeleteURL(news_url); + WaitForHistory(); + + { + TopSitesQuerier querier; + querier.QueryTopSites(top_sites(), false); + + // 2 extra prepopulated URLs. + ASSERT_EQ(3u, querier.urls().size()); + EXPECT_FALSE(top_sites()->IsURLPinned(news_url)); + } + + history_service()->ExpireHistoryBetween( + std::set<GURL>(), base::Time(), base::Time(), + consumer(), NewCallback(static_cast<TopSitesTest*>(this), + &TopSitesTest::EmptyCallback)), + WaitForHistory(); + + { + TopSitesQuerier querier; + querier.QueryTopSites(top_sites(), false); + + // 2 extra prepopulated URLs. + ASSERT_EQ(GetPrepopulatePages().size(), querier.urls().size()); + EXPECT_FALSE(top_sites()->IsURLPinned(google1_url)); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 0)); + } } +// Makes sure GetUpdateDelay is updated appropriately. TEST_F(TopSitesTest, GetUpdateDelay) { - top_sites().last_num_urls_changed_ = 0; - EXPECT_EQ(30, top_sites().GetUpdateDelay().InSeconds()); + SetLastNumUrlsChanged(0); + EXPECT_EQ(30, GetUpdateDelay().InSeconds()); - top_sites().top_sites_.resize(20); - top_sites().last_num_urls_changed_ = 0; - EXPECT_EQ(60, top_sites().GetUpdateDelay().InMinutes()); + MostVisitedURLList url_list; + url_list.resize(20); + GURL tmp_url(GURL("http://x")); + for (size_t i = 0; i < url_list.size(); ++i) { + url_list[i].url = tmp_url; + url_list[i].redirects.push_back(tmp_url); + } + SetTopSites(url_list); + EXPECT_EQ(20u, last_num_urls_changed()); + SetLastNumUrlsChanged(0); + EXPECT_EQ(60, GetUpdateDelay().InMinutes()); - top_sites().last_num_urls_changed_ = 3; - EXPECT_EQ(52, top_sites().GetUpdateDelay().InMinutes()); + SetLastNumUrlsChanged(3); + EXPECT_EQ(52, GetUpdateDelay().InMinutes()); - top_sites().last_num_urls_changed_ = 20; - EXPECT_EQ(1, top_sites().GetUpdateDelay().InMinutes()); + SetLastNumUrlsChanged(20); + EXPECT_EQ(1, GetUpdateDelay().InMinutes()); } -TEST_F(TopSitesTest, Migration) { - BrowserThread db_loop(BrowserThread::DB, MessageLoop::current()); - GURL google1_url("http://google.com"); - string16 google_title(ASCIIToUTF16("Google")); - GURL news_url("http://news.google.com"); - string16 news_title(ASCIIToUTF16("Google News")); +TEST_F(TopSitesMigrationTest, Migrate) { + EXPECT_TRUE(IsTopSitesLoaded()); - MockHistoryServiceImpl hs; + // Make sure the data was migrated to top sites. + ASSERT_NO_FATAL_FAILURE(MigrationAssertions()); - top_sites().Init(file_name()); + // We need to wait for top sites and history to finish processing requests. + WaitForTopSites(); + WaitForHistory(); - hs.AppendMockPage(google1_url, google_title); - hs.AppendMockPage(news_url, news_title); - top_sites().SetMockHistoryService(&hs); - MessageLoop::current()->RunAllPending(); + // Make sure there is no longer a Thumbnails file on disk. + ASSERT_FALSE(file_util::PathExists( + profile()->GetPath().Append(chrome::kThumbnailsFilename))); - top_sites().StartMigration(); - EXPECT_TRUE(top_sites().migration_in_progress_); - MessageLoop::current()->RunAllPending(); - EXPECT_EQ(2, hs.GetNumberOfThumbnailRequests()); - EXPECT_FALSE(top_sites().migration_in_progress_); + // Recreate top sites and make sure everything is still there. + profile()->CreateHistoryService(false, false); + RecreateTopSitesAndBlock(); + + ASSERT_NO_FATAL_FAILURE(MigrationAssertions()); } -TEST_F(TopSitesTest, QueueingRequestsForTopSites) { - BrowserThread db_loop(BrowserThread::DB, MessageLoop::current()); - CancelableRequestConsumer c1; - CancelableRequestConsumer c2; - CancelableRequestConsumer c3; - top_sites().GetMostVisitedURLs( - &c1, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - - top_sites().GetMostVisitedURLs( - &c2, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - - top_sites().GetMostVisitedURLs( - &c3, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - - EXPECT_EQ(0u, number_of_callbacks()); - EXPECT_EQ(0u, urls().size()); +// Verifies that callbacks are notified correctly if requested before top sites +// has loaded. +TEST_F(TopSitesTest, NotifyCallbacksWhenLoaded) { + // Recreate top sites. It won't be loaded now. + profile()->CreateTopSites(); + + EXPECT_FALSE(IsTopSitesLoaded()); + + TopSitesQuerier querier1; + TopSitesQuerier querier2; + TopSitesQuerier querier3; + + // Starts the queries. + querier1.QueryTopSites(top_sites(), false); + querier2.QueryTopSites(top_sites(), false); + querier3.QueryTopSites(top_sites(), false); + + // We shouldn't have gotten a callback. + EXPECT_EQ(0, querier1.number_of_callbacks()); + EXPECT_EQ(0, querier2.number_of_callbacks()); + EXPECT_EQ(0, querier3.number_of_callbacks()); + // Wait for loading to complete. + profile()->BlockUntilTopSitesLoaded(); + + // Now we should have gotten the callbacks. + EXPECT_EQ(1, querier1.number_of_callbacks()); + EXPECT_EQ(GetPrepopulatePages().size(), querier1.urls().size()); + EXPECT_EQ(1, querier2.number_of_callbacks()); + EXPECT_EQ(GetPrepopulatePages().size(), querier2.urls().size()); + EXPECT_EQ(1, querier3.number_of_callbacks()); + EXPECT_EQ(GetPrepopulatePages().size(), querier3.urls().size()); + + // Reset the top sites. MostVisitedURLList pages; MostVisitedURL url; url.url = GURL("http://1.com/"); @@ -985,109 +1005,107 @@ TEST_F(TopSitesTest, QueueingRequestsForTopSites) { url.url = GURL("http://2.com/"); url.redirects.push_back(url.url); pages.push_back(url); - top_sites().OnTopSitesAvailable(0, pages); - MessageLoop::current()->RunAllPending(); + SetTopSites(pages); + + // Recreate top sites. It won't be loaded now. + profile()->CreateTopSites(); + + EXPECT_FALSE(IsTopSitesLoaded()); - EXPECT_EQ(3u, number_of_callbacks()); + TopSitesQuerier querier4; - ASSERT_EQ(4u, urls().size()); - EXPECT_EQ("http://1.com/", urls()[0].url.spec()); - EXPECT_EQ("http://2.com/", urls()[1].url.spec()); - EXPECT_EQ(welcome_url(), urls()[2].url); - EXPECT_EQ(themes_url(), urls()[3].url); + // Query again. + querier4.QueryTopSites(top_sites(), false); + // We shouldn't have gotten a callback. + EXPECT_EQ(0, querier4.number_of_callbacks()); + // Wait for loading to complete. + profile()->BlockUntilTopSitesLoaded(); + + // Now we should have gotten the callbacks. + EXPECT_EQ(1, querier4.number_of_callbacks()); + ASSERT_EQ(2u + GetPrepopulatePages().size(), querier4.urls().size()); + + EXPECT_EQ("http://1.com/", querier4.urls()[0].url.spec()); + EXPECT_EQ("http://2.com/", querier4.urls()[1].url.spec()); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier4, 2)); + + // Reset the top sites again, this time don't reload. url.url = GURL("http://3.com/"); url.redirects.push_back(url.url); pages.push_back(url); - top_sites().OnTopSitesAvailable(0, pages); - MessageLoop::current()->RunAllPending(); + SetTopSites(pages); - top_sites().GetMostVisitedURLs( - &c3, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); + // Query again. + TopSitesQuerier querier5; + querier5.QueryTopSites(top_sites(), true); - EXPECT_EQ(4u, number_of_callbacks()); - - ASSERT_EQ(5u, urls().size()); - EXPECT_EQ("http://1.com/", urls()[0].url.spec()); - EXPECT_EQ("http://2.com/", urls()[1].url.spec()); - EXPECT_EQ("http://3.com/", urls()[2].url.spec()); - EXPECT_EQ(welcome_url(), urls()[3].url); - EXPECT_EQ(themes_url(), urls()[4].url); + EXPECT_EQ(1, querier5.number_of_callbacks()); + ASSERT_EQ(3u + GetPrepopulatePages().size(), querier5.urls().size()); + EXPECT_EQ("http://1.com/", querier5.urls()[0].url.spec()); + EXPECT_EQ("http://2.com/", querier5.urls()[1].url.spec()); + EXPECT_EQ("http://3.com/", querier5.urls()[2].url.spec()); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier5, 3)); } +// Makes sure canceled requests are not notified. TEST_F(TopSitesTest, CancelingRequestsForTopSites) { - BrowserThread db_loop(BrowserThread::DB, MessageLoop::current()); - CancelableRequestConsumer c1; - CancelableRequestConsumer c2; - top_sites().GetMostVisitedURLs( - &c1, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - - top_sites().GetMostVisitedURLs( - &c2, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); + // Recreate top sites. It won't be loaded now. + profile()->CreateTopSites(); - { - CancelableRequestConsumer c3; - top_sites().GetMostVisitedURLs( - &c3, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - // c3 is out of scope, and the request should be cancelled. - } + EXPECT_FALSE(IsTopSitesLoaded()); - // No requests until OnTopSitesAvailable is called. - EXPECT_EQ(0u, number_of_callbacks()); - EXPECT_EQ(0u, urls().size()); + TopSitesQuerier querier1; + TopSitesQuerier querier2; - MostVisitedURLList pages; - MostVisitedURL url; - url.url = GURL("http://1.com/"); - url.redirects.push_back(url.url); - pages.push_back(url); - url.url = GURL("http://2.com/"); - pages.push_back(url); + // Starts the queries. + querier1.QueryTopSites(top_sites(), false); + querier2.QueryTopSites(top_sites(), false); - top_sites().OnTopSitesAvailable(0, pages); - MessageLoop::current()->RunAllPending(); + // We shouldn't have gotten a callback. + EXPECT_EQ(0, querier1.number_of_callbacks()); + EXPECT_EQ(0, querier2.number_of_callbacks()); - // 1 request was canceled. - EXPECT_EQ(2u, number_of_callbacks()); + querier2.CancelRequest(); - // 2 extra prepopulated URLs. - ASSERT_EQ(4u, urls().size()); - EXPECT_EQ("http://1.com/", urls()[0].url.spec()); - EXPECT_EQ("http://2.com/", urls()[1].url.spec()); + // Wait for loading to complete. + profile()->BlockUntilTopSitesLoaded(); + + // The first callback should succeed. + EXPECT_EQ(1, querier1.number_of_callbacks()); + EXPECT_EQ(GetPrepopulatePages().size(), querier1.urls().size()); + + // And the canceled callback should not be notified. + EXPECT_EQ(0, querier2.number_of_callbacks()); } +// Makes sure temporary thumbnails are copied over correctly. TEST_F(TopSitesTest, AddTemporaryThumbnail) { - BrowserThread db_loop(BrowserThread::DB, MessageLoop::current()); GURL unknown_url("http://news.google.com/"); GURL invalid_url("chrome://thumb/http://google.com/"); GURL url1a("http://google.com/"); GURL url1b("http://www.google.com/"); // Create a dummy thumbnail. - SkBitmap thumbnail; - thumbnail.setConfig(SkBitmap::kARGB_8888_Config, 4, 4); - thumbnail.allocPixels(); - thumbnail.eraseRGB(0x00, 0x00, 0x00); + SkBitmap thumbnail(CreateBitmap(SK_ColorRED)); ThumbnailScore medium_score(0.5, true, true, base::Time::Now()); // Don't store thumbnails for Javascript URLs. - EXPECT_FALSE(top_sites().SetPageThumbnail(invalid_url, - thumbnail, medium_score)); + EXPECT_FALSE(top_sites()->SetPageThumbnail(invalid_url, + thumbnail, + medium_score)); // Store thumbnails for unknown (but valid) URLs temporarily - calls // AddTemporaryThumbnail. - EXPECT_TRUE(top_sites().SetPageThumbnail(unknown_url, - thumbnail, medium_score)); + EXPECT_TRUE(top_sites()->SetPageThumbnail(unknown_url, + thumbnail, + medium_score)); + + // We shouldn't get the thumnail back though (the url isn't in to sites yet). + scoped_refptr<RefCountedBytes> out; + EXPECT_FALSE(top_sites()->GetPageThumbnail(unknown_url, &out)); std::vector<MostVisitedURL> list; @@ -1098,19 +1116,15 @@ TEST_F(TopSitesTest, AddTemporaryThumbnail) { mv.redirects.push_back(url1b); list.push_back(mv); - // Update URLs - use temporary thumbnails. - top_sites().UpdateMostVisited(list); + // Update URLs. This should result in using thumbnail. + SetTopSites(list); - RefCountedBytes* out = NULL; - ASSERT_TRUE(top_sites().GetPageThumbnail(unknown_url, &out)); - scoped_ptr<SkBitmap> out_bitmap(gfx::JPEGCodec::Decode(out->front(), - out->size())); - EXPECT_EQ(0, memcmp(thumbnail.getPixels(), out_bitmap->getPixels(), - thumbnail.getSize())); + ASSERT_TRUE(top_sites()->GetPageThumbnail(unknown_url, &out)); + EXPECT_TRUE(ThumbnailEqualsBytes(thumbnail, out.get())); } +// Tests variations of blacklisting. TEST_F(TopSitesTest, Blacklisting) { - BrowserThread db_loop(BrowserThread::DB, MessageLoop::current()); MostVisitedURLList pages; MostVisitedURL url, url1; url.url = GURL("http://bbc.com/"); @@ -1120,91 +1134,83 @@ TEST_F(TopSitesTest, Blacklisting) { url1.redirects.push_back(url1.url); pages.push_back(url1); - CancelableRequestConsumer c; - top_sites().GetMostVisitedURLs( - &c, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - top_sites().OnTopSitesAvailable(0, pages); - MessageLoop::current()->RunAllPending(); + SetTopSites(pages); + EXPECT_FALSE(top_sites()->IsBlacklisted(GURL("http://bbc.com/"))); + + // Blacklist google.com. + top_sites()->AddBlacklistedURL(GURL("http://google.com/")); + + GURL prepopulate_url = GetPrepopulatePages()[0].url; + + EXPECT_TRUE(top_sites()->HasBlacklistedItems()); + EXPECT_TRUE(top_sites()->IsBlacklisted(GURL("http://google.com/"))); + EXPECT_FALSE(top_sites()->IsBlacklisted(GURL("http://bbc.com/"))); + EXPECT_FALSE(top_sites()->IsBlacklisted(prepopulate_url)); + + // Make sure the blacklisted site isn't returned in the results. { - Lock& l = lock(); - AutoLock lock(l); // IsBlacklisted must be in a lock. - EXPECT_FALSE(top_sites().IsBlacklisted(GURL("http://bbc.com/"))); + TopSitesQuerier q; + q.QueryTopSites(top_sites(), true); + ASSERT_EQ(1u + GetPrepopulatePages().size(), q.urls().size()); + EXPECT_EQ("http://bbc.com/", q.urls()[0].url.spec()); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(q, 1)); } - EXPECT_EQ(1u, number_of_callbacks()); + // Recreate top sites and make sure blacklisted url was correctly read. + RecreateTopSitesAndBlock(); + { + TopSitesQuerier q; + q.QueryTopSites(top_sites(), true); + ASSERT_EQ(1u + GetPrepopulatePages().size(), q.urls().size()); + EXPECT_EQ("http://bbc.com/", q.urls()[0].url.spec()); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(q, 1)); + } - ASSERT_EQ(4u, urls().size()); - EXPECT_EQ("http://bbc.com/", urls()[0].url.spec()); - EXPECT_EQ("http://google.com/", urls()[1].url.spec()); - EXPECT_EQ(welcome_url(), urls()[2].url); - EXPECT_EQ(themes_url(), urls()[3].url); - EXPECT_FALSE(top_sites().HasBlacklistedItems()); + // Blacklist one of the prepopulate urls. + top_sites()->AddBlacklistedURL(prepopulate_url); + EXPECT_TRUE(top_sites()->HasBlacklistedItems()); - top_sites().AddBlacklistedURL(GURL("http://google.com/")); - EXPECT_TRUE(top_sites().HasBlacklistedItems()); + // Make sure the blacklisted prepopulate url isn't returned. { - Lock& l = lock(); - AutoLock lock(l); // IsBlacklisted must be in a lock. - EXPECT_TRUE(top_sites().IsBlacklisted(GURL("http://google.com/"))); - EXPECT_FALSE(top_sites().IsBlacklisted(GURL("http://bbc.com/"))); - EXPECT_FALSE(top_sites().IsBlacklisted(welcome_url())); + TopSitesQuerier q; + q.QueryTopSites(top_sites(), true); + ASSERT_EQ(1u + GetPrepopulatePages().size() - 1, q.urls().size()); + EXPECT_EQ("http://bbc.com/", q.urls()[0].url.spec()); + for (size_t i = 1; i < q.urls().size(); ++i) + EXPECT_NE(prepopulate_url.spec(), q.urls()[i].url.spec()); } - top_sites().GetMostVisitedURLs( - &c, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - MessageLoop::current()->RunAllPending(); - EXPECT_EQ(2u, number_of_callbacks()); - ASSERT_EQ(3u, urls().size()); - EXPECT_EQ("http://bbc.com/", urls()[0].url.spec()); - EXPECT_EQ(welcome_url(), urls()[1].url); - EXPECT_EQ(themes_url(), urls()[2].url); - - top_sites().AddBlacklistedURL(welcome_url()); - EXPECT_TRUE(top_sites().HasBlacklistedItems()); - top_sites().GetMostVisitedURLs( - &c, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - ASSERT_EQ(2u, urls().size()); - EXPECT_EQ("http://bbc.com/", urls()[0].url.spec()); - EXPECT_EQ(themes_url(), urls()[1].url); - - top_sites().RemoveBlacklistedURL(GURL("http://google.com/")); - EXPECT_TRUE(top_sites().HasBlacklistedItems()); + // Mark google as no longer blacklisted. + top_sites()->RemoveBlacklistedURL(GURL("http://google.com/")); + EXPECT_TRUE(top_sites()->HasBlacklistedItems()); + EXPECT_FALSE(top_sites()->IsBlacklisted(GURL("http://google.com/"))); + + // Make sure google is returned now. { - Lock& l = lock(); - AutoLock lock(l); // IsBlacklisted must be in a lock. - EXPECT_FALSE(top_sites().IsBlacklisted(GURL("http://google.com/"))); + TopSitesQuerier q; + q.QueryTopSites(top_sites(), true); + ASSERT_EQ(2u + GetPrepopulatePages().size() - 1, q.urls().size()); + EXPECT_EQ("http://bbc.com/", q.urls()[0].url.spec()); + EXPECT_EQ("http://google.com/", q.urls()[1].url.spec()); + EXPECT_NE(prepopulate_url.spec(), q.urls()[2].url.spec()); } - top_sites().GetMostVisitedURLs( - &c, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - ASSERT_EQ(3u, urls().size()); - EXPECT_EQ("http://bbc.com/", urls()[0].url.spec()); - EXPECT_EQ("http://google.com/", urls()[1].url.spec()); - EXPECT_EQ(themes_url(), urls()[2].url); - - top_sites().ClearBlacklistedURLs(); - EXPECT_FALSE(top_sites().HasBlacklistedItems()); - top_sites().GetMostVisitedURLs( - &c, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - ASSERT_EQ(4u, urls().size()); - EXPECT_EQ("http://bbc.com/", urls()[0].url.spec()); - EXPECT_EQ("http://google.com/", urls()[1].url.spec()); - EXPECT_EQ(welcome_url(), urls()[2].url); - EXPECT_EQ(themes_url(), urls()[3].url); + // Remove all blacklisted sites. + top_sites()->ClearBlacklistedURLs(); + EXPECT_FALSE(top_sites()->HasBlacklistedItems()); + + { + TopSitesQuerier q; + q.QueryTopSites(top_sites(), true); + ASSERT_EQ(2u + GetPrepopulatePages().size(), q.urls().size()); + EXPECT_EQ("http://bbc.com/", q.urls()[0].url.spec()); + EXPECT_EQ("http://google.com/", q.urls()[1].url.spec()); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(q, 2)); + } } +// Tests variations of pinning/unpinning urls. TEST_F(TopSitesTest, PinnedURLs) { - BrowserThread db_loop(BrowserThread::DB, MessageLoop::current()); MostVisitedURLList pages; MostVisitedURL url, url1; url.url = GURL("http://bbc.com/"); @@ -1214,127 +1220,127 @@ TEST_F(TopSitesTest, PinnedURLs) { url1.redirects.push_back(url1.url); pages.push_back(url1); - CancelableRequestConsumer c; - top_sites().GetMostVisitedURLs( - &c, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - top_sites().OnTopSitesAvailable(0, pages); - MessageLoop::current()->RunAllPending(); - EXPECT_FALSE(top_sites().IsURLPinned(GURL("http://bbc.com/"))); - - ASSERT_EQ(4u, urls().size()); - EXPECT_EQ("http://bbc.com/", urls()[0].url.spec()); - EXPECT_EQ("http://google.com/", urls()[1].url.spec()); - EXPECT_EQ(welcome_url(), urls()[2].url); - EXPECT_EQ(themes_url(), urls()[3].url); - - top_sites().AddPinnedURL(GURL("http://google.com/"), 3); - EXPECT_FALSE(top_sites().IsURLPinned(GURL("http://bbc.com/"))); - EXPECT_FALSE(top_sites().IsURLPinned(welcome_url())); - - top_sites().GetMostVisitedURLs( - &c, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - EXPECT_EQ(2u, number_of_callbacks()); - ASSERT_EQ(4u, urls().size()); - EXPECT_EQ("http://bbc.com/", urls()[0].url.spec()); - EXPECT_EQ(welcome_url(), urls()[1].url); - EXPECT_EQ(themes_url(), urls()[2].url); - EXPECT_EQ("http://google.com/", urls()[3].url.spec()); - - top_sites().RemovePinnedURL(GURL("http://google.com/")); - EXPECT_FALSE(top_sites().IsURLPinned(GURL("http://google.com/"))); - top_sites().GetMostVisitedURLs( - &c, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - - ASSERT_EQ(4u, urls().size()); - EXPECT_EQ("http://bbc.com/", urls()[0].url.spec()); - EXPECT_EQ("http://google.com/", urls()[1].url.spec()); - EXPECT_EQ(welcome_url(), urls()[2].url); - EXPECT_EQ(themes_url(), urls()[3].url); - - top_sites().AddPinnedURL(GURL("http://bbc.com"), 1); - top_sites().AddPinnedURL(themes_url(), 0); - top_sites().GetMostVisitedURLs( - &c, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - - ASSERT_EQ(4u, urls().size()); - EXPECT_EQ(themes_url(), urls()[0].url); - EXPECT_EQ("http://bbc.com/", urls()[1].url.spec()); - EXPECT_EQ("http://google.com/", urls()[2].url.spec()); - EXPECT_EQ(welcome_url(), urls()[3].url); - - top_sites().RemovePinnedURL(GURL("http://bbc.com")); - top_sites().RemovePinnedURL(themes_url()); - - top_sites().AddPinnedURL(welcome_url(), 1); - top_sites().AddPinnedURL(GURL("http://bbc.com"), 3); - - top_sites().GetMostVisitedURLs( - &c, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - - ASSERT_EQ(4u, urls().size()); - EXPECT_EQ("http://google.com/", urls()[0].url.spec()); - EXPECT_EQ(welcome_url(), urls()[1].url); - EXPECT_EQ(themes_url(), urls()[2].url); - EXPECT_EQ("http://bbc.com/", urls()[3].url.spec()); + SetTopSites(pages); + + EXPECT_FALSE(top_sites()->IsURLPinned(GURL("http://bbc.com/"))); + + { + TopSitesQuerier q; + q.QueryTopSites(top_sites(), true); + ASSERT_EQ(2u + GetPrepopulatePages().size(), q.urls().size()); + EXPECT_EQ("http://bbc.com/", q.urls()[0].url.spec()); + EXPECT_EQ("http://google.com/", q.urls()[1].url.spec()); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(q, 2)); + } + + top_sites()->AddPinnedURL(GURL("http://google.com/"), 3); + EXPECT_FALSE(top_sites()->IsURLPinned(GURL("http://bbc.com/"))); + EXPECT_FALSE(top_sites()->IsURLPinned(GetPrepopulatePages()[0].url)); + + { + TopSitesQuerier q; + q.QueryTopSites(top_sites(), true); + ASSERT_EQ(4u, q.urls().size()); + EXPECT_EQ("http://bbc.com/", q.urls()[0].url.spec()); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(q, 1)); + EXPECT_EQ("http://google.com/", q.urls()[3].url.spec()); + } + + top_sites()->RemovePinnedURL(GURL("http://google.com/")); + EXPECT_FALSE(top_sites()->IsURLPinned(GURL("http://google.com/"))); + { + TopSitesQuerier q; + q.QueryTopSites(top_sites(), true); + + ASSERT_EQ(2u + GetPrepopulatePages().size(), q.urls().size()); + EXPECT_EQ("http://bbc.com/", q.urls()[0].url.spec()); + EXPECT_EQ("http://google.com/", q.urls()[1].url.spec()); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(q, 2)); + } + + GURL prepopulate_url = GetPrepopulatePages()[0].url; + top_sites()->AddPinnedURL(GURL("http://bbc.com"), 1); + top_sites()->AddPinnedURL(prepopulate_url, 0); + { + TopSitesQuerier q; + q.QueryTopSites(top_sites(), true); + + ASSERT_EQ(3u + GetPrepopulatePages().size() - 1, q.urls().size()); + EXPECT_EQ(prepopulate_url, q.urls()[0].url); + EXPECT_EQ("http://bbc.com/", q.urls()[1].url.spec()); + EXPECT_EQ("http://google.com/", q.urls()[2].url.spec()); + if (GetPrepopulatePages().size() > 1) + EXPECT_EQ(GetPrepopulatePages()[1].url, q.urls()[3].url); + } + + // Recreate and make sure state remains the same. + RecreateTopSitesAndBlock(); + { + TopSitesQuerier q; + q.QueryTopSites(top_sites(), true); + + ASSERT_EQ(3u + GetPrepopulatePages().size() - 1, q.urls().size()); + EXPECT_EQ(prepopulate_url, q.urls()[0].url); + EXPECT_EQ("http://bbc.com/", q.urls()[1].url.spec()); + EXPECT_EQ("http://google.com/", q.urls()[2].url.spec()); + if (GetPrepopulatePages().size() > 1) + EXPECT_EQ(GetPrepopulatePages()[1].url, q.urls()[3].url); + } } +// Tests blacklisting and pinning. TEST_F(TopSitesTest, BlacklistingAndPinnedURLs) { - BrowserThread db_loop(BrowserThread::DB, MessageLoop::current()); - MostVisitedURLList pages; - CancelableRequestConsumer c; - top_sites().GetMostVisitedURLs( - &c, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - top_sites().OnTopSitesAvailable(0, pages); - MessageLoop::current()->RunAllPending(); - - ASSERT_EQ(2u, urls().size()); - EXPECT_EQ(welcome_url(), urls()[0].url); - EXPECT_EQ(themes_url(), urls()[1].url); - - top_sites().AddPinnedURL(themes_url(), 1); - top_sites().AddBlacklistedURL(welcome_url()); - - top_sites().GetMostVisitedURLs( - &c, - NewCallback(static_cast<TopSitesTest*>(this), - &TopSitesTest::OnTopSitesAvailable)); - - ASSERT_EQ(2u, urls().size()); - EXPECT_EQ(GURL(), urls()[0].url); - EXPECT_EQ(themes_url(), urls()[1].url); + MostVisitedURLList prepopulate_urls = GetPrepopulatePages(); + if (prepopulate_urls.size() < 2) + return; + + top_sites()->AddPinnedURL(prepopulate_urls[0].url, 1); + top_sites()->AddBlacklistedURL(prepopulate_urls[1].url); + { + TopSitesQuerier q; + q.QueryTopSites(top_sites(), true); + + ASSERT_LE(2u, q.urls().size()); + EXPECT_EQ(GURL(), q.urls()[0].url); + EXPECT_EQ(prepopulate_urls[0].url, q.urls()[1].url); + } } +// Makes sure prepopulated pages exist. TEST_F(TopSitesTest, AddPrepopulatedPages) { - MostVisitedURLList pages; - top_sites().AddPrepopulatedPages(&pages); - ASSERT_EQ(2u, pages.size()); - EXPECT_EQ(welcome_url(), pages[0].url); - EXPECT_EQ(themes_url(), pages[1].url); - - pages.clear(); + TopSitesQuerier q; + q.QueryTopSites(top_sites(), true); + EXPECT_EQ(GetPrepopulatePages().size(), q.urls().size()); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(q, 0)); - MostVisitedURL url(themes_url(), GURL(), string16()); - pages.push_back(url); + MostVisitedURLList pages = q.urls(); + EXPECT_FALSE(AddPrepopulatedPages(&pages)); - top_sites().AddPrepopulatedPages(&pages); + EXPECT_EQ(GetPrepopulatePages().size(), pages.size()); + q.set_urls(pages); + ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(q, 0)); +} - // Themes URL is already in pages; should not be added twice. - ASSERT_EQ(2u, pages.size()); - EXPECT_EQ(themes_url(), pages[0].url); - EXPECT_EQ(welcome_url(), pages[1].url); +// Makes sure creating top sites before history is created works. +TEST_F(TopSitesTest, CreateTopSitesThenHistory) { + profile()->DestroyTopSites(); + profile()->DestroyHistoryService(); + + // Remove the TopSites file. This forces TopSites to wait until history loads + // before TopSites is considered loaded. + file_util::Delete(profile()->GetPath().Append(chrome::kTopSitesFilename), + false); + + // Create TopSites, but not History. + profile()->CreateTopSites(); + WaitForTopSites(); + EXPECT_FALSE(IsTopSitesLoaded()); + + // Load history, which should make TopSites finish loading too. + profile()->CreateHistoryService(false, false); + profile()->BlockUntilTopSitesLoaded(); + EXPECT_TRUE(IsTopSitesLoaded()); } } // namespace history diff --git a/chrome/browser/history/url_database.cc b/chrome/browser/history/url_database.cc index b63d24a..3f297bd 100644 --- a/chrome/browser/history/url_database.cc +++ b/chrome/browser/history/url_database.cc @@ -247,16 +247,29 @@ bool URLDatabase::IsFavIconUsed(FavIconID favicon_id) { void URLDatabase::AutocompleteForPrefix(const string16& prefix, size_t max_results, + bool typed_only, std::vector<history::URLRow>* results) { // NOTE: this query originally sorted by starred as the second parameter. But // as bookmarks is no longer part of the db we no longer include the order // by clause. results->clear(); - sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, - "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls " - "WHERE url >= ? AND url < ? AND hidden = 0 " - "ORDER BY typed_count DESC, visit_count DESC, last_visit_time DESC " - "LIMIT ?")); + const char* sql; + int line; + if (typed_only) { + sql = "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls " + "WHERE url >= ? AND url < ? AND hidden = 0 AND typed_count > 0 " + "ORDER BY typed_count DESC, visit_count DESC, last_visit_time DESC " + "LIMIT ?"; + line = __LINE__; + } else { + sql = "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls " + "WHERE url >= ? AND url < ? AND hidden = 0 " + "ORDER BY typed_count DESC, visit_count DESC, last_visit_time DESC " + "LIMIT ?"; + line = __LINE__; + } + sql::Statement statement( + GetDB().GetCachedStatement(sql::StatementID(__FILE__, line), sql)); if (!statement) return; @@ -327,7 +340,10 @@ bool URLDatabase::InitKeywordSearchTermsTable() { "term LONGVARCHAR NOT NULL)")) // The actual search term. return false; } + return true; +} +void URLDatabase::CreateKeywordSearchTermsIndices() { // For searching. GetDB().Execute("CREATE INDEX keyword_search_terms_index1 ON " "keyword_search_terms (keyword_id, lower_term)"); @@ -335,8 +351,6 @@ bool URLDatabase::InitKeywordSearchTermsTable() { // For deletion. GetDB().Execute("CREATE INDEX keyword_search_terms_index2 ON " "keyword_search_terms (url_id)"); - - return true; } bool URLDatabase::DropKeywordSearchTermsTable() { diff --git a/chrome/browser/history/url_database.h b/chrome/browser/history/url_database.h index 36bfebb..49bf5f0 100644 --- a/chrome/browser/history/url_database.h +++ b/chrome/browser/history/url_database.h @@ -138,10 +138,12 @@ class URLDatabase { // Autocomplete -------------------------------------------------------------- // Fills the given array with URLs matching the given prefix. They will be - // sorted by typed count, then by visit count, then by visit date (most - // recent first) up to the given maximum number. Called by HistoryURLProvider. + // sorted by typed count, then by visit count, then by visit date (most recent + // first) up to the given maximum number. If |typed_only| is true, only urls + // that have been typed once are returned. Called by HistoryURLProvider. void AutocompleteForPrefix(const string16& prefix, size_t max_results, + bool typed_only, std::vector<URLRow>* results); // Tries to find the shortest URL beginning with |base| that strictly @@ -218,6 +220,9 @@ class URLDatabase { // Ensures the keyword search terms table exists. bool InitKeywordSearchTermsTable(); + // Creates the indices used for keyword search terms. + void CreateKeywordSearchTermsIndices(); + // Deletes the keyword search terms table. bool DropKeywordSearchTermsTable(); diff --git a/chrome/browser/history/url_database_unittest.cc b/chrome/browser/history/url_database_unittest.cc index f567844..c37323f 100644 --- a/chrome/browser/history/url_database_unittest.cc +++ b/chrome/browser/history/url_database_unittest.cc @@ -52,6 +52,7 @@ class URLDatabaseTest : public testing::Test, CreateMainURLIndex(); CreateSupplimentaryURLIndices(); InitKeywordSearchTermsTable(); + CreateKeywordSearchTermsIndices(); } void TearDown() { db_.Close(); |