blob: 4fba5854833f7add9030a516b85078e4729c83fe [file] [log] [blame]
// Copyright (c) 2012 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 "components/history/core/browser/history_backend.h"
#include <stddef.h>
#include <algorithm>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/histogram_samples.h"
#include "base/metrics/statistics_recorder.h"
#include "base/run_loop.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/histogram_tester.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "components/favicon_base/favicon_usage_data.h"
#include "components/history/core/browser/history_backend_client.h"
#include "components/history/core/browser/history_constants.h"
#include "components/history/core/browser/history_database_params.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/history_service_observer.h"
#include "components/history/core/browser/in_memory_database.h"
#include "components/history/core/browser/in_memory_history_backend.h"
#include "components/history/core/browser/keyword_search_term.h"
#include "components/history/core/browser/visit_delegate.h"
#include "components/history/core/test/database_test_utils.h"
#include "components/history/core/test/history_client_fake_bookmarks.h"
#include "components/history/core/test/test_history_database.h"
#include "components/prefs/pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/png_codec.h"
#include "url/gurl.h"
// This file only tests functionality where it is most convenient to call the
// backend directly. Most of the history backend functions are tested by the
// history unit test. Because of the elaborate callbacks involved, this is no
// harder than calling it directly for many things.
namespace {
using ::testing::ElementsAre;
using base::HistogramBase;
const int kTinyEdgeSize = 10;
const int kSmallEdgeSize = 16;
const int kLargeEdgeSize = 32;
const gfx::Size kTinySize = gfx::Size(kTinyEdgeSize, kTinyEdgeSize);
const gfx::Size kSmallSize = gfx::Size(kSmallEdgeSize, kSmallEdgeSize);
const gfx::Size kLargeSize = gfx::Size(kLargeEdgeSize, kLargeEdgeSize);
typedef base::Callback<void(const history::URLRow*,
const history::URLRow*,
const history::URLRow*)>
SimulateNotificationCallback;
void SimulateNotificationURLVisited(history::HistoryServiceObserver* observer,
const history::URLRow* row1,
const history::URLRow* row2,
const history::URLRow* row3) {
history::URLRows rows;
rows.push_back(*row1);
if (row2)
rows.push_back(*row2);
if (row3)
rows.push_back(*row3);
base::Time visit_time;
history::RedirectList redirects;
for (const history::URLRow& row : rows) {
observer->OnURLVisited(nullptr, ui::PAGE_TRANSITION_LINK, row, redirects,
visit_time);
}
}
void SimulateNotificationURLsModified(history::HistoryServiceObserver* observer,
const history::URLRow* row1,
const history::URLRow* row2,
const history::URLRow* row3) {
history::URLRows rows;
rows.push_back(*row1);
if (row2)
rows.push_back(*row2);
if (row3)
rows.push_back(*row3);
observer->OnURLsModified(nullptr, rows);
}
} // namespace
namespace history {
class HistoryBackendTestBase;
// This must be a separate object since HistoryBackend manages its lifetime.
// This just forwards the messages we're interested in to the test object.
class HistoryBackendTestDelegate : public HistoryBackend::Delegate {
public:
explicit HistoryBackendTestDelegate(HistoryBackendTestBase* test)
: test_(test) {}
void NotifyProfileError(sql::InitStatus init_status,
const std::string& diagnostics) override {}
void SetInMemoryBackend(
std::unique_ptr<InMemoryHistoryBackend> backend) override;
void NotifyFaviconsChanged(const std::set<GURL>& page_urls,
const GURL& icon_url) override;
void NotifyURLVisited(ui::PageTransition transition,
const URLRow& row,
const RedirectList& redirects,
base::Time visit_time) override;
void NotifyURLsModified(const URLRows& changed_urls) override;
void NotifyURLsDeleted(bool all_history,
bool expired,
const URLRows& deleted_rows,
const std::set<GURL>& favicon_urls) override;
void NotifyKeywordSearchTermUpdated(const URLRow& row,
KeywordID keyword_id,
const base::string16& term) override;
void NotifyKeywordSearchTermDeleted(URLID url_id) override;
void DBLoaded() override;
private:
// Not owned by us.
HistoryBackendTestBase* test_;
DISALLOW_COPY_AND_ASSIGN(HistoryBackendTestDelegate);
};
class HistoryBackendTestBase : public testing::Test {
public:
typedef std::vector<std::pair<ui::PageTransition, URLRow>> URLVisitedList;
typedef std::vector<URLRows> URLsModifiedList;
typedef std::vector<std::pair<bool, bool>> URLsDeletedList;
HistoryBackendTestBase()
: loaded_(false) {}
~HistoryBackendTestBase() override {}
protected:
std::vector<GURL> favicon_changed_notifications_page_urls() const {
return favicon_changed_notifications_page_urls_;
}
std::vector<GURL> favicon_changed_notifications_icon_urls() const {
return favicon_changed_notifications_icon_urls_;
}
int num_url_visited_notifications() const {
return url_visited_notifications_.size();
}
const URLVisitedList& url_visited_notifications() const {
return url_visited_notifications_;
}
int num_urls_modified_notifications() const {
return urls_modified_notifications_.size();
}
const URLsModifiedList& urls_modified_notifications() const {
return urls_modified_notifications_;
}
const URLsDeletedList& urls_deleted_notifications() const {
return urls_deleted_notifications_;
}
void ClearBroadcastedNotifications() {
url_visited_notifications_.clear();
urls_modified_notifications_.clear();
urls_deleted_notifications_.clear();
favicon_changed_notifications_page_urls_.clear();
favicon_changed_notifications_icon_urls_.clear();
}
base::FilePath test_dir() { return test_dir_; }
void NotifyFaviconsChanged(const std::set<GURL>& page_urls,
const GURL& icon_url) {
favicon_changed_notifications_page_urls_.insert(
favicon_changed_notifications_page_urls_.end(), page_urls.begin(),
page_urls.end());
if (!icon_url.is_empty())
favicon_changed_notifications_icon_urls_.push_back(icon_url);
}
void NotifyURLVisited(ui::PageTransition transition,
const URLRow& row,
const RedirectList& redirects,
base::Time visit_time) {
// Send the notifications directly to the in-memory database.
mem_backend_->OnURLVisited(nullptr, transition, row, redirects, visit_time);
url_visited_notifications_.push_back(std::make_pair(transition, row));
}
void NotifyURLsModified(const URLRows& changed_urls) {
// Send the notifications directly to the in-memory database.
mem_backend_->OnURLsModified(nullptr, changed_urls);
urls_modified_notifications_.push_back(changed_urls);
}
void NotifyURLsDeleted(bool all_history,
bool expired,
const URLRows& deleted_rows,
const std::set<GURL>& favicon_urls) {
mem_backend_->OnURLsDeleted(nullptr, all_history, expired, deleted_rows,
favicon_urls);
urls_deleted_notifications_.push_back(std::make_pair(all_history, expired));
}
void NotifyKeywordSearchTermUpdated(const URLRow& row,
KeywordID keyword_id,
const base::string16& term) {
mem_backend_->OnKeywordSearchTermUpdated(nullptr, row, keyword_id, term);
}
void NotifyKeywordSearchTermDeleted(URLID url_id) {
mem_backend_->OnKeywordSearchTermDeleted(nullptr, url_id);
}
history::HistoryClientFakeBookmarks history_client_;
scoped_refptr<HistoryBackend> backend_; // Will be NULL on init failure.
std::unique_ptr<InMemoryHistoryBackend> mem_backend_;
bool loaded_;
private:
friend class HistoryBackendTestDelegate;
// testing::Test
void SetUp() override {
if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("BackendTest"),
&test_dir_))
return;
backend_ = new HistoryBackend(new HistoryBackendTestDelegate(this),
history_client_.CreateBackendClient(),
base::ThreadTaskRunnerHandle::Get());
backend_->Init(false, TestHistoryDatabaseParamsForPath(test_dir_));
}
void TearDown() override {
if (backend_.get())
backend_->Closing();
backend_ = NULL;
mem_backend_.reset();
base::DeleteFile(test_dir_, true);
base::RunLoop().RunUntilIdle();
history_client_.ClearAllBookmarks();
}
void SetInMemoryBackend(std::unique_ptr<InMemoryHistoryBackend> backend) {
mem_backend_.swap(backend);
}
// The types and details of notifications which were broadcasted.
std::vector<GURL> favicon_changed_notifications_page_urls_;
std::vector<GURL> favicon_changed_notifications_icon_urls_;
URLVisitedList url_visited_notifications_;
URLsModifiedList urls_modified_notifications_;
URLsDeletedList urls_deleted_notifications_;
base::test::ScopedTaskEnvironment scoped_task_environment_;
base::FilePath test_dir_;
DISALLOW_COPY_AND_ASSIGN(HistoryBackendTestBase);
};
void HistoryBackendTestDelegate::SetInMemoryBackend(
std::unique_ptr<InMemoryHistoryBackend> backend) {
test_->SetInMemoryBackend(std::move(backend));
}
void HistoryBackendTestDelegate::NotifyFaviconsChanged(
const std::set<GURL>& page_urls,
const GURL& icon_url) {
test_->NotifyFaviconsChanged(page_urls, icon_url);
}
void HistoryBackendTestDelegate::NotifyURLVisited(ui::PageTransition transition,
const URLRow& row,
const RedirectList& redirects,
base::Time visit_time) {
test_->NotifyURLVisited(transition, row, redirects, visit_time);
}
void HistoryBackendTestDelegate::NotifyURLsModified(
const URLRows& changed_urls) {
test_->NotifyURLsModified(changed_urls);
}
void HistoryBackendTestDelegate::NotifyURLsDeleted(
bool all_history,
bool expired,
const URLRows& deleted_rows,
const std::set<GURL>& favicon_urls) {
test_->NotifyURLsDeleted(all_history, expired, deleted_rows, favicon_urls);
}
void HistoryBackendTestDelegate::NotifyKeywordSearchTermUpdated(
const URLRow& row,
KeywordID keyword_id,
const base::string16& term) {
test_->NotifyKeywordSearchTermUpdated(row, keyword_id, term);
}
void HistoryBackendTestDelegate::NotifyKeywordSearchTermDeleted(URLID url_id) {
test_->NotifyKeywordSearchTermDeleted(url_id);
}
void HistoryBackendTestDelegate::DBLoaded() {
test_->loaded_ = true;
}
class HistoryBackendTest : public HistoryBackendTestBase {
public:
HistoryBackendTest() {}
~HistoryBackendTest() override {}
protected:
void AddRedirectChain(const char* sequence[], int nav_entry_id) {
AddRedirectChainWithTransitionAndTime(
sequence, nav_entry_id, ui::PAGE_TRANSITION_LINK, base::Time::Now());
}
void AddRedirectChainWithTransitionAndTime(const char* sequence[],
int nav_entry_id,
ui::PageTransition transition,
base::Time time) {
history::RedirectList redirects;
for (int i = 0; sequence[i] != NULL; ++i)
redirects.push_back(GURL(sequence[i]));
ContextID context_id = reinterpret_cast<ContextID>(1);
history::HistoryAddPageArgs request(
redirects.back(), time, context_id, nav_entry_id, GURL(),
redirects, transition, history::SOURCE_BROWSED,
true, true);
backend_->AddPage(request);
}
// Adds CLIENT_REDIRECT page transition.
// |url1| is the source URL and |url2| is the destination.
// |did_replace| is true if the transition is non-user initiated and the
// navigation entry for |url2| has replaced that for |url1|. The possibly
// updated transition code of the visit records for |url1| and |url2| is
// returned by filling in |*transition1| and |*transition2|, respectively.
// |time| is a time of the redirect.
void AddClientRedirect(const GURL& url1,
const GURL& url2,
bool did_replace,
base::Time time,
int* transition1,
int* transition2) {
ContextID dummy_context_id = reinterpret_cast<ContextID>(0x87654321);
history::RedirectList redirects;
if (url1.is_valid())
redirects.push_back(url1);
if (url2.is_valid())
redirects.push_back(url2);
HistoryAddPageArgs request(
url2, time, dummy_context_id, 0, url1,
redirects, ui::PAGE_TRANSITION_CLIENT_REDIRECT,
history::SOURCE_BROWSED, did_replace, true);
backend_->AddPage(request);
*transition1 = GetTransition(url1);
*transition2 = GetTransition(url2);
}
int GetTransition(const GURL& url) {
if (!url.is_valid())
return 0;
URLRow row;
URLID id = backend_->db()->GetRowForURL(url, &row);
VisitVector visits;
EXPECT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
return visits[0].transition;
}
// Returns a vector with the small and large edge sizes.
const std::vector<int> GetEdgeSizesSmallAndLarge() {
std::vector<int> sizes_small_and_large;
sizes_small_and_large.push_back(kSmallEdgeSize);
sizes_small_and_large.push_back(kLargeEdgeSize);
return sizes_small_and_large;
}
// Returns the number of icon mappings of |icon_type| to |page_url|.
size_t NumIconMappingsForPageURL(const GURL& page_url,
favicon_base::IconType icon_type) {
std::vector<IconMapping> icon_mappings;
backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, icon_type,
&icon_mappings);
return icon_mappings.size();
}
// Returns the icon mappings for |page_url| sorted alphabetically by icon
// URL in ascending order. Returns true if there is at least one icon
// mapping.
bool GetSortedIconMappingsForPageURL(
const GURL& page_url,
std::vector<IconMapping>* icon_mappings) {
if (!backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
icon_mappings)) {
return false;
}
std::sort(icon_mappings->begin(), icon_mappings->end(),
[](const history::IconMapping& a, const history::IconMapping& b) {
return a.icon_url < b.icon_url;
});
return true;
}
// Returns the favicon bitmaps for |icon_id| sorted by pixel size in
// ascending order. Returns true if there is at least one favicon bitmap.
bool GetSortedFaviconBitmaps(favicon_base::FaviconID icon_id,
std::vector<FaviconBitmap>* favicon_bitmaps) {
if (!backend_->thumbnail_db_->GetFaviconBitmaps(icon_id, favicon_bitmaps))
return false;
std::sort(
favicon_bitmaps->begin(), favicon_bitmaps->end(),
[](const history::FaviconBitmap& a, const history::FaviconBitmap& b) {
return a.pixel_size.GetArea() < b.pixel_size.GetArea();
});
return true;
}
// Returns true if there is exactly one favicon bitmap associated to
// |favicon_id|. If true, returns favicon bitmap in output parameter.
bool GetOnlyFaviconBitmap(const favicon_base::FaviconID icon_id,
FaviconBitmap* favicon_bitmap) {
std::vector<FaviconBitmap> favicon_bitmaps;
if (!backend_->thumbnail_db_->GetFaviconBitmaps(icon_id, &favicon_bitmaps))
return false;
if (favicon_bitmaps.size() != 1)
return false;
*favicon_bitmap = favicon_bitmaps[0];
return true;
}
// Creates an |edge_size|x|edge_size| bitmap of |color|.
SkBitmap CreateBitmap(SkColor color, int edge_size) {
SkBitmap bitmap;
bitmap.allocN32Pixels(edge_size, edge_size);
bitmap.eraseColor(color);
return bitmap;
}
// Returns true if |bitmap_data| is equal to |expected_data|.
bool BitmapDataEqual(char expected_data,
scoped_refptr<base::RefCountedMemory> bitmap_data) {
return bitmap_data.get() &&
bitmap_data->size() == 1u &&
*bitmap_data->front() == expected_data;
}
// Returns true if |bitmap_data| is of |color|.
bool BitmapColorEqual(SkColor expected_color,
scoped_refptr<base::RefCountedMemory> bitmap_data) {
SkBitmap bitmap;
if (!gfx::PNGCodec::Decode(bitmap_data->front(), bitmap_data->size(),
&bitmap))
return false;
SkAutoLockPixels bitmap_lock(bitmap);
return expected_color == bitmap.getColor(0, 0);
}
private:
DISALLOW_COPY_AND_ASSIGN(HistoryBackendTest);
};
class InMemoryHistoryBackendTest : public HistoryBackendTestBase {
public:
InMemoryHistoryBackendTest() {}
~InMemoryHistoryBackendTest() override {}
protected:
void SimulateNotificationURLsDeleted(const URLRow* row1,
const URLRow* row2 = NULL,
const URLRow* row3 = NULL) {
URLRows rows;
rows.push_back(*row1);
if (row2) rows.push_back(*row2);
if (row3) rows.push_back(*row3);
NotifyURLsDeleted(false, false, rows, std::set<GURL>());
}
size_t GetNumberOfMatchingSearchTerms(const int keyword_id,
const base::string16& prefix) {
std::vector<KeywordSearchTermVisit> matching_terms;
mem_backend_->db()->GetMostRecentKeywordSearchTerms(
keyword_id, prefix, 1, &matching_terms);
return matching_terms.size();
}
static URLRow CreateTestTypedURL() {
URLRow url_row(GURL("https://www.google.com/"));
url_row.set_id(10);
url_row.set_title(base::UTF8ToUTF16("Google Search"));
url_row.set_typed_count(1);
url_row.set_visit_count(1);
url_row.set_last_visit(base::Time::Now() - base::TimeDelta::FromHours(1));
return url_row;
}
static URLRow CreateAnotherTestTypedURL() {
URLRow url_row(GURL("https://maps.google.com/"));
url_row.set_id(20);
url_row.set_title(base::UTF8ToUTF16("Google Maps"));
url_row.set_typed_count(2);
url_row.set_visit_count(3);
url_row.set_last_visit(base::Time::Now() - base::TimeDelta::FromHours(2));
return url_row;
}
static URLRow CreateTestNonTypedURL() {
URLRow url_row(GURL("https://news.google.com/"));
url_row.set_id(30);
url_row.set_title(base::UTF8ToUTF16("Google News"));
url_row.set_visit_count(5);
url_row.set_last_visit(base::Time::Now() - base::TimeDelta::FromHours(3));
return url_row;
}
void PopulateTestURLsAndSearchTerms(URLRow* row1,
URLRow* row2,
const base::string16& term1,
const base::string16& term2);
void TestAddingAndChangingURLRows(
const SimulateNotificationCallback& callback);
static const KeywordID kTestKeywordId;
static const char kTestSearchTerm1[];
static const char kTestSearchTerm2[];
private:
DISALLOW_COPY_AND_ASSIGN(InMemoryHistoryBackendTest);
};
const KeywordID InMemoryHistoryBackendTest::kTestKeywordId = 42;
const char InMemoryHistoryBackendTest::kTestSearchTerm1[] = "banana";
const char InMemoryHistoryBackendTest::kTestSearchTerm2[] = "orange";
// http://crbug.com/114287
#if defined(OS_WIN)
#define MAYBE_Loaded DISABLED_Loaded
#else
#define MAYBE_Loaded Loaded
#endif // defined(OS_WIN)
TEST_F(HistoryBackendTest, MAYBE_Loaded) {
ASSERT_TRUE(backend_.get());
ASSERT_TRUE(loaded_);
}
TEST_F(HistoryBackendTest, DeleteAll) {
ASSERT_TRUE(backend_.get());
// Add two favicons, each with two bitmaps. Note that we add favicon2 before
// adding favicon1. This is so that favicon1 one gets ID 2 autoassigned to
// the database, which will change when the other one is deleted. This way
// we can test that updating works properly.
GURL favicon_url1("http://www.google.com/favicon.ico");
GURL favicon_url2("http://news.google.com/favicon.ico");
favicon_base::FaviconID favicon2 =
backend_->thumbnail_db_->AddFavicon(favicon_url2, favicon_base::FAVICON);
favicon_base::FaviconID favicon1 =
backend_->thumbnail_db_->AddFavicon(favicon_url1, favicon_base::FAVICON);
std::vector<unsigned char> data;
data.push_back('a');
EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(favicon1,
new base::RefCountedBytes(data), base::Time::Now(), kSmallSize));
data[0] = 'b';
EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(favicon1,
new base::RefCountedBytes(data), base::Time::Now(), kLargeSize));
data[0] = 'c';
EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(favicon2,
new base::RefCountedBytes(data), base::Time::Now(), kSmallSize));
data[0] = 'd';
EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(favicon2,
new base::RefCountedBytes(data), base::Time::Now(), kLargeSize));
// First visit two URLs.
URLRow row1(GURL("http://www.google.com/"));
row1.set_visit_count(2);
row1.set_typed_count(1);
row1.set_last_visit(base::Time::Now());
backend_->thumbnail_db_->AddIconMapping(row1.url(), favicon1);
URLRow row2(GURL("http://news.google.com/"));
row2.set_visit_count(1);
row2.set_last_visit(base::Time::Now());
backend_->thumbnail_db_->AddIconMapping(row2.url(), favicon2);
URLRows rows;
rows.push_back(row2); // Reversed order for the same reason as favicons.
rows.push_back(row1);
backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED);
URLID row1_id = backend_->db_->GetRowForURL(row1.url(), NULL);
URLID row2_id = backend_->db_->GetRowForURL(row2.url(), NULL);
// Get the two visits for the URLs we just added.
VisitVector visits;
backend_->db_->GetVisitsForURL(row1_id, &visits);
ASSERT_EQ(1U, visits.size());
visits.clear();
backend_->db_->GetVisitsForURL(row2_id, &visits);
ASSERT_EQ(1U, visits.size());
// The in-memory backend should have been set and it should have gotten the
// typed URL.
ASSERT_TRUE(mem_backend_.get());
URLRow outrow1;
EXPECT_TRUE(mem_backend_->db_->GetRowForURL(row1.url(), NULL));
// Star row1.
history_client_.AddBookmark(row1.url());
// Now finally clear all history.
ClearBroadcastedNotifications();
backend_->DeleteAllHistory();
// The first URL should be preserved but the time should be cleared.
EXPECT_TRUE(backend_->db_->GetRowForURL(row1.url(), &outrow1));
EXPECT_EQ(row1.url(), outrow1.url());
EXPECT_EQ(0, outrow1.visit_count());
EXPECT_EQ(0, outrow1.typed_count());
EXPECT_TRUE(base::Time() == outrow1.last_visit());
// The second row should be deleted.
URLRow outrow2;
EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &outrow2));
// All visits should be deleted for both URLs.
VisitVector all_visits;
backend_->db_->GetAllVisitsInRange(base::Time(), base::Time(), 0,
&all_visits);
ASSERT_EQ(0U, all_visits.size());
// We should have a favicon and favicon bitmaps for the first URL only. We
// look them up by favicon URL since the IDs may have changed.
favicon_base::FaviconID out_favicon1 =
backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
favicon_url1, favicon_base::FAVICON, NULL);
EXPECT_TRUE(out_favicon1);
std::vector<FaviconBitmap> favicon_bitmaps;
EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps(
out_favicon1, &favicon_bitmaps));
ASSERT_EQ(2u, favicon_bitmaps.size());
FaviconBitmap favicon_bitmap1 = favicon_bitmaps[0];
FaviconBitmap favicon_bitmap2 = favicon_bitmaps[1];
// Favicon bitmaps do not need to be in particular order.
if (favicon_bitmap1.pixel_size == kLargeSize) {
FaviconBitmap tmp_favicon_bitmap = favicon_bitmap1;
favicon_bitmap1 = favicon_bitmap2;
favicon_bitmap2 = tmp_favicon_bitmap;
}
EXPECT_TRUE(BitmapDataEqual('a', favicon_bitmap1.bitmap_data));
EXPECT_EQ(kSmallSize, favicon_bitmap1.pixel_size);
EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmap2.bitmap_data));
EXPECT_EQ(kLargeSize, favicon_bitmap2.pixel_size);
favicon_base::FaviconID out_favicon2 =
backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
favicon_url2, favicon_base::FAVICON, NULL);
EXPECT_FALSE(out_favicon2) << "Favicon not deleted";
// The remaining URL should still reference the same favicon, even if its
// ID has changed.
std::vector<IconMapping> mappings;
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
outrow1.url(), favicon_base::FAVICON, &mappings));
EXPECT_EQ(1u, mappings.size());
EXPECT_EQ(out_favicon1, mappings[0].icon_id);
// The first URL should still be bookmarked.
EXPECT_TRUE(history_client_.IsBookmarked(row1.url()));
// Check that we fire the notification about all history having been deleted.
ASSERT_EQ(1u, urls_deleted_notifications().size());
EXPECT_TRUE(urls_deleted_notifications()[0].first);
EXPECT_FALSE(urls_deleted_notifications()[0].second);
}
// Test that clearing all history does not delete bookmark favicons in the
// special case that the bookmark page URL is no longer present in the History
// database's urls table.
TEST_F(HistoryBackendTest, DeleteAllURLPreviouslyDeleted) {
ASSERT_TRUE(backend_.get());
GURL kPageURL("http://www.google.com");
GURL kFaviconURL("http://www.google.com/favicon.ico");
// Setup: Add visit for |kPageURL|.
URLRow row(kPageURL);
row.set_visit_count(2);
row.set_typed_count(1);
row.set_last_visit(base::Time::Now());
backend_->AddPagesWithDetails(std::vector<URLRow>(1u, row),
history::SOURCE_BROWSED);
// Setup: Add favicon for |kPageURL|.
std::vector<unsigned char> data;
data.push_back('a');
favicon_base::FaviconID favicon = backend_->thumbnail_db_->AddFavicon(
kFaviconURL, favicon_base::FAVICON, new base::RefCountedBytes(data),
base::Time::Now(), kSmallSize);
backend_->thumbnail_db_->AddIconMapping(row.url(), favicon);
history_client_.AddBookmark(kPageURL);
// Test initial state.
URLID row_id = backend_->db_->GetRowForURL(kPageURL, NULL);
ASSERT_NE(0, row_id);
VisitVector visits;
backend_->db_->GetVisitsForURL(row_id, &visits);
ASSERT_EQ(1U, visits.size());
std::vector<IconMapping> icon_mappings;
ASSERT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
kPageURL, favicon_base::FAVICON, &icon_mappings));
ASSERT_EQ(1u, icon_mappings.size());
// Delete information for |kPageURL|, then clear all browsing data.
backend_->DeleteURL(kPageURL);
backend_->DeleteAllHistory();
// Test that the entry in the url table for the bookmark is gone but that the
// favicon data for the bookmark is still there.
ASSERT_EQ(0, backend_->db_->GetRowForURL(kPageURL, NULL));
icon_mappings.clear();
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
kPageURL, favicon_base::FAVICON, &icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
}
// Checks that adding a visit, then calling DeleteAll, and then trying to add
// data for the visited page works. This can happen when clearing the history
// immediately after visiting a page.
TEST_F(HistoryBackendTest, DeleteAllThenAddData) {
ASSERT_TRUE(backend_.get());
base::Time visit_time = base::Time::Now();
GURL url("http://www.google.com/");
HistoryAddPageArgs request(url, visit_time, NULL, 0, GURL(),
history::RedirectList(),
ui::PAGE_TRANSITION_KEYWORD_GENERATED,
history::SOURCE_BROWSED, false, true);
backend_->AddPage(request);
// Check that a row was added.
URLRow outrow;
EXPECT_TRUE(backend_->db_->GetRowForURL(url, &outrow));
// Check that the visit was added.
VisitVector all_visits;
backend_->db_->GetAllVisitsInRange(base::Time(), base::Time(), 0,
&all_visits);
ASSERT_EQ(1U, all_visits.size());
// Clear all history.
backend_->DeleteAllHistory();
// The row should be deleted.
EXPECT_FALSE(backend_->db_->GetRowForURL(url, &outrow));
// The visit should be deleted.
backend_->db_->GetAllVisitsInRange(base::Time(), base::Time(), 0,
&all_visits);
ASSERT_EQ(0U, all_visits.size());
// Try and set the title.
backend_->SetPageTitle(url, base::UTF8ToUTF16("Title"));
// The row should still be deleted.
EXPECT_FALSE(backend_->db_->GetRowForURL(url, &outrow));
// The visit should still be deleted.
backend_->db_->GetAllVisitsInRange(base::Time(), base::Time(), 0,
&all_visits);
ASSERT_EQ(0U, all_visits.size());
}
TEST_F(HistoryBackendTest, URLsNoLongerBookmarked) {
GURL favicon_url1("http://www.google.com/favicon.ico");
GURL favicon_url2("http://news.google.com/favicon.ico");
std::vector<unsigned char> data;
data.push_back('1');
favicon_base::FaviconID favicon1 =
backend_->thumbnail_db_->AddFavicon(favicon_url1,
favicon_base::FAVICON,
new base::RefCountedBytes(data),
base::Time::Now(),
gfx::Size());
data[0] = '2';
favicon_base::FaviconID favicon2 =
backend_->thumbnail_db_->AddFavicon(favicon_url2,
favicon_base::FAVICON,
new base::RefCountedBytes(data),
base::Time::Now(),
gfx::Size());
// First visit two URLs.
URLRow row1(GURL("http://www.google.com/"));
row1.set_visit_count(2);
row1.set_typed_count(1);
row1.set_last_visit(base::Time::Now());
EXPECT_TRUE(backend_->thumbnail_db_->AddIconMapping(row1.url(), favicon1));
URLRow row2(GURL("http://news.google.com/"));
row2.set_visit_count(1);
row2.set_last_visit(base::Time::Now());
EXPECT_TRUE(backend_->thumbnail_db_->AddIconMapping(row2.url(), favicon2));
URLRows rows;
rows.push_back(row2); // Reversed order for the same reason as favicons.
rows.push_back(row1);
backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED);
URLID row1_id = backend_->db_->GetRowForURL(row1.url(), NULL);
URLID row2_id = backend_->db_->GetRowForURL(row2.url(), NULL);
// Star the two URLs.
history_client_.AddBookmark(row1.url());
history_client_.AddBookmark(row2.url());
// Delete url 2.
backend_->expirer_.DeleteURL(row2.url());
EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), NULL));
VisitVector visits;
backend_->db_->GetVisitsForURL(row2_id, &visits);
EXPECT_EQ(0U, visits.size());
// The favicon should still be valid.
EXPECT_EQ(favicon2,
backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
favicon_url2, favicon_base::FAVICON, NULL));
// Unstar row2.
history_client_.DelBookmark(row2.url());
// Tell the backend it was unstarred. We have to explicitly do this as
// BookmarkModel isn't wired up to the backend during testing.
std::set<GURL> unstarred_urls;
unstarred_urls.insert(row2.url());
backend_->URLsNoLongerBookmarked(unstarred_urls);
// The URL should still not exist.
EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), NULL));
// And the favicon should be deleted.
EXPECT_EQ(0,
backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
favicon_url2, favicon_base::FAVICON, NULL));
// Unstar row 1.
history_client_.DelBookmark(row1.url());
// Tell the backend it was unstarred. We have to explicitly do this as
// BookmarkModel isn't wired up to the backend during testing.
unstarred_urls.clear();
unstarred_urls.insert(row1.url());
backend_->URLsNoLongerBookmarked(unstarred_urls);
// The URL should still exist (because there were visits).
EXPECT_EQ(row1_id, backend_->db_->GetRowForURL(row1.url(), NULL));
// There should still be visits.
visits.clear();
backend_->db_->GetVisitsForURL(row1_id, &visits);
EXPECT_EQ(1U, visits.size());
// The favicon should still be valid.
EXPECT_EQ(favicon1,
backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
favicon_url1, favicon_base::FAVICON, NULL));
}
// Tests a handful of assertions for a navigation with a type of
// KEYWORD_GENERATED.
TEST_F(HistoryBackendTest, KeywordGenerated) {
ASSERT_TRUE(backend_.get());
GURL url("http://google.com");
base::Time visit_time = base::Time::Now() - base::TimeDelta::FromDays(1);
HistoryAddPageArgs request(url, visit_time, NULL, 0, GURL(),
history::RedirectList(),
ui::PAGE_TRANSITION_KEYWORD_GENERATED,
history::SOURCE_BROWSED, false, true);
backend_->AddPage(request);
// A row should have been added for the url.
URLRow row;
URLID url_id = backend_->db()->GetRowForURL(url, &row);
ASSERT_NE(0, url_id);
// The typed count should be 1.
ASSERT_EQ(1, row.typed_count());
// KEYWORD_GENERATED urls should not be added to the segment db.
std::string segment_name = VisitSegmentDatabase::ComputeSegmentName(url);
EXPECT_EQ(0, backend_->db()->GetSegmentNamed(segment_name));
// One visit should be added.
VisitVector visits;
EXPECT_TRUE(backend_->db()->GetVisitsForURL(url_id, &visits));
EXPECT_EQ(1U, visits.size());
// But no visible visits.
visits.clear();
QueryOptions query_options;
query_options.max_count = 1;
backend_->db()->GetVisibleVisitsInRange(query_options, &visits);
EXPECT_TRUE(visits.empty());
// Going back to the same entry should not increment the typed count.
ui::PageTransition back_transition = ui::PageTransitionFromInt(
ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FORWARD_BACK);
HistoryAddPageArgs back_request(url, visit_time, NULL, 0, GURL(),
history::RedirectList(), back_transition,
history::SOURCE_BROWSED, false, true);
backend_->AddPage(back_request);
url_id = backend_->db()->GetRowForURL(url, &row);
ASSERT_NE(0, url_id);
ASSERT_EQ(1, row.typed_count());
// Expire the visits.
std::set<GURL> restrict_urls;
backend_->expire_backend()->ExpireHistoryBetween(restrict_urls, visit_time,
base::Time::Now());
// The visit should have been nuked.
visits.clear();
EXPECT_TRUE(backend_->db()->GetVisitsForURL(url_id, &visits));
EXPECT_TRUE(visits.empty());
// As well as the url.
ASSERT_EQ(0, backend_->db()->GetRowForURL(url, &row));
}
TEST_F(HistoryBackendTest, ClientRedirect) {
ASSERT_TRUE(backend_.get());
int transition1;
int transition2;
// Initial transition to page A.
GURL url_a("http://google.com/a");
AddClientRedirect(GURL(), url_a, false, base::Time(),
&transition1, &transition2);
EXPECT_TRUE(transition2 & ui::PAGE_TRANSITION_CHAIN_END);
// User initiated redirect to page B.
GURL url_b("http://google.com/b");
AddClientRedirect(url_a, url_b, false, base::Time(),
&transition1, &transition2);
EXPECT_TRUE(transition1 & ui::PAGE_TRANSITION_CHAIN_END);
EXPECT_TRUE(transition2 & ui::PAGE_TRANSITION_CHAIN_END);
// Non-user initiated redirect to page C.
GURL url_c("http://google.com/c");
AddClientRedirect(url_b, url_c, true, base::Time(),
&transition1, &transition2);
EXPECT_FALSE(transition1 & ui::PAGE_TRANSITION_CHAIN_END);
EXPECT_TRUE(transition2 & ui::PAGE_TRANSITION_CHAIN_END);
}
TEST_F(HistoryBackendTest, AddPagesWithDetails) {
ASSERT_TRUE(backend_.get());
// Import one non-typed URL, and two recent and one expired typed URLs.
URLRow row1(GURL("https://news.google.com/"));
row1.set_visit_count(1);
row1.set_last_visit(base::Time::Now());
URLRow row2(GURL("https://www.google.com/"));
row2.set_typed_count(1);
row2.set_last_visit(base::Time::Now());
URLRow row3(GURL("https://mail.google.com/"));
row3.set_visit_count(1);
row3.set_typed_count(1);
row3.set_last_visit(base::Time::Now() - base::TimeDelta::FromDays(7 - 1));
URLRow row4(GURL("https://maps.google.com/"));
row4.set_visit_count(1);
row4.set_typed_count(1);
row4.set_last_visit(base::Time::Now() - base::TimeDelta::FromDays(365 + 2));
URLRows rows;
rows.push_back(row1);
rows.push_back(row2);
rows.push_back(row3);
rows.push_back(row4);
backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED);
// Verify that recent URLs have ended up in the main |db_|, while the already
// expired URL has been ignored.
URLRow stored_row1, stored_row2, stored_row3, stored_row4;
EXPECT_NE(0, backend_->db_->GetRowForURL(row1.url(), &stored_row1));
EXPECT_NE(0, backend_->db_->GetRowForURL(row2.url(), &stored_row2));
EXPECT_NE(0, backend_->db_->GetRowForURL(row3.url(), &stored_row3));
EXPECT_EQ(0, backend_->db_->GetRowForURL(row4.url(), &stored_row4));
// Ensure that a notification was fired for both typed and non-typed URLs.
// Further verify that the IDs in the notification are set to those that are
// in effect in the main database. The InMemoryHistoryBackend relies on this
// for caching.
ASSERT_EQ(1, num_urls_modified_notifications());
const URLRows& changed_urls = urls_modified_notifications()[0];
EXPECT_EQ(3u, changed_urls.size());
URLRows::const_iterator it_row1 =
std::find_if(changed_urls.begin(), changed_urls.end(),
history::URLRow::URLRowHasURL(row1.url()));
ASSERT_NE(changed_urls.end(), it_row1);
EXPECT_EQ(stored_row1.id(), it_row1->id());
URLRows::const_iterator it_row2 =
std::find_if(changed_urls.begin(), changed_urls.end(),
history::URLRow::URLRowHasURL(row2.url()));
ASSERT_NE(changed_urls.end(), it_row2);
EXPECT_EQ(stored_row2.id(), it_row2->id());
URLRows::const_iterator it_row3 =
std::find_if(changed_urls.begin(), changed_urls.end(),
history::URLRow::URLRowHasURL(row3.url()));
ASSERT_NE(changed_urls.end(), it_row3);
EXPECT_EQ(stored_row3.id(), it_row3->id());
}
TEST_F(HistoryBackendTest, UpdateURLs) {
ASSERT_TRUE(backend_.get());
// Add three pages directly to the database.
URLRow row1(GURL("https://news.google.com/"));
row1.set_visit_count(1);
row1.set_last_visit(base::Time::Now());
URLRow row2(GURL("https://maps.google.com/"));
row2.set_visit_count(2);
row2.set_last_visit(base::Time::Now());
URLRow row3(GURL("https://www.google.com/"));
row3.set_visit_count(3);
row3.set_last_visit(base::Time::Now());
backend_->db_->AddURL(row1);
backend_->db_->AddURL(row2);
backend_->db_->AddURL(row3);
// Now create changed versions of all URLRows by incrementing their visit
// counts, and in the meantime, also delete the second row from the database.
URLRow altered_row1, altered_row2, altered_row3;
backend_->db_->GetRowForURL(row1.url(), &altered_row1);
altered_row1.set_visit_count(42);
backend_->db_->GetRowForURL(row2.url(), &altered_row2);
altered_row2.set_visit_count(43);
backend_->db_->GetRowForURL(row3.url(), &altered_row3);
altered_row3.set_visit_count(44);
backend_->db_->DeleteURLRow(altered_row2.id());
// Now try to update all three rows at once. The change to the second URLRow
// should be ignored, as it is no longer present in the DB.
URLRows rows;
rows.push_back(altered_row1);
rows.push_back(altered_row2);
rows.push_back(altered_row3);
EXPECT_EQ(2u, backend_->UpdateURLs(rows));
URLRow stored_row1, stored_row3;
EXPECT_NE(0, backend_->db_->GetRowForURL(row1.url(), &stored_row1));
EXPECT_NE(0, backend_->db_->GetRowForURL(row3.url(), &stored_row3));
EXPECT_EQ(altered_row1.visit_count(), stored_row1.visit_count());
EXPECT_EQ(altered_row3.visit_count(), stored_row3.visit_count());
// Ensure that a notification was fired, and further verify that the IDs in
// the notification are set to those that are in effect in the main database.
// The InMemoryHistoryBackend relies on this for caching.
ASSERT_EQ(1, num_urls_modified_notifications());
const URLRows& changed_urls = urls_modified_notifications()[0];
EXPECT_EQ(2u, changed_urls.size());
URLRows::const_iterator it_row1 =
std::find_if(changed_urls.begin(), changed_urls.end(),
history::URLRow::URLRowHasURL(row1.url()));
ASSERT_NE(changed_urls.end(), it_row1);
EXPECT_EQ(altered_row1.id(), it_row1->id());
EXPECT_EQ(altered_row1.visit_count(), it_row1->visit_count());
URLRows::const_iterator it_row3 =
std::find_if(changed_urls.begin(), changed_urls.end(),
history::URLRow::URLRowHasURL(row3.url()));
ASSERT_NE(changed_urls.end(), it_row3);
EXPECT_EQ(altered_row3.id(), it_row3->id());
EXPECT_EQ(altered_row3.visit_count(), it_row3->visit_count());
}
// This verifies that a notification is fired. In-depth testing of logic should
// be done in HistoryTest.SetTitle.
TEST_F(HistoryBackendTest, SetPageTitleFiresNotificationWithCorrectDetails) {
const char kTestUrlTitle[] = "Google Search";
ASSERT_TRUE(backend_.get());
// Add two pages, then change the title of the second one.
URLRow row1(GURL("https://news.google.com/"));
row1.set_typed_count(1);
row1.set_last_visit(base::Time::Now());
URLRow row2(GURL("https://www.google.com/"));
row2.set_visit_count(2);
row2.set_last_visit(base::Time::Now());
URLRows rows;
rows.push_back(row1);
rows.push_back(row2);
backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED);
ClearBroadcastedNotifications();
backend_->SetPageTitle(row2.url(), base::UTF8ToUTF16(kTestUrlTitle));
// Ensure that a notification was fired, and further verify that the IDs in
// the notification are set to those that are in effect in the main database.
// The InMemoryHistoryBackend relies on this for caching.
URLRow stored_row2;
EXPECT_TRUE(backend_->GetURL(row2.url(), &stored_row2));
ASSERT_EQ(1, num_urls_modified_notifications());
const URLRows& changed_urls = urls_modified_notifications()[0];
ASSERT_EQ(1u, changed_urls.size());
EXPECT_EQ(base::UTF8ToUTF16(kTestUrlTitle), changed_urls[0].title());
EXPECT_EQ(stored_row2.id(), changed_urls[0].id());
}
// There's no importer on Android.
#if !defined(OS_ANDROID)
TEST_F(HistoryBackendTest, ImportedFaviconsTest) {
// Setup test data - two Urls in the history, one with favicon assigned and
// one without.
GURL favicon_url1("http://www.google.com/favicon.ico");
std::vector<unsigned char> data;
data.push_back('1');
favicon_base::FaviconID favicon1 = backend_->thumbnail_db_->AddFavicon(
favicon_url1,
favicon_base::FAVICON,
base::RefCountedBytes::TakeVector(&data),
base::Time::Now(),
gfx::Size());
URLRow row1(GURL("http://www.google.com/"));
row1.set_visit_count(1);
row1.set_last_visit(base::Time::Now());
EXPECT_TRUE(backend_->thumbnail_db_->AddIconMapping(row1.url(), favicon1));
URLRow row2(GURL("http://news.google.com/"));
row2.set_visit_count(1);
row2.set_last_visit(base::Time::Now());
URLRows rows;
rows.push_back(row1);
rows.push_back(row2);
backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED);
URLRow url_row1, url_row2;
EXPECT_FALSE(backend_->db_->GetRowForURL(row1.url(), &url_row1) == 0);
EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &url_row2) == 0);
EXPECT_EQ(1u, NumIconMappingsForPageURL(row1.url(), favicon_base::FAVICON));
EXPECT_EQ(0u, NumIconMappingsForPageURL(row2.url(), favicon_base::FAVICON));
// Now provide one imported favicon for both URLs already in the registry.
// The new favicon should only be used with the URL that doesn't already have
// a favicon.
favicon_base::FaviconUsageDataList favicons;
favicon_base::FaviconUsageData favicon;
favicon.favicon_url = GURL("http://news.google.com/favicon.ico");
favicon.png_data.push_back('2');
favicon.urls.insert(row1.url());
favicon.urls.insert(row2.url());
favicons.push_back(favicon);
backend_->SetImportedFavicons(favicons);
EXPECT_FALSE(backend_->db_->GetRowForURL(row1.url(), &url_row1) == 0);
EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &url_row2) == 0);
std::vector<IconMapping> mappings;
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
row1.url(), favicon_base::FAVICON, &mappings));
EXPECT_EQ(1u, mappings.size());
EXPECT_EQ(favicon1, mappings[0].icon_id);
EXPECT_EQ(favicon_url1, mappings[0].icon_url);
mappings.clear();
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
row2.url(), favicon_base::FAVICON, &mappings));
EXPECT_EQ(1u, mappings.size());
EXPECT_EQ(favicon.favicon_url, mappings[0].icon_url);
// A URL should not be added to history (to store favicon), if
// the URL is not bookmarked.
GURL url3("http://mail.google.com");
favicons.clear();
favicon.favicon_url = GURL("http://mail.google.com/favicon.ico");
favicon.png_data.push_back('3');
favicon.urls.insert(url3);
favicons.push_back(favicon);
backend_->SetImportedFavicons(favicons);
URLRow url_row3;
EXPECT_TRUE(backend_->db_->GetRowForURL(url3, &url_row3) == 0);
// If the URL is bookmarked, it should get added to history with 0 visits.
history_client_.AddBookmark(url3);
backend_->SetImportedFavicons(favicons);
EXPECT_FALSE(backend_->db_->GetRowForURL(url3, &url_row3) == 0);
EXPECT_TRUE(url_row3.visit_count() == 0);
}
#endif // !defined(OS_ANDROID)
TEST_F(HistoryBackendTest, StripUsernamePasswordTest) {
ASSERT_TRUE(backend_.get());
GURL url("http://anyuser:anypass@www.google.com");
GURL stripped_url("http://www.google.com");
// Clear all history.
backend_->DeleteAllHistory();
// Visit the url with username, password.
backend_->AddPageVisit(url, base::Time::Now(), 0,
ui::PageTransitionFromInt(
ui::PageTransitionGetQualifier(ui::PAGE_TRANSITION_TYPED)),
history::SOURCE_BROWSED);
// Fetch the row information about stripped url from history db.
VisitVector visits;
URLID row_id = backend_->db_->GetRowForURL(stripped_url, NULL);
backend_->db_->GetVisitsForURL(row_id, &visits);
// Check if stripped url is stored in database.
ASSERT_EQ(1U, visits.size());
}
TEST_F(HistoryBackendTest, AddPageVisitBackForward) {
ASSERT_TRUE(backend_.get());
GURL url("http://www.google.com");
// Clear all history.
backend_->DeleteAllHistory();
// Visit the url after typing it.
backend_->AddPageVisit(url, base::Time::Now(), 0,
ui::PAGE_TRANSITION_TYPED,
history::SOURCE_BROWSED);
// Ensure both the typed count and visit count are 1.
VisitVector visits;
URLRow row;
URLID id = backend_->db()->GetRowForURL(url, &row);
ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
EXPECT_EQ(1, row.typed_count());
EXPECT_EQ(1, row.visit_count());
// Visit the url again via back/forward.
backend_->AddPageVisit(url, base::Time::Now(), 0,
ui::PageTransitionFromInt(
ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FORWARD_BACK),
history::SOURCE_BROWSED);
// Ensure the typed count is still 1 but the visit count is 2.
id = backend_->db()->GetRowForURL(url, &row);
ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
EXPECT_EQ(1, row.typed_count());
EXPECT_EQ(2, row.visit_count());
}
TEST_F(HistoryBackendTest, AddPageVisitRedirectBackForward) {
ASSERT_TRUE(backend_.get());
GURL url1("http://www.google.com");
GURL url2("http://www.chromium.org");
// Clear all history.
backend_->DeleteAllHistory();
// Visit a typed URL with a redirect.
backend_->AddPageVisit(url1, base::Time::Now(), 0,
ui::PAGE_TRANSITION_TYPED,
history::SOURCE_BROWSED);
backend_->AddPageVisit(url2, base::Time::Now(), 0,
ui::PageTransitionFromInt(
ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_CLIENT_REDIRECT),
history::SOURCE_BROWSED);
// Ensure the redirected URL does not count as typed.
VisitVector visits;
URLRow row;
URLID id = backend_->db()->GetRowForURL(url2, &row);
ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
EXPECT_EQ(0, row.typed_count());
EXPECT_EQ(1, row.visit_count());
// Visit the redirected url again via back/forward.
backend_->AddPageVisit(url2, base::Time::Now(), 0,
ui::PageTransitionFromInt(
ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FORWARD_BACK |
ui::PAGE_TRANSITION_CLIENT_REDIRECT),
history::SOURCE_BROWSED);
// Ensure the typed count is still 1 but the visit count is 2.
id = backend_->db()->GetRowForURL(url2, &row);
ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
EXPECT_EQ(0, row.typed_count());
EXPECT_EQ(2, row.visit_count());
}
TEST_F(HistoryBackendTest, AddPageVisitSource) {
ASSERT_TRUE(backend_.get());
GURL url("http://www.google.com");
// Clear all history.
backend_->DeleteAllHistory();
// Assume visiting the url from an externsion.
backend_->AddPageVisit(
url, base::Time::Now(), 0, ui::PAGE_TRANSITION_TYPED,
history::SOURCE_EXTENSION);
// Assume the url is imported from Firefox.
backend_->AddPageVisit(url, base::Time::Now(), 0,
ui::PAGE_TRANSITION_TYPED,
history::SOURCE_FIREFOX_IMPORTED);
// Assume this url is also synced.
backend_->AddPageVisit(url, base::Time::Now(), 0,
ui::PAGE_TRANSITION_TYPED,
history::SOURCE_SYNCED);
// Fetch the row information about the url from history db.
VisitVector visits;
URLID row_id = backend_->db_->GetRowForURL(url, NULL);
backend_->db_->GetVisitsForURL(row_id, &visits);
// Check if all the visits to the url are stored in database.
ASSERT_EQ(3U, visits.size());
VisitSourceMap visit_sources;
ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources));
ASSERT_EQ(3U, visit_sources.size());
int sources = 0;
for (int i = 0; i < 3; i++) {
switch (visit_sources[visits[i].visit_id]) {
case history::SOURCE_EXTENSION:
sources |= 0x1;
break;
case history::SOURCE_FIREFOX_IMPORTED:
sources |= 0x2;
break;
case history::SOURCE_SYNCED:
sources |= 0x4;
default:
break;
}
}
EXPECT_EQ(0x7, sources);
}
TEST_F(HistoryBackendTest, AddPageVisitNotLastVisit) {
ASSERT_TRUE(backend_.get());
GURL url("http://www.google.com");
// Clear all history.
backend_->DeleteAllHistory();
// Create visit times
base::Time recent_time = base::Time::Now();
base::TimeDelta visit_age = base::TimeDelta::FromDays(3);
base::Time older_time = recent_time - visit_age;
// Visit the url with recent time.
backend_->AddPageVisit(url, recent_time, 0,
ui::PageTransitionFromInt(
ui::PageTransitionGetQualifier(ui::PAGE_TRANSITION_TYPED)),
history::SOURCE_BROWSED);
// Add to the url a visit with older time (could be syncing from another
// client, etc.).
backend_->AddPageVisit(url, older_time, 0,
ui::PageTransitionFromInt(
ui::PageTransitionGetQualifier(ui::PAGE_TRANSITION_TYPED)),
history::SOURCE_SYNCED);
// Fetch the row information about url from history db.
VisitVector visits;
URLRow row;
URLID row_id = backend_->db_->GetRowForURL(url, &row);
backend_->db_->GetVisitsForURL(row_id, &visits);
// Last visit time should be the most recent time, not the most recently added
// visit.
ASSERT_EQ(2U, visits.size());
ASSERT_EQ(recent_time, row.last_visit());
}
TEST_F(HistoryBackendTest, AddPageVisitFiresNotificationWithCorrectDetails) {
ASSERT_TRUE(backend_.get());
GURL url1("http://www.google.com");
GURL url2("http://maps.google.com");
// Clear all history.
backend_->DeleteAllHistory();
ClearBroadcastedNotifications();
// Visit two distinct URLs, the second one twice.
backend_->AddPageVisit(url1, base::Time::Now(), 0,
ui::PAGE_TRANSITION_LINK,
history::SOURCE_BROWSED);
for (int i = 0; i < 2; ++i) {
backend_->AddPageVisit(url2, base::Time::Now(), 0,
ui::PAGE_TRANSITION_TYPED,
history::SOURCE_BROWSED);
}
URLRow stored_row1, stored_row2;
EXPECT_NE(0, backend_->db_->GetRowForURL(url1, &stored_row1));
EXPECT_NE(0, backend_->db_->GetRowForURL(url2, &stored_row2));
// Expect that HistoryServiceObserver::OnURLVisited has been called 3 times,
// and that each time the URLRows have the correct URLs and IDs set.
ASSERT_EQ(3, num_url_visited_notifications());
EXPECT_TRUE(ui::PageTransitionCoreTypeIs(url_visited_notifications()[0].first,
ui::PAGE_TRANSITION_LINK));
EXPECT_EQ(stored_row1.id(), url_visited_notifications()[0].second.id());
EXPECT_EQ(stored_row1.url(), url_visited_notifications()[0].second.url());
EXPECT_TRUE(ui::PageTransitionCoreTypeIs(url_visited_notifications()[1].first,
ui::PAGE_TRANSITION_TYPED));
EXPECT_EQ(stored_row2.id(), url_visited_notifications()[1].second.id());
EXPECT_EQ(stored_row2.url(), url_visited_notifications()[1].second.url());
EXPECT_TRUE(ui::PageTransitionCoreTypeIs(url_visited_notifications()[2].first,
ui::PAGE_TRANSITION_TYPED));
EXPECT_EQ(stored_row2.id(), url_visited_notifications()[2].second.id());
EXPECT_EQ(stored_row2.url(), url_visited_notifications()[2].second.url());
}
TEST_F(HistoryBackendTest, AddPageArgsSource) {
ASSERT_TRUE(backend_.get());
GURL url("http://testpageargs.com");
// Assume this page is browsed by user.
HistoryAddPageArgs request1(url, base::Time::Now(), NULL, 0, GURL(),
history::RedirectList(),
ui::PAGE_TRANSITION_KEYWORD_GENERATED,
history::SOURCE_BROWSED, false, true);
backend_->AddPage(request1);
// Assume this page is synced.
HistoryAddPageArgs request2(url, base::Time::Now(), NULL, 0, GURL(),
history::RedirectList(),
ui::PAGE_TRANSITION_LINK,
history::SOURCE_SYNCED, false, true);
backend_->AddPage(request2);
// Assume this page is browsed again.
HistoryAddPageArgs request3(url, base::Time::Now(), NULL, 0, GURL(),
history::RedirectList(),
ui::PAGE_TRANSITION_TYPED,
history::SOURCE_BROWSED, false, true);
backend_->AddPage(request3);
// Three visits should be added with proper sources.
VisitVector visits;
URLRow row;
URLID id = backend_->db()->GetRowForURL(url, &row);
ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
ASSERT_EQ(3U, visits.size());
VisitSourceMap visit_sources;
ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources));
ASSERT_EQ(1U, visit_sources.size());
EXPECT_EQ(history::SOURCE_SYNCED, visit_sources.begin()->second);
}
TEST_F(HistoryBackendTest, AddVisitsSource) {
ASSERT_TRUE(backend_.get());
GURL url1("http://www.cnn.com");
std::vector<VisitInfo> visits1, visits2;
visits1.push_back(VisitInfo(
base::Time::Now() - base::TimeDelta::FromDays(5),
ui::PAGE_TRANSITION_LINK));
visits1.push_back(VisitInfo(
base::Time::Now() - base::TimeDelta::FromDays(1),
ui::PAGE_TRANSITION_LINK));
visits1.push_back(VisitInfo(
base::Time::Now(), ui::PAGE_TRANSITION_LINK));
GURL url2("http://www.example.com");
visits2.push_back(VisitInfo(
base::Time::Now() - base::TimeDelta::FromDays(10),
ui::PAGE_TRANSITION_LINK));
visits2.push_back(VisitInfo(base::Time::Now(), ui::PAGE_TRANSITION_LINK));
// Clear all history.
backend_->DeleteAllHistory();
// Add the visits.
backend_->AddVisits(url1, visits1, history::SOURCE_IE_IMPORTED);
backend_->AddVisits(url2, visits2, history::SOURCE_SYNCED);
// Verify the visits were added with their sources.
VisitVector visits;
URLRow row;
URLID id = backend_->db()->GetRowForURL(url1, &row);
ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
ASSERT_EQ(3U, visits.size());
VisitSourceMap visit_sources;
ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources));
ASSERT_EQ(3U, visit_sources.size());
for (int i = 0; i < 3; i++)
EXPECT_EQ(history::SOURCE_IE_IMPORTED, visit_sources[visits[i].visit_id]);
id = backend_->db()->GetRowForURL(url2, &row);
ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
ASSERT_EQ(2U, visits.size());
ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources));
ASSERT_EQ(2U, visit_sources.size());
for (int i = 0; i < 2; i++)
EXPECT_EQ(history::SOURCE_SYNCED, visit_sources[visits[i].visit_id]);
}
TEST_F(HistoryBackendTest, GetMostRecentVisits) {
ASSERT_TRUE(backend_.get());
GURL url1("http://www.cnn.com");
std::vector<VisitInfo> visits1;
visits1.push_back(VisitInfo(
base::Time::Now() - base::TimeDelta::FromDays(5),
ui::PAGE_TRANSITION_LINK));
visits1.push_back(VisitInfo(
base::Time::Now() - base::TimeDelta::FromDays(1),
ui::PAGE_TRANSITION_LINK));
visits1.push_back(VisitInfo(
base::Time::Now(), ui::PAGE_TRANSITION_LINK));
// Clear all history.
backend_->DeleteAllHistory();
// Add the visits.
backend_->AddVisits(url1, visits1, history::SOURCE_IE_IMPORTED);
// Verify the visits were added with their sources.
VisitVector visits;
URLRow row;
URLID id = backend_->db()->GetRowForURL(url1, &row);
ASSERT_TRUE(backend_->db()->GetMostRecentVisitsForURL(id, 1, &visits));
ASSERT_EQ(1U, visits.size());
EXPECT_EQ(visits1[2].first, visits[0].visit_time);
}
TEST_F(HistoryBackendTest, RemoveVisitsTransitions) {
ASSERT_TRUE(backend_.get());
// Clear all history.
backend_->DeleteAllHistory();
GURL url1("http://www.cnn.com");
VisitInfo typed_visit(
base::Time::Now() - base::TimeDelta::FromDays(6),
ui::PAGE_TRANSITION_TYPED);
VisitInfo reload_visit(
base::Time::Now() - base::TimeDelta::FromDays(5),
ui::PAGE_TRANSITION_RELOAD);
VisitInfo link_visit(
base::Time::Now() - base::TimeDelta::FromDays(4),
ui::PAGE_TRANSITION_LINK);
std::vector<VisitInfo> visits_to_add;
visits_to_add.push_back(typed_visit);
visits_to_add.push_back(reload_visit);
visits_to_add.push_back(link_visit);
// Add the visits.
backend_->AddVisits(url1, visits_to_add, history::SOURCE_SYNCED);
// Verify that the various counts are what we expect.
VisitVector visits;
URLRow row;
URLID id = backend_->db()->GetRowForURL(url1, &row);
ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
ASSERT_EQ(3U, visits.size());
ASSERT_EQ(1, row.typed_count());
ASSERT_EQ(2, row.visit_count());
// Now, delete the typed visit and verify that typed_count is updated.
ASSERT_TRUE(backend_->RemoveVisits(VisitVector(1, visits[0])));
id = backend_->db()->GetRowForURL(url1, &row);
ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
ASSERT_EQ(2U, visits.size());
ASSERT_EQ(0, row.typed_count());
ASSERT_EQ(1, row.visit_count());
// Delete the reload visit now and verify that none of the counts have
// changed.
ASSERT_TRUE(backend_->RemoveVisits(VisitVector(1, visits[0])));
id = backend_->db()->GetRowForURL(url1, &row);
ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
ASSERT_EQ(1U, visits.size());
ASSERT_EQ(0, row.typed_count());
ASSERT_EQ(1, row.visit_count());
// Delete the last visit and verify that we delete the URL.
ASSERT_TRUE(backend_->RemoveVisits(VisitVector(1, visits[0])));
ASSERT_EQ(0, backend_->db()->GetRowForURL(url1, &row));
}
TEST_F(HistoryBackendTest, RemoveVisitsSource) {
ASSERT_TRUE(backend_.get());
GURL url1("http://www.cnn.com");
std::vector<VisitInfo> visits1, visits2;
visits1.push_back(VisitInfo(
base::Time::Now() - base::TimeDelta::FromDays(5),
ui::PAGE_TRANSITION_LINK));
visits1.push_back(VisitInfo(base::Time::Now(),
ui::PAGE_TRANSITION_LINK));
GURL url2("http://www.example.com");
visits2.push_back(VisitInfo(
base::Time::Now() - base::TimeDelta::FromDays(10),
ui::PAGE_TRANSITION_LINK));
visits2.push_back(VisitInfo(base::Time::Now(), ui::PAGE_TRANSITION_LINK));
// Clear all history.
backend_->DeleteAllHistory();
// Add the visits.
backend_->AddVisits(url1, visits1, history::SOURCE_IE_IMPORTED);
backend_->AddVisits(url2, visits2, history::SOURCE_SYNCED);
// Verify the visits of url1 were added.
VisitVector visits;
URLRow row;
URLID id = backend_->db()->GetRowForURL(url1, &row);
ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
ASSERT_EQ(2U, visits.size());
// Remove these visits.
ASSERT_TRUE(backend_->RemoveVisits(visits));
// Now check only url2's source in visit_source table.
VisitSourceMap visit_sources;
ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources));
ASSERT_EQ(0U, visit_sources.size());
id = backend_->db()->GetRowForURL(url2, &row);
ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
ASSERT_EQ(2U, visits.size());
ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources));
ASSERT_EQ(2U, visit_sources.size());
for (int i = 0; i < 2; i++)
EXPECT_EQ(history::SOURCE_SYNCED, visit_sources[visits[i].visit_id]);
}
// Test for migration of adding visit_source table.
TEST_F(HistoryBackendTest, MigrationVisitSource) {
ASSERT_TRUE(backend_.get());
backend_->Closing();
backend_ = NULL;
base::FilePath old_history_path;
ASSERT_TRUE(GetTestDataHistoryDir(&old_history_path));
old_history_path = old_history_path.AppendASCII("HistoryNoSource");
// Copy history database file to current directory so that it will be deleted
// in Teardown.
base::FilePath new_history_path(test_dir());
base::DeleteFile(new_history_path, true);
base::CreateDirectory(new_history_path);
base::FilePath new_history_file = new_history_path.Append(kHistoryFilename);
ASSERT_TRUE(base::CopyFile(old_history_path, new_history_file));
backend_ = new HistoryBackend(new HistoryBackendTestDelegate(this),
history_client_.CreateBackendClient(),
base::ThreadTaskRunnerHandle::Get());
backend_->Init(false, TestHistoryDatabaseParamsForPath(new_history_path));
backend_->Closing();
backend_ = NULL;
// Now the database should already be migrated.
// Check version first.
int cur_version = HistoryDatabase::GetCurrentVersion();
sql::Connection db;
ASSERT_TRUE(db.Open(new_history_file));
sql::Statement s(db.GetUniqueStatement(
"SELECT value FROM meta WHERE key = 'version'"));
ASSERT_TRUE(s.Step());
int file_version = s.ColumnInt(0);
EXPECT_EQ(cur_version, file_version);
// Check visit_source table is created and empty.
s.Assign(db.GetUniqueStatement(
"SELECT name FROM sqlite_master WHERE name=\"visit_source\""));
ASSERT_TRUE(s.Step());
s.Assign(db.GetUniqueStatement("SELECT * FROM visit_source LIMIT 10"));
EXPECT_FALSE(s.Step());
}
// Test that SetFaviconMappingsForPageAndRedirects correctly updates icon
// mappings based on redirects, icon URLs and icon types.
TEST_F(HistoryBackendTest, SetFaviconMappingsForPageAndRedirects) {
// Init recent_redirects_
const GURL url1("http://www.google.com");
const GURL url2("http://www.google.com/m");
URLRow url_info1(url1);
url_info1.set_visit_count(0);
url_info1.set_typed_count(0);
url_info1.set_last_visit(base::Time());
url_info1.set_hidden(false);
backend_->db_->AddURL(url_info1);
URLRow url_info2(url2);
url_info2.set_visit_count(0);
url_info2.set_typed_count(0);
url_info2.set_last_visit(base::Time());
url_info2.set_hidden(false);
backend_->db_->AddURL(url_info2);
history::RedirectList redirects;
redirects.push_back(url2);
redirects.push_back(url1);
backend_->recent_redirects_.Put(url1, redirects);
const GURL icon_url1("http://www.google.com/icon");
const GURL icon_url2("http://www.google.com/icon2");
std::vector<SkBitmap> bitmaps;
bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize));
// Add a favicon.
backend_->SetFavicons(url1, favicon_base::FAVICON, icon_url1, bitmaps);
EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON));
EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, favicon_base::FAVICON));
// Add one touch_icon
backend_->SetFavicons(url1, favicon_base::TOUCH_ICON, icon_url1, bitmaps);
EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::TOUCH_ICON));
EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, favicon_base::TOUCH_ICON));
EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON));
// Add one TOUCH_PRECOMPOSED_ICON
backend_->SetFavicons(
url1, favicon_base::TOUCH_PRECOMPOSED_ICON, icon_url1, bitmaps);
// The touch_icon was replaced.
EXPECT_EQ(0u, NumIconMappingsForPageURL(url1, favicon_base::TOUCH_ICON));
EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON));
EXPECT_EQ(
1u,
NumIconMappingsForPageURL(url1, favicon_base::TOUCH_PRECOMPOSED_ICON));
EXPECT_EQ(
1u,
NumIconMappingsForPageURL(url2, favicon_base::TOUCH_PRECOMPOSED_ICON));
// Add a touch_icon.
backend_->SetFavicons(url1, favicon_base::TOUCH_ICON, icon_url1, bitmaps);
EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::TOUCH_ICON));
EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON));
// The TOUCH_PRECOMPOSED_ICON was replaced.
EXPECT_EQ(
0u,
NumIconMappingsForPageURL(url1, favicon_base::TOUCH_PRECOMPOSED_ICON));
// Add a different favicon.
backend_->SetFavicons(url1, favicon_base::FAVICON, icon_url2, bitmaps);
EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::TOUCH_ICON));
EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON));
EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, favicon_base::FAVICON));
}
// Test that SetFaviconMappingsForPageAndRedirects correctly updates icon
// mappings when the final URL has a fragment.
TEST_F(HistoryBackendTest, SetFaviconMappingsForPageAndRedirectsWithFragment) {
// URLs used for recent_redirects_
const GURL url1("http://www.google.com#abc");
const GURL url2("http://www.google.com");
const GURL url3("http://www.google.com/m");
URLRow url_info1(url1);
url_info1.set_visit_count(0);
url_info1.set_typed_count(0);
url_info1.set_last_visit(base::Time());
url_info1.set_hidden(false);
backend_->db_->AddURL(url_info1);
URLRow url_info2(url2);
url_info2.set_visit_count(0);
url_info2.set_typed_count(0);
url_info2.set_last_visit(base::Time());
url_info2.set_hidden(false);
backend_->db_->AddURL(url_info2);
URLRow url_info3(url3);
url_info3.set_visit_count(0);
url_info3.set_typed_count(0);
url_info3.set_last_visit(base::Time());
url_info3.set_hidden(false);
backend_->db_->AddURL(url_info3);
// Icon URL.
const GURL icon_url1("http://www.google.com/icon");
std::vector<SkBitmap> bitmaps;
bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
// Page URL has a fragment, but redirect is keyed to the same URL without a
// fragment.
history::RedirectList redirects;
redirects.push_back(url3);
redirects.push_back(url2);
backend_->recent_redirects_.Put(url2, redirects);
backend_->SetFavicons(url1, favicon_base::FAVICON, icon_url1, bitmaps);
EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON));
EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, favicon_base::FAVICON));
EXPECT_EQ(1u, NumIconMappingsForPageURL(url3, favicon_base::FAVICON));
// Both page and redirect key have a fragment.
redirects.clear();
redirects.push_back(url3);
redirects.push_back(url2);
redirects.push_back(url1);
backend_->recent_redirects_.Clear();
backend_->recent_redirects_.Put(url1, redirects);
backend_->SetFavicons(url1, favicon_base::FAVICON, icon_url1, bitmaps);
EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON));
EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, favicon_base::FAVICON));
EXPECT_EQ(1u, NumIconMappingsForPageURL(url3, favicon_base::FAVICON));
}
// Test that there is no churn in icon mappings from calling
// SetFavicons() twice with the same |bitmaps| parameter.
TEST_F(HistoryBackendTest, SetFaviconMappingsForPageDuplicates) {
const GURL url("http://www.google.com/");
const GURL icon_url("http://www.google.com/icon");
std::vector<SkBitmap> bitmaps;
bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize));
backend_->SetFavicons(url, favicon_base::FAVICON, icon_url, bitmaps);
std::vector<IconMapping> icon_mappings;
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
url, favicon_base::FAVICON, &icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
IconMappingID mapping_id = icon_mappings[0].mapping_id;
backend_->SetFavicons(url, favicon_base::FAVICON, icon_url, bitmaps);
icon_mappings.clear();
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
url, favicon_base::FAVICON, &icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
// The same row in the icon_mapping table should be used for the mapping as
// before.
EXPECT_EQ(mapping_id, icon_mappings[0].mapping_id);
}
// Test that calling SetFavicons() with FaviconBitmapData of different pixel
// sizes than the initially passed in FaviconBitmapData deletes the no longer
// used favicon bitmaps.
TEST_F(HistoryBackendTest, SetFaviconsDeleteBitmaps) {
const GURL page_url("http://www.google.com/");
const GURL icon_url("http://www.google.com/icon");
std::vector<SkBitmap> bitmaps;
bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize));
backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);
// Test initial state.
std::vector<IconMapping> icon_mappings;
EXPECT_TRUE(GetSortedIconMappingsForPageURL(page_url, &icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
EXPECT_EQ(icon_url, icon_mappings[0].icon_url);
EXPECT_EQ(favicon_base::FAVICON, icon_mappings[0].icon_type);
favicon_base::FaviconID favicon_id = icon_mappings[0].icon_id;
std::vector<FaviconBitmap> favicon_bitmaps;
EXPECT_TRUE(GetSortedFaviconBitmaps(favicon_id, &favicon_bitmaps));
EXPECT_EQ(2u, favicon_bitmaps.size());
FaviconBitmapID small_bitmap_id = favicon_bitmaps[0].bitmap_id;
EXPECT_NE(0, small_bitmap_id);
EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmaps[0].bitmap_data));
EXPECT_EQ(kSmallSize, favicon_bitmaps[0].pixel_size);
FaviconBitmapID large_bitmap_id = favicon_bitmaps[1].bitmap_id;
EXPECT_NE(0, large_bitmap_id);
EXPECT_TRUE(BitmapColorEqual(SK_ColorRED, favicon_bitmaps[1].bitmap_data));
EXPECT_EQ(kLargeSize, favicon_bitmaps[1].pixel_size);
// Call SetFavicons() with bitmap data for only the large bitmap. Check that
// the small bitmap is in fact deleted.
bitmaps.clear();
bitmaps.push_back(CreateBitmap(SK_ColorWHITE, kLargeEdgeSize));
backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);
scoped_refptr<base::RefCountedMemory> bitmap_data_out;
gfx::Size pixel_size_out;
EXPECT_FALSE(backend_->thumbnail_db_->GetFaviconBitmap(small_bitmap_id,
NULL, NULL, &bitmap_data_out, &pixel_size_out));
EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmap(large_bitmap_id,
NULL, NULL, &bitmap_data_out, &pixel_size_out));
EXPECT_TRUE(BitmapColorEqual(SK_ColorWHITE, bitmap_data_out));
EXPECT_EQ(kLargeSize, pixel_size_out);
icon_mappings.clear();
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
&icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
EXPECT_EQ(favicon_id, icon_mappings[0].icon_id);
}
// Test updating a single favicon bitmap's data via SetFavicons.
TEST_F(HistoryBackendTest, SetFaviconsReplaceBitmapData) {
const GURL page_url("http://www.google.com/");
const GURL icon_url("http://www.google.com/icon");
std::vector<SkBitmap> bitmaps;
bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
// Add bitmap to the database.
backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);
favicon_base::FaviconID original_favicon_id =
backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
icon_url, favicon_base::FAVICON, NULL);
EXPECT_NE(0, original_favicon_id);
FaviconBitmap original_favicon_bitmap;
EXPECT_TRUE(
GetOnlyFaviconBitmap(original_favicon_id, &original_favicon_bitmap));
EXPECT_TRUE(
BitmapColorEqual(SK_ColorBLUE, original_favicon_bitmap.bitmap_data));
EXPECT_NE(base::Time(), original_favicon_bitmap.last_updated);
// Call SetFavicons() with completely identical data.
bitmaps[0] = CreateBitmap(SK_ColorBLUE, kSmallEdgeSize);
backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);
favicon_base::FaviconID updated_favicon_id =
backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
icon_url, favicon_base::FAVICON, NULL);
EXPECT_NE(0, updated_favicon_id);
FaviconBitmap updated_favicon_bitmap;
EXPECT_TRUE(
GetOnlyFaviconBitmap(updated_favicon_id, &updated_favicon_bitmap));
EXPECT_TRUE(
BitmapColorEqual(SK_ColorBLUE, updated_favicon_bitmap.bitmap_data));
EXPECT_NE(base::Time(), updated_favicon_bitmap.last_updated);
// Call SetFavicons() with a different bitmap of the same size.
bitmaps[0] = CreateBitmap(SK_ColorWHITE, kSmallEdgeSize);
backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);
updated_favicon_id = backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
icon_url, favicon_base::FAVICON, NULL);
EXPECT_NE(0, updated_favicon_id);
EXPECT_TRUE(
GetOnlyFaviconBitmap(updated_favicon_id, &updated_favicon_bitmap));
EXPECT_TRUE(
BitmapColorEqual(SK_ColorWHITE, updated_favicon_bitmap.bitmap_data));
// There should be no churn in FaviconIDs or FaviconBitmapIds even though
// the bitmap data changed.
EXPECT_EQ(original_favicon_bitmap.icon_id, updated_favicon_bitmap.icon_id);
EXPECT_EQ(original_favicon_bitmap.bitmap_id,
updated_favicon_bitmap.bitmap_id);
}
// Test that if two pages share the same FaviconID, changing the favicon for
// one page does not affect the other.
TEST_F(HistoryBackendTest, SetFaviconsSameFaviconURLForTwoPages) {
GURL icon_url("http://www.google.com/favicon.ico");
GURL icon_url_new("http://www.google.com/favicon2.ico");
GURL page_url1("http://www.google.com");
GURL page_url2("http://www.google.ca");
std::vector<SkBitmap> bitmaps;
bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize));
backend_->SetFavicons(page_url1, favicon_base::FAVICON, icon_url, bitmaps);
std::vector<GURL> icon_urls;
icon_urls.push_back(icon_url);
std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results;
backend_->UpdateFaviconMappingsAndFetch(page_url2,
icon_urls,
favicon_base::FAVICON,
GetEdgeSizesSmallAndLarge(),
&bitmap_results);
// Check that the same FaviconID is mapped to both page URLs.
std::vector<IconMapping> icon_mappings;
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
page_url1, &icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
favicon_base::FaviconID favicon_id = icon_mappings[0].icon_id;
EXPECT_NE(0, favicon_id);
icon_mappings.clear();
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
page_url2, &icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
EXPECT_EQ(favicon_id, icon_mappings[0].icon_id);
// Change the icon URL that |page_url1| is mapped to.
bitmaps.clear();
bitmaps.push_back(CreateBitmap(SK_ColorWHITE, kSmallEdgeSize));
backend_->SetFavicons(
page_url1, favicon_base::FAVICON, icon_url_new, bitmaps);
// |page_url1| should map to a new FaviconID and have valid bitmap data.
icon_mappings.clear();
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
page_url1, &icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
EXPECT_EQ(icon_url_new, icon_mappings[0].icon_url);
EXPECT_NE(favicon_id, icon_mappings[0].icon_id);
std::vector<FaviconBitmap> favicon_bitmaps;
EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps(
icon_mappings[0].icon_id, &favicon_bitmaps));
EXPECT_EQ(1u, favicon_bitmaps.size());
// |page_url2| should still map to the same FaviconID and have valid bitmap
// data.
icon_mappings.clear();
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
page_url2, &icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
EXPECT_EQ(favicon_id, icon_mappings[0].icon_id);
favicon_bitmaps.clear();
EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps(favicon_id,
&favicon_bitmaps));
EXPECT_EQ(2u, favicon_bitmaps.size());
}
// Tests calling SetLastResortFavicons(). Neither |page_url| nor |icon_url| are
// known to the database.
TEST_F(HistoryBackendTest, SetLastResortFaviconsForEmptyDB) {
GURL page_url("http://www.google.com");
GURL icon_url("http:/www.google.com/favicon.ico");
std::vector<SkBitmap> bitmaps;
bitmaps.push_back(CreateBitmap(SK_ColorRED, kSmallEdgeSize));
// Call SetLastResortFavicons() with a different icon URL and bitmap data.
EXPECT_TRUE(backend_->SetLastResortFavicons(page_url, favicon_base::FAVICON,
icon_url, bitmaps));
favicon_base::FaviconID favicon_id =
backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
icon_url, favicon_base::FAVICON, NULL);
EXPECT_NE(0, favicon_id);
FaviconBitmap favicon_bitmap;
ASSERT_TRUE(GetOnlyFaviconBitmap(favicon_id, &favicon_bitmap));
// The original bitmap should have been retrieved.
EXPECT_TRUE(BitmapColorEqual(SK_ColorRED, favicon_bitmap.bitmap_data));
// The favicon should not be marked as expired.
EXPECT_EQ(base::Time(), favicon_bitmap.last_updated);
}
// Tests calling SetLastResortFavicons(). |page_url| is known to the database
// but |icon_url| is not (the second should be irrelevant though).
TEST_F(HistoryBackendTest, SetLastResortFaviconsForPageInDB) {
GURL page_url("http://www.google.com");
GURL icon_url1("http:/www.google.com/favicon1.ico");
GURL icon_url2("http:/www.google.com/favicon2.ico");
std::vector<SkBitmap> bitmaps;
bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
// Add bitmap to the database.
backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url1, bitmaps);
favicon_base::FaviconID original_favicon_id =
backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
icon_url1, favicon_base::FAVICON, NULL);
ASSERT_NE(0, original_favicon_id);
// Call SetLastResortFavicons() with a different icon URL and bitmap data.
bitmaps[0] = CreateBitmap(SK_ColorWHITE, kSmallEdgeSize);
EXPECT_FALSE(backend_->SetLastResortFavicons(page_url, favicon_base::FAVICON,
icon_url2, bitmaps));
EXPECT_EQ(0, backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
icon_url2, favicon_base::FAVICON, NULL));
FaviconBitmap favicon_bitmap;
ASSERT_TRUE(GetOnlyFaviconBitmap(original_favicon_id, &favicon_bitmap));
// The original bitmap should have been retrieved.
EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data));
// The favicon should not be marked as expired.
EXPECT_NE(base::Time(), favicon_bitmap.last_updated);
}
// Tests calling SetLastResortFavicons(). |page_url| is not known to the
// database but |icon_url| is.
TEST_F(HistoryBackendTest, SetLastResortFaviconsForIconInDB) {
const GURL old_page_url("http://www.google.com/old");
const GURL page_url("http://www.google.com/");
const GURL icon_url("http://www.google.com/icon");
std::vector<SkBitmap> bitmaps;
bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
// Add bitmap to the database.
backend_->SetFavicons(old_page_url, favicon_base::FAVICON, icon_url, bitmaps);
favicon_base::FaviconID original_favicon_id =
backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
icon_url, favicon_base::FAVICON, NULL);
ASSERT_NE(0, original_favicon_id);
// Call SetLastResortFavicons() with a different bitmap.
bitmaps[0] = CreateBitmap(SK_ColorWHITE, kSmallEdgeSize);
EXPECT_FALSE(backend_->SetLastResortFavicons(page_url, favicon_base::FAVICON,
icon_url, bitmaps));
EXPECT_EQ(original_favicon_id,
backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
icon_url, favicon_base::FAVICON, NULL));
FaviconBitmap favicon_bitmap;
ASSERT_TRUE(GetOnlyFaviconBitmap(original_favicon_id, &favicon_bitmap));
// The original bitmap should have been retrieved.
EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data));
// The favicon should not be marked as expired.
EXPECT_NE(base::Time(), favicon_bitmap.last_updated);
}
// Test repeatedly calling MergeFavicon(). |page_url| is initially not known
// to the database.
TEST_F(HistoryBackendTest, MergeFaviconPageURLNotInDB) {
GURL page_url("http://www.google.com");
GURL icon_url("http:/www.google.com/favicon.ico");
std::vector<unsigned char> data;
data.push_back('a');
scoped_refptr<base::RefCountedBytes> bitmap_data(
new base::RefCountedBytes(data));
backend_->MergeFavicon(
page_url, icon_url, favicon_base::FAVICON, bitmap_data, kSmallSize);
// |page_url| should now be mapped to |icon_url| and the favicon bitmap should
// not be expired.
std::vector<IconMapping> icon_mappings;
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
&icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
EXPECT_EQ(icon_url, icon_mappings[0].icon_url);
FaviconBitmap favicon_bitmap;
EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap));
EXPECT_NE(base::Time(), favicon_bitmap.last_updated);
EXPECT_TRUE(BitmapDataEqual('a', favicon_bitmap.bitmap_data));
EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);
data[0] = 'b';
bitmap_data = new base::RefCountedBytes(data);
backend_->MergeFavicon(
page_url, icon_url, favicon_base::FAVICON, bitmap_data, kSmallSize);
// |page_url| should still have a single favicon bitmap. The bitmap data
// should be updated.
icon_mappings.clear();
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
&icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
EXPECT_EQ(icon_url, icon_mappings[0].icon_url);
EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap));
EXPECT_EQ(base::Time(), favicon_bitmap.last_updated);
EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmap.bitmap_data));
EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);
}
// Test calling MergeFavicon() when |page_url| is known to the database.
TEST_F(HistoryBackendTest, MergeFaviconPageURLInDB) {
GURL page_url("http://www.google.com");
GURL icon_url1("http:/www.google.com/favicon.ico");
GURL icon_url2("http://www.google.com/favicon2.ico");
std::vector<SkBitmap> bitmaps;
bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url1, bitmaps);
// Test initial state.
std::vector<IconMapping> icon_mappings;
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
&icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
EXPECT_EQ(icon_url1, icon_mappings[0].icon_url);
FaviconBitmap favicon_bitmap;
EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap));
EXPECT_NE(base::Time(), favicon_bitmap.last_updated);
EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data));
EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);
// 1) Merge identical favicon bitmap.
std::vector<unsigned char> data;
gfx::PNGCodec::EncodeBGRASkBitmap(bitmaps[0], false, &data);
scoped_refptr<base::RefCountedBytes> bitmap_data(
new base::RefCountedBytes(data));
backend_->MergeFavicon(
page_url, icon_url1, favicon_base::FAVICON, bitmap_data, kSmallSize);
// All the data should stay the same and no notifications should have been
// sent.
icon_mappings.clear();
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
&icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
EXPECT_EQ(icon_url1, icon_mappings[0].icon_url);
EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap));
EXPECT_NE(base::Time(), favicon_bitmap.last_updated);
EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data));
EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);
// 2) Merge favicon bitmap of the same size.
data.clear();
data.push_back('b');
bitmap_data = new base::RefCountedBytes(data);
backend_->MergeFavicon(
page_url, icon_url1, favicon_base::FAVICON, bitmap_data, kSmallSize);
// The small favicon bitmap at |icon_url1| should be overwritten.
icon_mappings.clear();
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
&icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
EXPECT_EQ(icon_url1, icon_mappings[0].icon_url);
EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap));
EXPECT_EQ(base::Time(), favicon_bitmap.last_updated);
EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmap.bitmap_data));
EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);
// 3) Merge favicon for the same icon URL, but a pixel size for which there is
// no favicon bitmap.
data[0] = 'c';
bitmap_data = new base::RefCountedBytes(data);
backend_->MergeFavicon(
page_url, icon_url1, favicon_base::FAVICON, bitmap_data, kTinySize);
// A new favicon bitmap should be created and the preexisting favicon bitmap
// ('b') should be expired.
icon_mappings.clear();
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
&icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
EXPECT_EQ(icon_url1, icon_mappings[0].icon_url);
std::vector<FaviconBitmap> favicon_bitmaps;
EXPECT_TRUE(GetSortedFaviconBitmaps(icon_mappings[0].icon_id,
&favicon_bitmaps));
EXPECT_NE(base::Time(), favicon_bitmaps[0].last_updated);
EXPECT_TRUE(BitmapDataEqual('c', favicon_bitmaps[0].bitmap_data));
EXPECT_EQ(kTinySize, favicon_bitmaps[0].pixel_size);
EXPECT_EQ(base::Time(), favicon_bitmaps[1].last_updated);
EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmaps[1].bitmap_data));
EXPECT_EQ(kSmallSize, favicon_bitmaps[1].pixel_size);
// 4) Merge favicon for an icon URL different from the icon URLs already
// mapped to page URL.
data[0] = 'd';
bitmap_data = new base::RefCountedBytes(data);
backend_->MergeFavicon(
page_url, icon_url2, favicon_base::FAVICON, bitmap_data, kSmallSize);
// The existing favicon bitmaps should be copied over to the newly created
// favicon at |icon_url2|. |page_url| should solely be mapped to |icon_url2|.
icon_mappings.clear();
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
&icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
EXPECT_EQ(icon_url2, icon_mappings[0].icon_url);
favicon_bitmaps.clear();
EXPECT_TRUE(GetSortedFaviconBitmaps(icon_mappings[0].icon_id,
&favicon_bitmaps));
EXPECT_EQ(base::Time(), favicon_bitmaps[0].last_updated);
EXPECT_TRUE(BitmapDataEqual('c', favicon_bitmaps[0].bitmap_data));
EXPECT_EQ(kTinySize, favicon_bitmaps[0].pixel_size);
// The favicon being merged should take precedence over the preexisting
// favicon bitmaps.
EXPECT_NE(base::Time(), favicon_bitmaps[1].last_updated);
EXPECT_TRUE(BitmapDataEqual('d', favicon_bitmaps[1].bitmap_data));
EXPECT_EQ(kSmallSize, favicon_bitmaps[1].pixel_size);
}
// Test calling MergeFavicon() when |icon_url| is known to the database but not
// mapped to |page_url|.
TEST_F(HistoryBackendTest, MergeFaviconIconURLMappedToDifferentPageURL) {
GURL page_url1("http://www.google.com");
GURL page_url2("http://news.google.com");
GURL page_url3("http://maps.google.com");
GURL icon_url("http:/www.google.com/favicon.ico");
std::vector<SkBitmap> bitmaps;
bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
backend_->SetFavicons(page_url1, favicon_base::FAVICON, icon_url, bitmaps);
// Test initial state.
std::vector<IconMapping> icon_mappings;
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url1,
&icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
EXPECT_EQ(icon_url, icon_mappings[0].icon_url);
FaviconBitmap favicon_bitmap;
EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap));
EXPECT_NE(base::Time(), favicon_bitmap.last_updated);
EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data));
EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);
// 1) Merge in an identical favicon bitmap data but for a different page URL.
std::vector<unsigned char> data;
gfx::PNGCodec::EncodeBGRASkBitmap(bitmaps[0], false, &data);
scoped_refptr<base::RefCountedBytes> bitmap_data(
new base::RefCountedBytes(data));
backend_->MergeFavicon(
page_url2, icon_url, favicon_base::FAVICON, bitmap_data, kSmallSize);
favicon_base::FaviconID favicon_id =
backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
icon_url, favicon_base::FAVICON, NULL);
EXPECT_NE(0, favicon_id);
EXPECT_TRUE(GetOnlyFaviconBitmap(favicon_id, &favicon_bitmap));
EXPECT_NE(base::Time(), favicon_bitmap.last_updated);
EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data));
EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);
// 2) Merging a favicon bitmap with different bitmap data for the same icon
// URL should overwrite the small favicon bitmap at |icon_url|.
data.clear();
data.push_back('b');
bitmap_data = new base::RefCountedBytes(data);
backend_->MergeFavicon(
page_url3, icon_url, favicon_base::FAVICON, bitmap_data, kSmallSize);
favicon_id = backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
icon_url, favicon_base::FAVICON, NULL);
EXPECT_NE(0, favicon_id);
EXPECT_TRUE(GetOnlyFaviconBitmap(favicon_id, &favicon_bitmap));
EXPECT_EQ(base::Time(), favicon_bitmap.last_updated);
EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmap.bitmap_data));
EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);
// |icon_url| should be mapped to all three page URLs.
icon_mappings.clear();
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url1,
&icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
EXPECT_EQ(favicon_id, icon_mappings[0].icon_id);
icon_mappings.clear();
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url2,
&icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
EXPECT_EQ(favicon_id, icon_mappings[0].icon_id);
icon_mappings.clear();
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url3,
&icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
EXPECT_EQ(favicon_id, icon_mappings[0].icon_id);
}
// Test that MergeFavicon() does not add more than
// |kMaxFaviconBitmapsPerIconURL| to a favicon.
TEST_F(HistoryBackendTest, MergeFaviconMaxFaviconBitmapsPerIconURL) {
GURL page_url("http://www.google.com");
std::string icon_url_string("http://www.google.com/favicon.ico");
size_t replace_index = icon_url_string.size() - 1;
std::vector<unsigned char> data;
data.push_back('a');
scoped_refptr<base::RefCountedMemory> bitmap_data =
base::RefCountedBytes::TakeVector(&data);
int pixel_size = 1;
for (size_t i = 0; i < kMaxFaviconBitmapsPerIconURL + 1; ++i) {
icon_url_string[replace_index] = '0' + i;
GURL icon_url(icon_url_string);
backend_->MergeFavicon(page_url,
icon_url,
favicon_base::FAVICON,
bitmap_data,
gfx::Size(pixel_size, pixel_size));
++pixel_size;
}
// There should be a single favicon mapped to |page_url| with exactly
// kMaxFaviconBitmapsPerIconURL favicon bitmaps.
std::vector<IconMapping> icon_mappings;
EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
&icon_mappings));
EXPECT_EQ(1u, icon_mappings.size());
std::vector<FaviconBitmap> favicon_bitmaps;
EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps(
icon_mappings[0].icon_id, &favicon_bitmaps));
EXPECT_EQ(kMaxFaviconBitmapsPerIconURL, favicon_bitmaps.size());
}
// Tests that the favicon set by MergeFavicon() shows up in the result of
// GetFaviconsForURL().
TEST_F(HistoryBackendTest, MergeFaviconShowsUpInGetFaviconsForURLResult) {
GURL page_url("http://www.google.com");
GURL icon_url("http://www.google.com/favicon.ico");
GURL merged_icon_url("http://wwww.google.com/favicon2.ico");
std::vector<SkBitmap> bitmaps;
bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize));
// Set some preexisting favicons for |page_url|.
backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);
// Merge small favicon.
std::vector<unsigned char> data;
data.push_back('c');
scoped_refptr<base::RefCountedBytes> bitmap_data(
new base::RefCountedBytes(data));
backend_->MergeFavicon(page_url,
merged_icon_url,
favicon_base::FAVICON,
bitmap_data,
kSmallSize);
// Request favicon bitmaps for both 1x and 2x to simulate request done by
// BookmarkModel::GetFavicon().
std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results;
backend_->GetFaviconsForURL(page_url,
favicon_base::FAVICON,
GetEdgeSizesSmallAndLarge(),
&bitmap_results);
EXPECT_EQ(2u, bitmap_results.size());
const favicon_base::FaviconRawBitmapResult& first_result = bitmap_results[0];
const favicon_base::FaviconRawBitmapResult& result =
(first_result.pixel_size == kSmallSize) ? first_result
: bitmap_results[1];
EXPECT_TRUE(BitmapDataEqual('c', result.bitmap_data));
}
// Test that adding a favicon for a new icon URL:
// - Sends a notification that the favicon for the page URL has changed.
// - Does not send a notification that the favicon for the icon URL has changed
// as there are no other page URLs which use the icon URL.
TEST_F(HistoryBackendTest, FaviconChangedNotificationNewFavicon) {
GURL page_url1("http://www.google.com/a");
GURL icon_url1("http://www.google.com/favicon1.ico");
GURL page_url2("http://www.google.com/b");
GURL icon_url2("http://www.google.com/favicon2.ico");
// SetFavicons()
{
std::vector<SkBitmap> bitmaps;
bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
backend_->SetFavicons(page_url1, favicon_base::FAVICON, icon_url1, bitmaps);
ASSERT_EQ(1u, favicon_changed_notifications_page_urls().size());
EXPECT_EQ(page_url1, favicon_changed_notifications_page_urls()[0]);
EXPECT_EQ(0u, favicon_changed_notifications_icon_urls().size());
ClearBroadcastedNotifications();
}
// MergeFavicon()
{
std::vector<unsigned char> data;
data.push_back('a');
scoped_refptr<base::RefCountedBytes> bitmap_data(
new base::RefCountedBytes(data));
backend_->MergeFavicon(
page_url2, icon_url2, favicon_base::FAVICON, bitmap_data, kSmallSize);
ASSERT_EQ(1u, favicon_changed_notifications_page_urls().size());
EXPECT_EQ(page_url2, favicon_changed_notifications_page_urls()[0]);
EXPECT_EQ(0u, favicon_changed_notifications_icon_urls().size());
}
}
// Test that changing the favicon bitmap data for an icon URL:
// - Does not send a notification that the favicon for the page URL has changed.
// - Sends a notification that the favicon for the icon URL has changed (Several
// page URLs may be mapped to the icon URL).
TEST_F(HistoryBackendTest, FaviconChangedNotificationBitmapDataChanged) {
GURL page_url("http://www.google.com");
GURL icon_url("http://www.google.com/favicon.ico");
// Setup
{
std::vector<SkBitmap> bitmaps;
bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);
ClearBroadcastedNotifications();
}
// SetFavicons()
{
std::vector<SkBitmap> bitmaps;
bitmaps.push_back(CreateBitmap(SK_ColorWHITE, kSmallEdgeSize));
backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);
EXPECT_EQ(0u, favicon_changed_notifications_page_urls().size());
ASSERT_EQ(1u, favicon_changed_notifications_icon_urls().size());
EXPECT_EQ(icon_url, favicon_changed_notifications_icon_urls()[0]);