summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/history/top_sites.cc5
-rw-r--r--chrome/browser/history/top_sites.h3
-rw-r--r--chrome/browser/history/top_sites_database.cc235
-rw-r--r--chrome/browser/history/top_sites_database.h59
-rw-r--r--chrome/browser/history/top_sites_unittest.cc202
-rw-r--r--chrome/chrome_browser.gypi1
6 files changed, 496 insertions, 9 deletions
diff --git a/chrome/browser/history/top_sites.cc b/chrome/browser/history/top_sites.cc
index ab610a0..d8ea651 100644
--- a/chrome/browser/history/top_sites.cc
+++ b/chrome/browser/history/top_sites.cc
@@ -82,14 +82,15 @@ bool TopSites::SetPageThumbnail(const GURL& url,
Images& image = top_images_[most_visited.url];
// When comparing the thumbnail scores, we need to take into account the
- // redirect hops, which are not generated when the thumbnail is becuase the
+ // redirect hops, which are not generated when the thumbnail is because the
// redirects weren't known. We fill that in here since we know the redirects.
ThumbnailScore new_score_with_redirects(score);
new_score_with_redirects.redirect_hops_from_dest =
GetRedirectDistanceForURL(most_visited, url);
if (!ShouldReplaceThumbnailWith(image.thumbnail_score,
- new_score_with_redirects))
+ new_score_with_redirects) &&
+ image.thumbnail.get())
return false; // The one we already have is better.
// Take ownership of the thumbnail data.
diff --git a/chrome/browser/history/top_sites.h b/chrome/browser/history/top_sites.h
index 0b9b1e4..4f6f854 100644
--- a/chrome/browser/history/top_sites.h
+++ b/chrome/browser/history/top_sites.h
@@ -86,7 +86,8 @@ class TopSites : public base::RefCountedThreadSafe<TopSites> {
friend class base::RefCountedThreadSafe<TopSites>;
friend class TopSitesTest;
friend class TopSitesTest_GetMostVisited_Test;
- friend class TopSitesTest_ReadDatabase_Test;
+ friend class TopSitesTest_RealDatabase_Test;
+ friend class TopSitesTest_MockDatabase_Test;
~TopSites();
diff --git a/chrome/browser/history/top_sites_database.cc b/chrome/browser/history/top_sites_database.cc
new file mode 100644
index 0000000..5b13d61
--- /dev/null
+++ b/chrome/browser/history/top_sites_database.cc
@@ -0,0 +1,235 @@
+// Copyright (c) 2009 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 "app/sql/transaction.h"
+#include "base/string_util.h"
+#include "chrome/browser/diagnostics/sqlite_diagnostics.h"
+#include "chrome/browser/history/top_sites.h"
+#include "chrome/browser/history/top_sites_database.h"
+
+namespace history {
+
+TopSitesDatabaseImpl::TopSitesDatabaseImpl() {
+}
+
+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);
+
+ if (!db_.Open(db_name)) {
+ LOG(WARNING) << db_.GetErrorMessage();
+ return sql::INIT_FAILURE;
+ }
+
+ return InitThumbnailTable();
+}
+
+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)")) {
+ LOG(WARNING) << db_.GetErrorMessage();
+ return false;
+ }
+ }
+ return true;
+}
+
+MostVisitedURLList TopSitesDatabaseImpl::GetTopURLs() {
+ MostVisitedURLList result;
+ sql::Statement select_statement(db_.GetCachedStatement(
+ SQL_FROM_HERE,
+ "SELECT url, url_rank, title, redirects "
+ "FROM thumbnails ORDER BY url_rank"));
+
+ if (!select_statement) {
+ LOG(WARNING) << db_.GetErrorMessage();
+ return result;
+ }
+
+ int row_count = 0;
+ int max_rank = -1; // -1 is for the case of empty DB.
+ while (select_statement.Step()) {
+ // Results are sorted by url_rank.
+ MostVisitedURL url;
+ GURL gurl(select_statement.ColumnString(0));
+ url.url = gurl;
+ url.title = select_statement.ColumnString16(2);
+ std::string redirects = select_statement.ColumnString(3);
+ SetRedirects(redirects, &url);
+ result.push_back(url);
+ }
+ DCHECK(select_statement.Succeeded());
+ DCHECK_EQ(max_rank + 1, row_count);
+
+ return result;
+}
+
+// static
+std::string TopSitesDatabaseImpl::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());
+ return JoinString(redirects, ' ');
+}
+
+// static
+void TopSitesDatabaseImpl::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,
+ int new_rank,
+ const TopSites::Images& thumbnail) {
+ sql::Transaction transaction(&db_);
+ transaction.Begin();
+
+ int prev_rank = GetURLRank(url);
+
+ sql::Statement statement(db_.GetCachedStatement(
+ SQL_FROM_HERE,
+ "INSERT OR REPLACE INTO thumbnails "
+ "(url, url_rank, title, thumbnail, redirects) "
+ "VALUES (?, ?, ?, ?, ?)")); // TODO(Nik): add the rest of the schema.
+ if (!statement)
+ return;
+
+ statement.BindString(0, url.url.spec());
+ statement.BindInt(1, -1); // Temporary value
+ statement.BindString16(2, url.title);
+ if (thumbnail.thumbnail.get()) {
+ statement.BindBlob(3, &thumbnail.thumbnail->data.front(),
+ static_cast<int>(thumbnail.thumbnail->data.size()));
+ }
+ statement.BindString(4, GetRedirects(url));
+
+ if (!statement.Run())
+ NOTREACHED() << db_.GetErrorMessage();
+
+ // Shift the ranks.
+ if (prev_rank == -1) {
+ // Shift up
+ sql::Statement shift_statement(db_.GetCachedStatement(
+ SQL_FROM_HERE,
+ "UPDATE thumbnails "
+ "SET url_rank = url_rank + 1 WHERE url_rank >= ?"));
+ shift_statement.BindInt(0, new_rank);
+ if (shift_statement)
+ shift_statement.Run();
+ } else if (prev_rank > new_rank) {
+ // Shift up
+ sql::Statement shift_statement(db_.GetCachedStatement(
+ SQL_FROM_HERE,
+ "UPDATE thumbnails "
+ "SET url_rank = url_rank + 1 "
+ "WHERE url_rank >= ? AND url_rank < ?"));
+ shift_statement.BindInt(0, new_rank);
+ shift_statement.BindInt(1, prev_rank);
+ if (shift_statement)
+ shift_statement.Run();
+ } else if (prev_rank < new_rank) {
+ // Shift down
+ sql::Statement shift_statement(db_.GetCachedStatement(
+ SQL_FROM_HERE,
+ "UPDATE thumbnails "
+ "SET url_rank = url_rank - 1 "
+ "WHERE url_rank > ? AND url_rank <= ?"));
+ shift_statement.BindInt(0, prev_rank);
+ shift_statement.BindInt(1, new_rank);
+ if (shift_statement)
+ shift_statement.Run();
+ }
+
+ // Set the real rank.
+ sql::Statement set_statement(db_.GetCachedStatement(
+ SQL_FROM_HERE,
+ "UPDATE thumbnails "
+ "SET url_rank = ? "
+ "WHERE url_rank == -1"));
+ set_statement.BindInt(0, new_rank);
+ if (set_statement)
+ set_statement.Run();
+ transaction.Commit();
+}
+
+bool TopSitesDatabaseImpl::GetPageThumbnail(const MostVisitedURL& url,
+ TopSites::Images* thumbnail) {
+ sql::Statement select_statement(db_.GetCachedStatement(
+ SQL_FROM_HERE,
+ "SELECT thumbnail "
+ "FROM thumbnails WHERE url=?"));
+
+ if (!select_statement) {
+ LOG(WARNING) << db_.GetErrorMessage();
+ return false;
+ }
+
+ select_statement.BindString(0, url.url.spec());
+ if (!select_statement.Step())
+ return false;
+
+ std::vector<unsigned char> data;
+ select_statement.ColumnBlobAsVector(0, &data);
+ thumbnail->thumbnail = RefCountedBytes::TakeVector(&data);
+ return true;
+}
+
+int TopSitesDatabaseImpl::GetURLRank(const MostVisitedURL& url) {
+ int result = -1;
+ sql::Statement select_statement(db_.GetCachedStatement(
+ SQL_FROM_HERE,
+ "SELECT url_rank "
+ "FROM thumbnails WHERE url=?"));
+ if (!select_statement) {
+ LOG(WARNING) << db_.GetErrorMessage();
+ return result;
+ }
+
+ select_statement.BindString(0, url.url.spec());
+ if (select_statement.Step())
+ result = select_statement.ColumnInt(0);
+
+ return result;
+}
+
+// Remove the record for this URL. Returns true iff removed successfully.
+bool TopSitesDatabaseImpl::RemoveURL(const MostVisitedURL& url) {
+ int old_rank = GetURLRank(url);
+ sql::Transaction transaction(&db_);
+ transaction.Begin();
+ // Decrement all following ranks.
+ sql::Statement shift_statement(db_.GetCachedStatement(
+ SQL_FROM_HERE,
+ "UPDATE thumbnails "
+ "SET url_rank = url_rank - 1 "
+ "WHERE url_rank > ?"));
+ if (!shift_statement)
+ return false;
+ shift_statement.BindInt(0, old_rank);
+ shift_statement.Run();
+
+ sql::Statement delete_statement(
+ db_.GetCachedStatement(SQL_FROM_HERE,
+ "DELETE FROM thumbnails WHERE url = ?"));
+ if (!delete_statement)
+ return false;
+ delete_statement.BindString(0, url.url.spec());
+ delete_statement.Run();
+
+ return transaction.Commit();
+}
+
+} // namespace history
+
diff --git a/chrome/browser/history/top_sites_database.h b/chrome/browser/history/top_sites_database.h
index 50da75d..2a664a6 100644
--- a/chrome/browser/history/top_sites_database.h
+++ b/chrome/browser/history/top_sites_database.h
@@ -5,6 +5,7 @@
#ifndef CHROME_BROWSER_HISTORY_TOP_SITES_DATABASE_H_
#define CHROME_BROWSER_HISTORY_TOP_SITES_DATABASE_H_
+#include <string>
#include <vector>
#include "app/sql/connection.h"
@@ -17,6 +18,7 @@
class FilePath;
class RefCountedMemory;
class SkBitmap;
+class TopSites;
namespace base {
class Time;
@@ -39,17 +41,68 @@ class TopSitesDatabase {
// thumbnail.
virtual void SetPageThumbnail(const MostVisitedURL& url,
int url_rank,
- const TopSites::Images thumbnail) = 0;
+ const TopSites::Images& thumbnail) = 0;
// Get a thumbnail for a given page. Returns true iff we have the thumbnail.
virtual bool GetPageThumbnail(const MostVisitedURL& url,
- TopSites::Images* thumbnail) const = 0;
+ TopSites::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();
+ ~TopSitesDatabaseImpl() {}
+
+ // Must be called after creation but before any other methods are called.
+ // Returns true on success. If false, no other functions should be called.
+ bool Init(const FilePath& db_name);
+
+ // Thumbnails ----------------------------------------------------------------
+
+ // Returns a list of all URLs currently in the table.
+ virtual MostVisitedURLList GetTopURLs();
+
+ // 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 TopSites::Images& thumbnail);
+
+ // Get a thumbnail for a given page. Returns true iff we have the thumbnail.
+ virtual bool GetPageThumbnail(const MostVisitedURL& url,
+ TopSites::Images* thumbnail);
+
+ // Remove the record for this URL. Returns true iff removed successfully.
+ virtual bool RemoveURL(const MostVisitedURL& url);
+
+ private:
+ // Creates the thumbnail table, returning true if the table already exists
+ // or was successfully created.
+ bool InitThumbnailTable();
+
+ // Returns the URL's current rank or -1 if it is not present.
+ int GetURLRank(const MostVisitedURL& url);
+
+ // Gets the URL at |rank|. Returns true if the URL is there.
+ bool GetURLAtRank(int rank, MostVisitedURL* url);
+
+ // Sets the rank of a URL. The URL must be present in the DB.
+ void SetURLRank(const MostVisitedURL& url, int rank);
+
+ // 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_;
+};
+
} // namespace history
#endif // CHROME_BROWSER_HISTORY_TOP_SITES_DATABASE_H_
-
diff --git a/chrome/browser/history/top_sites_unittest.cc b/chrome/browser/history/top_sites_unittest.cc
index fbc1aeb..7463075 100644
--- a/chrome/browser/history/top_sites_unittest.cc
+++ b/chrome/browser/history/top_sites_unittest.cc
@@ -2,10 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "base/scoped_temp_dir.h"
#include "base/string_util.h"
#include "chrome/browser/history/top_sites.h"
+#include "chrome/common/chrome_paths.h"
#include "chrome/browser/history/top_sites_database.h"
#include "chrome/test/testing_profile.h"
+#include "chrome/tools/profiles/thumbnail-inl.h"
#include "googleurl/src/gurl.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
@@ -13,6 +16,9 @@
namespace history {
+static const unsigned char kBlob[] =
+ "12346102356120394751634516591348710478123649165419234519234512349134";
+
class TopSitesTest : public testing::Test {
public:
TopSitesTest() {
@@ -21,15 +27,30 @@ class TopSitesTest : public testing::Test {
}
TopSites& top_sites() { return *top_sites_; }
+ FilePath& file_name() { return file_name_; }
+ RefCountedBytes* google_thumbnail() { return google_thumbnail_; }
+ RefCountedBytes* random_thumbnail() { return random_thumbnail_; }
virtual void SetUp() {
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));
+ random_thumbnail_ = new RefCountedBytes(random_data);
+ google_thumbnail_ = new RefCountedBytes(google_data);
}
virtual void TearDown() {
profile_.reset();
top_sites_ = NULL;
+ EXPECT_TRUE(file_util::Delete(file_name_, false));
}
// Wrappers that allow private TopSites functions to be called from the
@@ -56,6 +77,10 @@ class TopSitesTest : public testing::Test {
private:
scoped_refptr<TopSites> top_sites_;
scoped_ptr<TestingProfile> profile_;
+ ScopedTempDir temp_dir_;
+ FilePath file_name_; // Database filename.
+ scoped_refptr<RefCountedBytes> google_thumbnail_;
+ scoped_refptr<RefCountedBytes> random_thumbnail_;
DISALLOW_COPY_AND_ASSIGN(TopSitesTest);
};
@@ -99,7 +124,7 @@ class MockTopSitesDatabaseImpl : public TopSitesDatabase {
}
virtual void SetPageThumbnail(const MostVisitedURL& url, int url_rank,
- const TopSites::Images thumbnail) {
+ const TopSites::Images& thumbnail) {
// Update the list of URLs
// Check if this url is in the list, and at which position.
MostVisitedURLList::iterator pos = std::find(top_sites_list_.begin(),
@@ -123,7 +148,7 @@ class MockTopSitesDatabaseImpl : public TopSitesDatabase {
// Get a thumbnail for a given page. Returns true iff we have the thumbnail.
virtual bool GetPageThumbnail(const MostVisitedURL& url,
- TopSites::Images* thumbnail) const {
+ TopSites::Images* thumbnail) {
std::map<GURL, TopSites::Images>::const_iterator found =
thumbnails_map_.find(url.url);
if (found == thumbnails_map_.end())
@@ -163,6 +188,16 @@ 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;
+ 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(
@@ -305,7 +340,7 @@ TEST_F(TopSitesTest, GetMostVisited) {
EXPECT_EQ(google, results[1].url);
}
-TEST_F(TopSitesTest, ReadDatabase) {
+TEST_F(TopSitesTest, MockDatabase) {
MockTopSitesDatabaseImpl* db = new MockTopSitesDatabaseImpl;
// |db| is destroyed when the top_sites is destroyed in TearDown.
top_sites().db_.reset(db);
@@ -362,4 +397,165 @@ TEST_F(TopSitesTest, ReadDatabase) {
EXPECT_EQ(news_title, result[1].title);
}
+// Test TopSitesDatabaseImpl.
+TEST_F(TopSitesTest, TopSitesDB) {
+ TopSitesDatabaseImpl db;
+
+ ASSERT_TRUE(db.Init(file_name()));
+
+ 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"));
+
+ url.url = asdf_url;
+ url.title = asdf_title;
+ url.redirects.push_back(url.url);
+ TopSites::Images thumbnail;
+ thumbnail.thumbnail = random_thumbnail();
+ // Add asdf at rank 0.
+ db.SetPageThumbnail(url, 0, thumbnail);
+
+ TopSites::Images result;
+ EXPECT_TRUE(db.GetPageThumbnail(url, &result));
+ EXPECT_EQ(thumbnail.thumbnail->data.size(), result.thumbnail->data.size());
+ EXPECT_TRUE(ThumbnailsAreEqual(thumbnail.thumbnail, result.thumbnail));
+
+ MostVisitedURLList urls = db.GetTopURLs();
+ 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);
+ urls = db.GetTopURLs();
+ 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);
+ urls = db.GetTopURLs();
+ 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);
+
+ db.GetTopURLs();
+ // Move news at rank 0 - shift the rest up.
+ db.SetPageThumbnail(url, 0, thumbnail);
+ urls = db.GetTopURLs();
+ 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);
+ urls = db.GetTopURLs();
+ 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);
+
+ urls = db.GetTopURLs();
+ ASSERT_EQ(2u, urls.size());
+ EXPECT_EQ(google_url, urls[0].url);
+ EXPECT_EQ(news_url, urls[1].url);
+}
+
+// Test TopSites with a real database.
+TEST_F(TopSitesTest, RealDatabase) {
+ 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"));
+ GURL google1_url("http://google.com");
+ GURL google2_url("http://google.com/redirect");
+ GURL google3_url("http://www.google.com");
+ string16 google_title(ASCIIToUTF16("Google"));
+ 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);
+ TopSites::Images thumbnail;
+ thumbnail.thumbnail = random_thumbnail();
+ db->SetPageThumbnail(url, 0, thumbnail);
+
+ top_sites().ReadDatabase();
+
+ MostVisitedURLList result = top_sites().GetMostVisitedURLs();
+ ASSERT_EQ(1u, result.size());
+ EXPECT_EQ(asdf_url, result[0].url);
+ EXPECT_EQ(asdf_title, result[0].title);
+
+ RefCountedBytes* thumbnail_result;
+ top_sites().GetPageThumbnail(asdf_url, &thumbnail_result);
+ EXPECT_TRUE(thumbnail_result != NULL);
+ EXPECT_TRUE(ThumbnailsAreEqual(random_thumbnail(), thumbnail_result));
+
+ MostVisitedURL url2;
+ url2.url = google1_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.
+ TopSites::Images g_thumbnail;
+ g_thumbnail.thumbnail = google_thumbnail();
+ db->SetPageThumbnail(url2, 0, g_thumbnail);
+
+ top_sites().ReadDatabase();
+
+ result = top_sites().GetMostVisitedURLs();
+ ASSERT_EQ(2u, result.size());
+ EXPECT_EQ(google1_url, result[0].url);
+ EXPECT_EQ(google_title, result[0].title);
+ top_sites().GetPageThumbnail(google1_url, &thumbnail_result);
+ EXPECT_TRUE(ThumbnailsAreEqual(google_thumbnail(), thumbnail_result));
+ ASSERT_EQ(3u, result[0].redirects.size());
+ EXPECT_EQ(google1_url, result[0].redirects[0]);
+ EXPECT_EQ(google2_url, result[0].redirects[1]);
+ EXPECT_EQ(google3_url, result[0].redirects[2]);
+
+ EXPECT_EQ(asdf_url, result[1].url);
+ EXPECT_EQ(asdf_title, result[1].title);
+
+ 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 writes the new data to the DB.
+ top_sites().StartQueryForMostVisited();
+
+ result = db->GetTopURLs();
+ ASSERT_EQ(2u, result.size());
+ EXPECT_EQ(google_title, result[0].title);
+ EXPECT_EQ(news_title, result[1].title);
+}
+
} // namespace history
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 890b972..cc2147c 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1498,6 +1498,7 @@
'browser/history/thumbnail_database.cc',
'browser/history/thumbnail_database.h',
'browser/history/top_sites.cc',
+ 'browser/history/top_sites_database.cc',
'browser/history/url_database.cc',
'browser/history/url_database.h',
'browser/history/visit_database.cc',