summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authordglazkov@chromium.org <dglazkov@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-03 17:38:39 +0000
committerdglazkov@chromium.org <dglazkov@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-03 17:38:39 +0000
commit3e90d4a00082fd985ab610cd2faff84b5c597a4e (patch)
tree4d2f0da17f4fb68bab53381c9f49351380a24127 /chrome/browser
parentfd694982d0e1ee8b3e1753d9c48b01c1a633ac27 (diff)
downloadchromium_src-3e90d4a00082fd985ab610cd2faff84b5c597a4e.zip
chromium_src-3e90d4a00082fd985ab610cd2faff84b5c597a4e.tar.gz
chromium_src-3e90d4a00082fd985ab610cd2faff84b5c597a4e.tar.bz2
Fix Acid3 Test 48: LINKTEST, Chromium side....
R=brettw BUG=http://crbug.com/231 BUG=http://crubg.com/5160 TEST=ExpireHistoryTest.ArchiveSomeOldHistory TEST=ExpireHistoryTest.ExpiringVisitsReader TEST=VisitedLinkTest.Listener TEST=VisitedLinkTest.Resizing TEST=VisitedLinkRelayTest.* TEST=VisitedLinkEventsTest.* Review URL: http://codereview.chromium.org/113591 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@19910 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/history/expire_history_backend.cc154
-rw-r--r--chrome/browser/history/expire_history_backend.h56
-rw-r--r--chrome/browser/history/expire_history_backend_unittest.cc37
-rw-r--r--chrome/browser/history/history.cc3
-rw-r--r--chrome/browser/history/history_database.cc24
-rw-r--r--chrome/browser/history/history_database.h7
-rw-r--r--chrome/browser/history/redirect_uitest.cc2
-rw-r--r--chrome/browser/history/visit_database.cc29
-rw-r--r--chrome/browser/history/visit_database.h15
-rw-r--r--chrome/browser/profile.cc27
-rw-r--r--chrome/browser/profile.h2
-rw-r--r--chrome/browser/renderer_host/browser_render_process_host.cc73
-rw-r--r--chrome/browser/renderer_host/browser_render_process_host.h8
-rw-r--r--chrome/browser/renderer_host/mock_render_process_host.cc7
-rw-r--r--chrome/browser/renderer_host/mock_render_process_host.h3
-rw-r--r--chrome/browser/renderer_host/render_process_host.h9
-rw-r--r--chrome/browser/renderer_host/test_render_view_host.h5
-rw-r--r--chrome/browser/tab_contents/tab_contents.cc26
-rw-r--r--chrome/browser/visitedlink_event_listener.cc68
-rw-r--r--chrome/browser/visitedlink_event_listener.h37
-rw-r--r--chrome/browser/visitedlink_master.cc26
-rw-r--r--chrome/browser/visitedlink_master.h28
-rw-r--r--chrome/browser/visitedlink_perftest.cc42
-rw-r--r--chrome/browser/visitedlink_unittest.cc366
24 files changed, 939 insertions, 115 deletions
diff --git a/chrome/browser/history/expire_history_backend.cc b/chrome/browser/history/expire_history_backend.cc
index 084fa61..7243aa3 100644
--- a/chrome/browser/history/expire_history_backend.cc
+++ b/chrome/browser/history/expire_history_backend.cc
@@ -24,6 +24,62 @@ namespace history {
namespace {
+// The number of days by which the expiration threshold is advanced for items
+// that we want to expire early, such as those of AUTO_SUBFRAME transition type.
+const int kEarlyExpirationAdvanceDays = 30;
+
+// Reads all types of visits starting from beginning of time to the given end
+// time. This is the most general reader.
+class AllVisitsReader : public ExpiringVisitsReader {
+ public:
+ virtual bool Read(Time end_time, HistoryDatabase* db,
+ VisitVector* visits, int max_visits) const {
+ DCHECK(db) << "must have a database to operate upon";
+ DCHECK(visits) << "visit vector has to exist in order to populate it";
+
+ db->GetAllVisitsInRange(Time(), end_time, max_visits, visits);
+ // When we got the maximum number of visits we asked for, we say there could
+ // be additional things to expire now.
+ return static_cast<int>(visits->size()) == max_visits;
+ }
+};
+
+// Reads only AUTO_SUBFRAME visits, within a computed range. The range is
+// computed as follows:
+// * |begin_time| is read from the meta table. This value is updated whenever
+// there are no more additional visits to expire by this reader.
+// * |end_time| is advanced forward by a constant (kEarlyExpirationAdvanceDay),
+// but not past the current time.
+class AutoSubframeVisitsReader : public ExpiringVisitsReader {
+ public:
+ virtual bool Read(Time end_time, HistoryDatabase* db,
+ VisitVector* visits, int max_visits) const {
+ DCHECK(db) << "must have a database to operate upon";
+ DCHECK(visits) << "visit vector has to exist in order to populate it";
+
+ Time begin_time = db->GetEarlyExpirationThreshold();
+ // Advance |end_time| to expire early.
+ Time early_end_time = end_time +
+ TimeDelta::FromDays(kEarlyExpirationAdvanceDays);
+
+ // We don't want to set the early expiration threshold to a time in the
+ // future.
+ Time now = Time::Now();
+ if (early_end_time > now)
+ early_end_time = now;
+
+ db->GetVisitsInRangeForTransition(begin_time, early_end_time,
+ max_visits,
+ PageTransition::AUTO_SUBFRAME,
+ visits);
+ bool more = static_cast<int>(visits->size()) == max_visits;
+ if (!more)
+ db->UpdateEarlyExpirationThreshold(early_end_time);
+
+ return more;
+ }
+};
+
// Returns true if this visit is worth archiving. Otherwise, this visit is not
// worth saving (for example, subframe navigations and redirects) and we can
// just delete it when it gets old.
@@ -58,7 +114,7 @@ const int kNumExpirePerIteration = 10;
// we think there might be more items to expire. This timeout is used when the
// last expiration found at least kNumExpirePerIteration and we want to check
// again "soon."
-const int kExpirationDelaySec = 60;
+const int kExpirationDelaySec = 30;
// The number of minutes between checking, as with kExpirationDelaySec, but
// when we didn't find enough things to expire last time. If there was no
@@ -166,14 +222,47 @@ void ExpireHistoryBackend::ArchiveHistoryBefore(Time end_time) {
return;
// Archive as much history as possible before the given date.
- ArchiveSomeOldHistory(end_time, std::numeric_limits<size_t>::max());
+ ArchiveSomeOldHistory(end_time, GetAllVisitsReader(),
+ std::numeric_limits<size_t>::max());
ParanoidExpireHistory();
}
+void ExpireHistoryBackend::InitWorkQueue() {
+ DCHECK(work_queue_.empty()) << "queue has to be empty prior to init";
+
+ for (size_t i = 0; i < readers_.size(); i++)
+ work_queue_.push(readers_[i]);
+}
+
+const ExpiringVisitsReader* ExpireHistoryBackend::GetAllVisitsReader() {
+ if (!all_visits_reader_.get())
+ all_visits_reader_.reset(new AllVisitsReader());
+ return all_visits_reader_.get();
+}
+
+const ExpiringVisitsReader*
+ ExpireHistoryBackend::GetAutoSubframeVisitsReader() {
+ if (!auto_subframe_visits_reader_.get())
+ auto_subframe_visits_reader_.reset(new AutoSubframeVisitsReader());
+ return auto_subframe_visits_reader_.get();
+}
+
void ExpireHistoryBackend::StartArchivingOldStuff(
TimeDelta expiration_threshold) {
expiration_threshold_ = expiration_threshold;
- ScheduleArchive(TimeDelta::FromSeconds(kExpirationDelaySec));
+
+ // Remove all readers, just in case this was method was called before.
+ readers_.clear();
+ // For now, we explicitly add all known readers. If we come up with more
+ // reader types (in case we want to expire different types of visits in
+ // different ways), we can make it be populated by creator/owner of
+ // ExpireHistoryBackend.
+ readers_.push_back(GetAllVisitsReader());
+ readers_.push_back(GetAutoSubframeVisitsReader());
+
+ // Initialize the queue with all tasks for the first set of iterations.
+ InitWorkQueue();
+ ScheduleArchive();
}
void ExpireHistoryBackend::DeleteFaviconsIfPossible(
@@ -413,40 +502,53 @@ void ExpireHistoryBackend::ArchiveURLsAndVisits(
}
}
-void ExpireHistoryBackend::ScheduleArchive(TimeDelta delay) {
+void ExpireHistoryBackend::ScheduleArchive() {
+ TimeDelta delay;
+ if (work_queue_.empty()) {
+ // If work queue is empty, reset the work queue to contain all tasks and
+ // schedule next iteration after a longer delay.
+ InitWorkQueue();
+ delay = TimeDelta::FromMinutes(kExpirationEmptyDelayMin);
+ } else {
+ delay = TimeDelta::FromSeconds(kExpirationDelaySec);
+ }
+
factory_.RevokeAll();
MessageLoop::current()->PostDelayedTask(FROM_HERE, factory_.NewRunnableMethod(
&ExpireHistoryBackend::DoArchiveIteration), delay.InMilliseconds());
}
void ExpireHistoryBackend::DoArchiveIteration() {
- DCHECK(expiration_threshold_ != TimeDelta()) << "threshold should be set";
- Time threshold = Time::Now() - expiration_threshold_;
+ DCHECK(!work_queue_.empty()) << "queue has to be non-empty";
- if (ArchiveSomeOldHistory(threshold, kNumExpirePerIteration)) {
- // Possibly more items to delete now, schedule it sooner to happen again.
- ScheduleArchive(TimeDelta::FromSeconds(kExpirationDelaySec));
- } else {
- // If we didn't find the maximum number of items to delete, wait longer
- // before trying to delete more later.
- ScheduleArchive(TimeDelta::FromMinutes(kExpirationEmptyDelayMin));
- }
+ const ExpiringVisitsReader* reader = work_queue_.front();
+ bool more_to_expire = ArchiveSomeOldHistory(GetCurrentArchiveTime(), reader,
+ kNumExpirePerIteration);
+
+ work_queue_.pop();
+ // If there are more items to expire, add the reader back to the queue, thus
+ // creating a new task for future iterations.
+ if (more_to_expire)
+ work_queue_.push(reader);
+
+ ScheduleArchive();
}
-bool ExpireHistoryBackend::ArchiveSomeOldHistory(Time time_threshold,
- int max_visits) {
+bool ExpireHistoryBackend::ArchiveSomeOldHistory(
+ base::Time end_time,
+ const ExpiringVisitsReader* reader,
+ int max_visits) {
if (!main_db_)
return false;
- // Get all visits up to and including the threshold. This is a little tricky
- // because GetAllVisitsInRange's end value is non-inclusive, so we have to
- // increment the time by one unit to get the input value to be inclusive.
- DCHECK(!time_threshold.is_null());
- Time effective_threshold =
- Time::FromInternalValue(time_threshold.ToInternalValue() + 1);
+ // Add an extra time unit to given end time, because
+ // GetAllVisitsInRange, et al. queries' end value is non-inclusive.
+ Time effective_end_time =
+ Time::FromInternalValue(end_time.ToInternalValue() + 1);
+
VisitVector affected_visits;
- main_db_->GetAllVisitsInRange(Time(), effective_threshold, max_visits,
- &affected_visits);
+ bool more_to_expire = reader->Read(effective_end_time, main_db_,
+ &affected_visits, max_visits);
// Some visits we'll delete while others we'll archive.
VisitVector deleted_visits, archived_visits;
@@ -489,9 +591,7 @@ bool ExpireHistoryBackend::ArchiveSomeOldHistory(Time time_threshold,
// to not do anything if nothing was deleted.
BroadcastDeleteNotifications(&deleted_dependencies);
- // When we got the maximum number of visits we asked for, we say there could
- // be additional things to expire now.
- return static_cast<int>(affected_visits.size()) == max_visits;
+ return more_to_expire;
}
void ExpireHistoryBackend::ParanoidExpireHistory() {
diff --git a/chrome/browser/history/expire_history_backend.h b/chrome/browser/history/expire_history_backend.h
index ed44096..0b87b28 100644
--- a/chrome/browser/history/expire_history_backend.h
+++ b/chrome/browser/history/expire_history_backend.h
@@ -5,12 +5,14 @@
#ifndef CHROME_BROWSER_HISTORY_EXPIRE_HISTORY_BACKEND_H__
#define CHROME_BROWSER_HISTORY_EXPIRE_HISTORY_BACKEND_H__
+#include <queue>
#include <set>
#include <vector>
#include "base/basictypes.h"
#include "base/task.h"
#include "base/time.h"
+#include "base/scoped_ptr.h"
#include "chrome/browser/history/history_types.h"
#include "chrome/browser/history/text_database_manager.h"
#include "testing/gtest/include/gtest/gtest_prod.h"
@@ -36,6 +38,18 @@ class BroadcastNotificationDelegate {
HistoryDetails* details_deleted) = 0;
};
+// Encapsulates visit expiration criteria and type of visits to expire.
+class ExpiringVisitsReader {
+ public:
+ virtual ~ExpiringVisitsReader() {}
+ // Populates |visits| from |db|, using provided |end_time| and |max_visits|
+ // cap.
+ virtual bool Read(base::Time end_time, HistoryDatabase* db,
+ VisitVector* visits, int max_visits) const = 0;
+};
+
+typedef std::vector<const ExpiringVisitsReader*> ExpiringVisitsReaders;
+
// Helper component to HistoryBackend that manages expiration and deleting of
// history, as well as moving data from the main database to the archived
// database as it gets old.
@@ -86,6 +100,7 @@ class ExpireHistoryBackend {
FRIEND_TEST(ExpireHistoryTest, DeleteTextIndexForURL);
FRIEND_TEST(ExpireHistoryTest, DeleteFaviconsIfPossible);
FRIEND_TEST(ExpireHistoryTest, ArchiveSomeOldHistory);
+ FRIEND_TEST(ExpireHistoryTest, ExpiringVisitsReader);
friend class ::TestingProfile;
struct DeleteDependencies {
@@ -207,19 +222,21 @@ class ExpireHistoryBackend {
// Broadcast the URL deleted notification.
void BroadcastDeleteNotifications(DeleteDependencies* dependencies);
- // Schedules a call to DoArchiveIteration at the given time in the
- // future.
- void ScheduleArchive(base::TimeDelta delay);
+ // Schedules a call to DoArchiveIteration.
+ void ScheduleArchive();
- // Calls ArchiveSomeOldHistory to expire some amount of old history, and
- // schedules another call to happen in the future.
+ // Calls ArchiveSomeOldHistory to expire some amount of old history, according
+ // to the items in work queue, and schedules another call to happen in the
+ // future.
void DoArchiveIteration();
// Tries to expire the oldest |max_visits| visits from history that are older
// than |time_threshold|. The return value indicates if we think there might
// be more history to expire with the current time threshold (it does not
// indicate success or failure).
- bool ArchiveSomeOldHistory(base::Time time_threshold, int max_visits);
+ bool ArchiveSomeOldHistory(base::Time end_time,
+ const ExpiringVisitsReader* reader,
+ int max_visits);
// Tries to detect possible bad history or inconsistencies in the database
// and deletes items. For example, URLs with no visits.
@@ -229,6 +246,18 @@ class ExpireHistoryBackend {
// NULL.
BookmarkService* GetBookmarkService();
+ // Initializes periodic expiration work queue by populating it with with tasks
+ // for all known readers.
+ void InitWorkQueue();
+
+ // Returns the reader for all visits. This method is only used by the unit
+ // tests.
+ const ExpiringVisitsReader* GetAllVisitsReader();
+
+ // Returns the reader for AUTO_SUBFRAME visits. This method is only used by
+ // the unit tests.
+ const ExpiringVisitsReader* GetAutoSubframeVisitsReader();
+
// Non-owning pointer to the notification delegate (guaranteed non-NULL).
BroadcastNotificationDelegate* delegate_;
@@ -246,6 +275,21 @@ class ExpireHistoryBackend {
// the archived database.
base::TimeDelta expiration_threshold_;
+ // List of all distinct types of readers. This list is used to populate the
+ // work queue.
+ ExpiringVisitsReaders readers_;
+
+ // Work queue for periodic expiration tasks, used by DoArchiveIteration() to
+ // determine what to do at an iteration, as well as populate it for future
+ // iterations.
+ std::queue<const ExpiringVisitsReader*> work_queue_;
+
+ // Readers for various types of visits.
+ // TODO(dglazkov): If you are adding another one, please consider reorganizing
+ // into a map.
+ scoped_ptr<ExpiringVisitsReader> all_visits_reader_;
+ scoped_ptr<ExpiringVisitsReader> auto_subframe_visits_reader_;
+
// The BookmarkService; may be null. This is owned by the Profile.
//
// Use GetBookmarkService to access this, which makes sure the service is
diff --git a/chrome/browser/history/expire_history_backend_unittest.cc b/chrome/browser/history/expire_history_backend_unittest.cc
index 1734499..08cdf38 100644
--- a/chrome/browser/history/expire_history_backend_unittest.cc
+++ b/chrome/browser/history/expire_history_backend_unittest.cc
@@ -695,18 +695,49 @@ TEST_F(ExpireHistoryTest, ArchiveSomeOldHistory) {
URLID url_ids[3];
Time visit_times[4];
AddExampleData(url_ids, visit_times);
+ const ExpiringVisitsReader* reader = expirer_.GetAllVisitsReader();
// Deleting a time range with no URLs should return false (nothing found).
EXPECT_FALSE(expirer_.ArchiveSomeOldHistory(
- visit_times[0] - TimeDelta::FromDays(100), 1));
+ visit_times[0] - TimeDelta::FromDays(100), reader, 1));
// Deleting a time range with not up the the max results should also return
// false (there will only be one visit deleted in this range).
- EXPECT_FALSE(expirer_.ArchiveSomeOldHistory(visit_times[0], 2));
+ EXPECT_FALSE(expirer_.ArchiveSomeOldHistory(visit_times[0], reader, 2));
// Deleting a time range with the max number of results should return true
// (max deleted).
- EXPECT_TRUE(expirer_.ArchiveSomeOldHistory(visit_times[2], 1));
+ EXPECT_TRUE(expirer_.ArchiveSomeOldHistory(visit_times[2], reader, 1));
+}
+
+TEST_F(ExpireHistoryTest, ExpiringVisitsReader) {
+ URLID url_ids[3];
+ Time visit_times[4];
+ AddExampleData(url_ids, visit_times);
+
+ const ExpiringVisitsReader* all = expirer_.GetAllVisitsReader();
+ const ExpiringVisitsReader* auto_subframes =
+ expirer_.GetAutoSubframeVisitsReader();
+
+ VisitVector visits;
+ Time now = Time::Now();
+
+ // Verify that the early expiration threshold, stored in the meta table is
+ // initialized.
+ EXPECT_TRUE(main_db_->GetEarlyExpirationThreshold() ==
+ Time::FromInternalValue(1L));
+
+ // First, attempt reading AUTO_SUBFRAME visits. We should get none.
+ EXPECT_FALSE(auto_subframes->Read(now, main_db_.get(), &visits, 1));
+ EXPECT_EQ(0U, visits.size());
+
+ // Verify that the early expiration threshold was updated, since there are no
+ // AUTO_SUBFRAME visits in the given time range.
+ EXPECT_TRUE(now <= main_db_->GetEarlyExpirationThreshold());
+
+ // Now, read all visits and verify that there's at least one.
+ EXPECT_TRUE(all->Read(now, main_db_.get(), &visits, 1));
+ EXPECT_EQ(1U, visits.size());
}
// TODO(brettw) add some visits with no URL to make sure everything is updated
diff --git a/chrome/browser/history/history.cc b/chrome/browser/history/history.cc
index aa004b4..cbd6754 100644
--- a/chrome/browser/history/history.cc
+++ b/chrome/browser/history/history.cc
@@ -273,8 +273,7 @@ void HistoryService::AddPage(const GURL& url,
// large part of history (think iframes for ads) and we never display them in
// history UI. We will still add manual subframes, which are ones the user
// has clicked on to get.
- if (!CanAddURL(url) || PageTransition::StripQualifier(transition) ==
- PageTransition::AUTO_SUBFRAME)
+ if (!CanAddURL(url))
return;
// Add link & all redirects to visited link list.
diff --git a/chrome/browser/history/history_database.cc b/chrome/browser/history/history_database.cc
index 7b198fc..5a929b1 100644
--- a/chrome/browser/history/history_database.cc
+++ b/chrome/browser/history/history_database.cc
@@ -10,6 +10,8 @@
#include "base/string_util.h"
#include "chrome/common/sqlite_utils.h"
+using base::Time;
+
namespace history {
namespace {
@@ -17,6 +19,7 @@ namespace {
// Current version number.
static const int kCurrentVersionNumber = 16;
static const int kCompatibleVersionNumber = 16;
+static const char kEarlyExpirationThresholdKey[] = "early_expiration_threshold";
} // namespace
@@ -171,6 +174,27 @@ SegmentID HistoryDatabase::GetSegmentID(VisitID visit_id) {
return 0;
}
+Time HistoryDatabase::GetEarlyExpirationThreshold() {
+ if (!cached_early_expiration_threshold_.is_null())
+ return cached_early_expiration_threshold_;
+
+ int64 threshold;
+ if (!meta_table_.GetValue(kEarlyExpirationThresholdKey, &threshold)) {
+ // Set to a very early non-zero time, so it's before all history, but not
+ // zero to avoid re-retrieval.
+ threshold = 1L;
+ }
+
+ cached_early_expiration_threshold_ = Time::FromInternalValue(threshold);
+ return cached_early_expiration_threshold_;
+}
+
+void HistoryDatabase::UpdateEarlyExpirationThreshold(Time threshold) {
+ meta_table_.SetValue(kEarlyExpirationThresholdKey,
+ threshold.ToInternalValue());
+ cached_early_expiration_threshold_ = threshold;
+}
+
sqlite3* HistoryDatabase::GetDB() {
return db_;
}
diff --git a/chrome/browser/history/history_database.h b/chrome/browser/history/history_database.h
index 61a27d7..55ca0f8 100644
--- a/chrome/browser/history/history_database.h
+++ b/chrome/browser/history/history_database.h
@@ -121,6 +121,12 @@ class HistoryDatabase : public DownloadDatabase,
// visit id wasn't found.
SegmentID GetSegmentID(VisitID visit_id);
+ // Retrieves/Updates early expiration threshold, which specifies the earliest
+ // known point in history that may possibly to contain visits suitable for
+ // early expiration (AUTO_SUBFRAMES).
+ virtual base::Time GetEarlyExpirationThreshold();
+ virtual void UpdateEarlyExpirationThreshold(base::Time threshold);
+
// Drops the starred table and star_id from urls.
bool MigrateFromVersion15ToVersion16();
@@ -156,6 +162,7 @@ class HistoryDatabase : public DownloadDatabase,
SqliteStatementCache* statement_cache_;
MetaTableHelper meta_table_;
+ base::Time cached_early_expiration_threshold_;
DISALLOW_COPY_AND_ASSIGN(HistoryDatabase);
};
diff --git a/chrome/browser/history/redirect_uitest.cc b/chrome/browser/history/redirect_uitest.cc
index 7f3e7be..30b63b2 100644
--- a/chrome/browser/history/redirect_uitest.cc
+++ b/chrome/browser/history/redirect_uitest.cc
@@ -111,7 +111,7 @@ TEST_F(RedirectTest, ClientEmptyReferer) {
break;
}
- EXPECT_EQ(1U, redirects.size());
+ ASSERT_EQ(1U, redirects.size());
EXPECT_EQ(final_url.spec(), redirects[0].spec());
}
diff --git a/chrome/browser/history/visit_database.cc b/chrome/browser/history/visit_database.cc
index 7ce3e62..60d4c5b 100644
--- a/chrome/browser/history/visit_database.cc
+++ b/chrome/browser/history/visit_database.cc
@@ -213,6 +213,35 @@ void VisitDatabase::GetAllVisitsInRange(Time begin_time, Time end_time,
FillVisitVector(*statement, visits);
}
+void VisitDatabase::GetVisitsInRangeForTransition(
+ Time begin_time,
+ Time end_time,
+ int max_results,
+ PageTransition::Type transition,
+ VisitVector* visits) {
+ DCHECK(visits);
+ visits->clear();
+
+ SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(),
+ "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits "
+ "WHERE visit_time >= ? AND visit_time < ? "
+ "AND (transition & ?) == ?"
+ "ORDER BY visit_time LIMIT ?");
+ if (!statement.is_valid())
+ return;
+
+ // See GetVisibleVisitsInRange for more info on how these times are bound.
+ int64 end = end_time.ToInternalValue();
+ statement->bind_int64(0, begin_time.ToInternalValue());
+ statement->bind_int64(1, end ? end : std::numeric_limits<int64>::max());
+ statement->bind_int(2, PageTransition::CORE_MASK);
+ statement->bind_int(3, transition);
+ statement->bind_int64(4,
+ max_results ? max_results : std::numeric_limits<int64>::max());
+
+ FillVisitVector(*statement, visits);
+}
+
void VisitDatabase::GetVisibleVisitsInRange(Time begin_time, Time end_time,
bool most_recent_visit_only,
int max_count,
diff --git a/chrome/browser/history/visit_database.h b/chrome/browser/history/visit_database.h
index 2f73f90..9fa4601 100644
--- a/chrome/browser/history/visit_database.h
+++ b/chrome/browser/history/visit_database.h
@@ -64,6 +64,21 @@ class VisitDatabase {
void GetAllVisitsInRange(base::Time begin_time, base::Time end_time,
int max_results, VisitVector* visits);
+ // Fills all visits with specified transition in the time range [begin, end)
+ // to the given vector. Either time can be is_null(), in which case the times
+ // in that direction are unbounded.
+ //
+ // If |max_results| is non-zero, up to that many results will be returned. If
+ // there are more results than that, the oldest ones will be returned. (This
+ // is used for history expiration.)
+ //
+ // The results will be in increasing order of date.
+ void GetVisitsInRangeForTransition(base::Time begin_time,
+ base::Time end_time,
+ int max_results,
+ PageTransition::Type transition,
+ VisitVector* visits);
+
// Fills all visits in the given time range into the given vector that should
// be user-visible, which excludes things like redirects and subframes. The
// begin time is inclusive, the end time is exclusive. Either time can be
diff --git a/chrome/browser/profile.cc b/chrome/browser/profile.cc
index 0034792..2e37219 100644
--- a/chrome/browser/profile.cc
+++ b/chrome/browser/profile.cc
@@ -33,6 +33,7 @@
#include "chrome/browser/ssl/ssl_host_state.h"
#include "chrome/browser/thumbnail_store.h"
#include "chrome/browser/visitedlink_master.h"
+#include "chrome/browser/visitedlink_event_listener.h"
#include "chrome/browser/webdata/web_data_service.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
@@ -478,6 +479,7 @@ class OffTheRecordProfileImpl : public Profile,
ProfileImpl::ProfileImpl(const FilePath& path)
: path_(path),
+ visited_link_event_listener_(new VisitedLinkEventListener()),
request_context_(NULL),
media_request_context_(NULL),
extensions_request_context_(NULL),
@@ -672,34 +674,11 @@ Profile* ProfileImpl::GetOriginalProfile() {
return this;
}
-static void BroadcastNewHistoryTable(base::SharedMemory* table_memory) {
- if (!table_memory)
- return;
-
- // send to all RenderProcessHosts
- for (RenderProcessHost::iterator i = RenderProcessHost::begin();
- i != RenderProcessHost::end(); i++) {
- if (!i->second->HasConnection())
- continue;
-
- base::SharedMemoryHandle new_table;
- base::ProcessHandle process = i->second->process().handle();
- if (!process) {
- // process can be null if it's started with the --single-process flag.
- process = base::Process::Current().handle();
- }
-
- table_memory->ShareToProcess(process, &new_table);
- IPC::Message* msg = new ViewMsg_VisitedLink_NewTable(new_table);
- i->second->Send(msg);
- }
-}
-
VisitedLinkMaster* ProfileImpl::GetVisitedLinkMaster() {
if (!visited_link_master_.get()) {
scoped_ptr<VisitedLinkMaster> visited_links(
new VisitedLinkMaster(g_browser_process->file_thread(),
- BroadcastNewHistoryTable, this));
+ visited_link_event_listener_.get(), this));
if (!visited_links->Init())
return NULL;
visited_link_master_.swap(visited_links);
diff --git a/chrome/browser/profile.h b/chrome/browser/profile.h
index 302d222a..e16bfe1 100644
--- a/chrome/browser/profile.h
+++ b/chrome/browser/profile.h
@@ -47,6 +47,7 @@ class ThumbnailStore;
class URLRequestContext;
class UserScriptMaster;
class VisitedLinkMaster;
+class VisitedLinkEventListener;
class WebDataService;
class WebKitContext;
@@ -407,6 +408,7 @@ class ProfileImpl : public Profile,
NotificationRegistrar registrar_;
FilePath path_;
+ scoped_ptr<VisitedLinkEventListener> visited_link_event_listener_;
scoped_ptr<VisitedLinkMaster> visited_link_master_;
scoped_refptr<ExtensionsService> extensions_service_;
scoped_refptr<UserScriptMaster> user_script_master_;
diff --git a/chrome/browser/renderer_host/browser_render_process_host.cc b/chrome/browser/renderer_host/browser_render_process_host.cc
index a462862..0736bd7 100644
--- a/chrome/browser/renderer_host/browser_render_process_host.cc
+++ b/chrome/browser/renderer_host/browser_render_process_host.cc
@@ -123,6 +123,62 @@ class RendererMainThread : public base::Thread {
RenderProcess* render_process_;
};
+
+// Size of the buffer after which individual link updates deemed not warranted
+// and the overall update should be used instead.
+static const unsigned kVisitedLinkBufferThreshold = 50;
+
+// This class manages buffering and sending visited link hashes (fingerprints)
+// to renderer based on widget visibility.
+// As opposed to the VisitedLinkEventListener in profile.cc, which coalesces to
+// reduce the rate of messages being send to render processes, this class
+// ensures that the updates occur only when explicitly requested. This is
+// used by BrowserRenderProcessHost to only send Add/Reset link events to the
+// renderers when their tabs are visible.
+class VisitedLinkUpdater {
+ public:
+ VisitedLinkUpdater() : threshold_reached_(false) {}
+
+ void Buffer(const VisitedLinkCommon::Fingerprints& links) {
+ if (threshold_reached_)
+ return;
+
+ if (pending_.size() + links.size() > kVisitedLinkBufferThreshold) {
+ threshold_reached_ = true;
+ // Once the threshold is reached, there's no need to store pending visited
+ // links.
+ pending_.clear();
+ return;
+ }
+
+ pending_.insert(pending_.end(), links.begin(), links.end());
+ }
+
+ void Clear() {
+ pending_.clear();
+ }
+
+ void Update(IPC::Channel::Sender* sender) {
+ if (threshold_reached_) {
+ sender->Send(new ViewMsg_VisitedLink_Reset());
+ threshold_reached_ = false;
+ return;
+ }
+
+ if (pending_.size() == 0)
+ return;
+
+ sender->Send(new ViewMsg_VisitedLink_Add(pending_));
+
+ pending_.clear();
+ }
+
+ private:
+ bool threshold_reached_;
+ VisitedLinkCommon::Fingerprints pending_;
+};
+
+
// Used for a View_ID where the renderer has not been attached yet
const int32 kInvalidViewID = -1;
@@ -155,6 +211,8 @@ BrowserRenderProcessHost::BrowserRenderProcessHost(Profile* profile)
SetProcessID(next_pid);
}
+ visited_link_updater_.reset(new VisitedLinkUpdater());
+
// Note: When we create the BrowserRenderProcessHost, it's technically
// backgrounded, because it has no visible listeners. But the process
// doesn't actually exist yet, so we'll Background it later, after
@@ -456,6 +514,7 @@ void BrowserRenderProcessHost::WidgetRestored() {
// Verify we were properly backgrounded.
DCHECK(backgrounded_ == (visible_widgets_ == 0));
visible_widgets_++;
+ visited_link_updater_->Update(this);
SetBackgrounded(false);
}
@@ -487,6 +546,20 @@ void BrowserRenderProcessHost::AddWord(const std::wstring& word) {
#endif // !defined(OS_WIN)
}
+void BrowserRenderProcessHost::AddVisitedLinks(
+ const VisitedLinkCommon::Fingerprints& links) {
+ visited_link_updater_->Buffer(links);
+ if (visible_widgets_ == 0)
+ return;
+
+ visited_link_updater_->Update(this);
+}
+
+void BrowserRenderProcessHost::ResetVisitedLinks() {
+ visited_link_updater_->Clear();
+ Send(new ViewMsg_VisitedLink_Reset());
+}
+
base::ProcessHandle BrowserRenderProcessHost::GetRendererProcessHandle() {
if (run_renderer_in_process())
return base::Process::Current().handle();
diff --git a/chrome/browser/renderer_host/browser_render_process_host.h b/chrome/browser/renderer_host/browser_render_process_host.h
index d92d9fd..5230b5e 100644
--- a/chrome/browser/renderer_host/browser_render_process_host.h
+++ b/chrome/browser/renderer_host/browser_render_process_host.h
@@ -26,6 +26,7 @@ class GURL;
class RendererMainThread;
class RenderWidgetHelper;
class TabContents;
+class VisitedLinkUpdater;
namespace gfx {
class Size;
@@ -64,6 +65,8 @@ class BrowserRenderProcessHost : public RenderProcessHost,
virtual void WidgetRestored();
virtual void WidgetHidden();
virtual void AddWord(const std::wstring& word);
+ virtual void AddVisitedLinks(const VisitedLinkCommon::Fingerprints& links);
+ virtual void ResetVisitedLinks();
virtual bool FastShutdownIfPossible();
virtual bool SendWithTimeout(IPC::Message* msg, int timeout_ms);
virtual TransportDIB* GetTransportDIB(TransportDIB::Id dib_id);
@@ -88,6 +91,8 @@ class BrowserRenderProcessHost : public RenderProcessHost,
const NotificationDetails& details);
private:
+ friend class VisitRelayingRenderProcessHost;
+
// Control message handlers.
void OnPageContents(const GURL& url, int32 page_id,
const std::wstring& contents);
@@ -154,6 +159,9 @@ class BrowserRenderProcessHost : public RenderProcessHost,
// Used in single-process mode.
scoped_ptr<RendererMainThread> in_process_renderer_;
+ // Buffer visited links and send them to to renderer.
+ scoped_ptr<VisitedLinkUpdater> visited_link_updater_;
+
// True iff the renderer is a child of a zygote process.
bool zygote_child_;
diff --git a/chrome/browser/renderer_host/mock_render_process_host.cc b/chrome/browser/renderer_host/mock_render_process_host.cc
index 4d4eda2..cb276b7 100644
--- a/chrome/browser/renderer_host/mock_render_process_host.cc
+++ b/chrome/browser/renderer_host/mock_render_process_host.cc
@@ -51,6 +51,13 @@ void MockRenderProcessHost::WidgetHidden() {
void MockRenderProcessHost::AddWord(const std::wstring& word) {
}
+void MockRenderProcessHost::AddVisitedLinks(
+ const VisitedLinkCommon::Fingerprints& links) {
+}
+
+void MockRenderProcessHost::ResetVisitedLinks() {
+}
+
bool MockRenderProcessHost::FastShutdownIfPossible() {
return false;
}
diff --git a/chrome/browser/renderer_host/mock_render_process_host.h b/chrome/browser/renderer_host/mock_render_process_host.h
index 7975636..a4c1cef 100644
--- a/chrome/browser/renderer_host/mock_render_process_host.h
+++ b/chrome/browser/renderer_host/mock_render_process_host.h
@@ -39,6 +39,9 @@ class MockRenderProcessHost : public RenderProcessHost {
virtual void WidgetRestored();
virtual void WidgetHidden();
virtual void AddWord(const std::wstring& word);
+ virtual void AddVisitedLinks(
+ const VisitedLinkCommon::Fingerprints& visited_links);
+ virtual void ResetVisitedLinks();
virtual bool FastShutdownIfPossible();
virtual bool SendWithTimeout(IPC::Message* msg, int timeout_ms);
virtual TransportDIB* GetTransportDIB(TransportDIB::Id dib_id);
diff --git a/chrome/browser/renderer_host/render_process_host.h b/chrome/browser/renderer_host/render_process_host.h
index e661a0f..93d94cdc 100644
--- a/chrome/browser/renderer_host/render_process_host.h
+++ b/chrome/browser/renderer_host/render_process_host.h
@@ -13,6 +13,7 @@
#include "base/scoped_ptr.h"
#include "chrome/common/ipc_sync_channel.h"
#include "chrome/common/transport_dib.h"
+#include "chrome/common/visitedlink_common.h"
class Profile;
@@ -138,6 +139,14 @@ class RenderProcessHost : public IPC::Channel::Sender,
// Add a word in the spellchecker.
virtual void AddWord(const std::wstring& word) = 0;
+ // Notify the renderer that a link was visited.
+ virtual void AddVisitedLinks(
+ const VisitedLinkCommon::Fingerprints& links) = 0;
+
+ // Clear internal visited links buffer and ask the renderer to update link
+ // coloring state for all of its links.
+ virtual void ResetVisitedLinks() = 0;
+
// Try to shutdown the associated renderer process as fast as possible.
// If this renderer has any RenderViews with unload handlers, then this
// function does nothing. The current implementation uses TerminateProcess.
diff --git a/chrome/browser/renderer_host/test_render_view_host.h b/chrome/browser/renderer_host/test_render_view_host.h
index 008ab19..39bfce6 100644
--- a/chrome/browser/renderer_host/test_render_view_host.h
+++ b/chrome/browser/renderer_host/test_render_view_host.h
@@ -156,6 +156,11 @@ class TestRenderViewHostFactory : public RenderViewHostFactory {
RenderViewHostFactory::UnregisterFactory();
}
+ virtual void set_render_process_host_factory(
+ RenderProcessHostFactory* rph_factory) {
+ render_process_host_factory_ = rph_factory;
+ }
+
virtual RenderViewHost* CreateRenderViewHost(
SiteInstance* instance,
RenderViewHostDelegate* delegate,
diff --git a/chrome/browser/tab_contents/tab_contents.cc b/chrome/browser/tab_contents/tab_contents.cc
index 4508d34..d303c6a 100644
--- a/chrome/browser/tab_contents/tab_contents.cc
+++ b/chrome/browser/tab_contents/tab_contents.cc
@@ -1236,16 +1236,6 @@ void TabContents::DidNavigateAnyFramePostCommit(
// reload the page to stop blocking.
suppress_javascript_messages_ = false;
- // Update history. Note that this needs to happen after the entry is complete,
- // which WillNavigate[Main,Sub]Frame will do before this function is called.
- if (params.should_update_history) {
- // Most of the time, the displayURL matches the loaded URL, but for about:
- // URLs, we use a data: URL as the real value. We actually want to save
- // the about: URL to the history db and keep the data: URL hidden. This is
- // what the TabContents' URL getter does.
- UpdateHistoryForNavigation(GetURL(), details, params);
- }
-
// Notify the password manager of the navigation or form submit.
// TODO(brettw) bug 1343111: Password manager stuff in here needs to be
// cleaned up and covered by tests.
@@ -1576,8 +1566,20 @@ void TabContents::DidNavigate(RenderViewHost* rvh,
contents_mime_type_ = params.contents_mime_type;
NavigationController::LoadCommittedDetails details;
- if (!controller_.RendererDidNavigate(params, &details))
- return; // No navigation happened.
+ bool did_navigate = controller_.RendererDidNavigate(params, &details);
+
+ // Update history. Note that this needs to happen after the entry is complete,
+ // which WillNavigate[Main,Sub]Frame will do before this function is called.
+ if (params.should_update_history) {
+ // Most of the time, the displayURL matches the loaded URL, but for about:
+ // URLs, we use a data: URL as the real value. We actually want to save
+ // the about: URL to the history db and keep the data: URL hidden. This is
+ // what the TabContents' URL getter does.
+ UpdateHistoryForNavigation(GetURL(), details, params);
+ }
+
+ if (!did_navigate)
+ return; // No navigation happened.
// DO NOT ADD MORE STUFF TO THIS FUNCTION! Your component should either listen
// for the appropriate notification (best) or you can add it to
diff --git a/chrome/browser/visitedlink_event_listener.cc b/chrome/browser/visitedlink_event_listener.cc
new file mode 100644
index 0000000..6b27929
--- /dev/null
+++ b/chrome/browser/visitedlink_event_listener.cc
@@ -0,0 +1,68 @@
+// 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 "chrome/browser/visitedlink_event_listener.h"
+
+#include "base/shared_memory.h"
+#include "chrome/browser/renderer_host/render_process_host.h"
+#include "chrome/common/render_messages.h"
+
+using base::Time;
+using base::TimeDelta;
+
+// The amount of time we wait to accumulate visited link additions.
+static const int kCommitIntervalMs = 100;
+
+void VisitedLinkEventListener::NewTable(base::SharedMemory* table_memory) {
+ if (!table_memory)
+ return;
+
+ // Send to all RenderProcessHosts.
+ for (RenderProcessHost::iterator i = RenderProcessHost::begin();
+ i != RenderProcessHost::end(); ++i) {
+ if (!i->second->HasConnection())
+ continue;
+
+ base::SharedMemoryHandle new_table;
+ base::ProcessHandle process = i->second->process().handle();
+ if (!process) {
+ // process can be null if it's started with the --single-process flag.
+ process = base::Process::Current().handle();
+ }
+
+ table_memory->ShareToProcess(process, &new_table);
+ i->second->Send(new ViewMsg_VisitedLink_NewTable(new_table));
+ }
+}
+
+void VisitedLinkEventListener::Add(VisitedLinkMaster::Fingerprint fingerprint) {
+ pending_visited_links_.push_back(fingerprint);
+
+ if (!coalesce_timer_.IsRunning()) {
+ coalesce_timer_.Start(
+ TimeDelta::FromMilliseconds(kCommitIntervalMs), this,
+ &VisitedLinkEventListener::CommitVisitedLinks);
+ }
+}
+
+void VisitedLinkEventListener::Reset() {
+ pending_visited_links_.clear();
+ coalesce_timer_.Stop();
+
+ // Send to all RenderProcessHosts.
+ for (RenderProcessHost::iterator i = RenderProcessHost::begin();
+ i != RenderProcessHost::end(); ++i) {
+ i->second->ResetVisitedLinks();
+ }
+}
+
+void VisitedLinkEventListener::CommitVisitedLinks() {
+ // Send to all RenderProcessHosts.
+ for (RenderProcessHost::iterator i = RenderProcessHost::begin();
+ i != RenderProcessHost::end(); ++i) {
+ i->second->AddVisitedLinks(pending_visited_links_);
+ }
+
+ pending_visited_links_.clear();
+}
diff --git a/chrome/browser/visitedlink_event_listener.h b/chrome/browser/visitedlink_event_listener.h
new file mode 100644
index 0000000..7d51d1e
--- /dev/null
+++ b/chrome/browser/visitedlink_event_listener.h
@@ -0,0 +1,37 @@
+// 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.
+
+// VisitedLinkEventListener broadcasts link coloring database updates to all
+// processes. It also coalesces the updates to avoid excessive broadcasting of
+// messages to the renderers.
+
+#ifndef VISITEDLINK_EVENT_LISTENER_H_
+#define VISITEDLINK_EVENT_LISTENER_H_
+
+#include "base/timer.h"
+#include "chrome/browser/visitedlink_master.h"
+
+namespace base {
+class SharedMemory;
+}
+
+class VisitedLinkEventListener : public VisitedLinkMaster::Listener {
+ public:
+ VisitedLinkEventListener() {}
+ virtual ~VisitedLinkEventListener() {}
+
+ virtual void NewTable(base::SharedMemory* table_memory);
+ virtual void Add(VisitedLinkMaster::Fingerprint fingerprint);
+ virtual void Reset();
+
+ private:
+ void CommitVisitedLinks();
+
+ base::OneShotTimer<VisitedLinkEventListener> coalesce_timer_;
+ VisitedLinkCommon::Fingerprints pending_visited_links_;
+
+ DISALLOW_COPY_AND_ASSIGN(VisitedLinkEventListener);
+};
+
+#endif // VISITEDLINK_EVENT_LISTENER_H_
diff --git a/chrome/browser/visitedlink_master.cc b/chrome/browser/visitedlink_master.cc
index 54a3a22..41a97a1 100644
--- a/chrome/browser/visitedlink_master.cc
+++ b/chrome/browser/visitedlink_master.cc
@@ -199,24 +199,24 @@ class VisitedLinkMaster::TableBuilder : public HistoryService::URLEnumerator,
uint8 salt_[LINK_SALT_LENGTH];
// Stores the fingerprints we computed on the background thread.
- std::vector<VisitedLinkMaster::Fingerprint> fingerprints_;
+ VisitedLinkCommon::Fingerprints fingerprints_;
};
// VisitedLinkMaster ----------------------------------------------------------
VisitedLinkMaster::VisitedLinkMaster(base::Thread* file_thread,
- PostNewTableEvent* poster,
+ Listener* listener,
Profile* profile) {
- InitMembers(file_thread, poster, profile);
+ InitMembers(file_thread, listener, profile);
}
VisitedLinkMaster::VisitedLinkMaster(base::Thread* file_thread,
- PostNewTableEvent* poster,
+ Listener* listener,
HistoryService* history_service,
bool suppress_rebuild,
const FilePath& filename,
int32 default_table_size) {
- InitMembers(file_thread, poster, NULL);
+ InitMembers(file_thread, listener, NULL);
database_name_override_ = filename;
table_size_override_ = default_table_size;
@@ -236,14 +236,16 @@ VisitedLinkMaster::~VisitedLinkMaster() {
}
void VisitedLinkMaster::InitMembers(base::Thread* file_thread,
- PostNewTableEvent* poster,
+ Listener* listener,
Profile* profile) {
+ DCHECK(listener);
+
if (file_thread)
file_thread_ = file_thread->message_loop();
else
file_thread_ = NULL;
- post_new_table_event_ = poster;
+ listener_ = listener;
file_ = NULL;
shared_memory_ = NULL;
shared_memory_serial_ = 0;
@@ -348,6 +350,8 @@ void VisitedLinkMaster::DeleteAllURLs() {
// us, otherwise, schedule writing the new table to disk ourselves.
if (!ResizeTableIfNecessary())
WriteFullTable();
+
+ listener_->Reset();
}
void VisitedLinkMaster::DeleteURLs(const std::set<GURL>& urls) {
@@ -356,6 +360,8 @@ void VisitedLinkMaster::DeleteURLs(const std::set<GURL>& urls) {
if (urls.empty())
return;
+ listener_->Reset();
+
if (table_builder_) {
// A rebuild is in progress, save this deletion in the temporary list so
// it can be added once rebuild is complete.
@@ -411,6 +417,8 @@ VisitedLinkMaster::Hash VisitedLinkMaster::AddFingerprint(
// End of probe sequence found, insert here.
hash_table_[cur_hash] = fingerprint;
used_items_++;
+ // Notify listener that a new visited link was added.
+ listener_->Add(fingerprint);
return cur_hash;
}
@@ -805,7 +813,7 @@ void VisitedLinkMaster::ResizeTable(int32 new_size) {
// Send an update notification to all child processes so they read the new
// table.
- post_new_table_event_(shared_memory_);
+ listener_->NewTable(shared_memory_);
#ifndef NDEBUG
DebugValidate();
@@ -908,7 +916,7 @@ void VisitedLinkMaster::OnTableRebuildComplete(
deleted_since_rebuild_.clear();
// Send an update notification to all child processes.
- post_new_table_event_(shared_memory_);
+ listener_->NewTable(shared_memory_);
WriteFullTable();
}
diff --git a/chrome/browser/visitedlink_master.h b/chrome/browser/visitedlink_master.h
index d814c83..ad7be42 100644
--- a/chrome/browser/visitedlink_master.h
+++ b/chrome/browser/visitedlink_master.h
@@ -35,12 +35,30 @@ class Thread;
// operations are pending on another thread.
class VisitedLinkMaster : public VisitedLinkCommon {
public:
- typedef void (PostNewTableEvent)(base::SharedMemory*);
+ // Listens to the link coloring database events. The master is given this
+ // event as a constructor argument and dispatches events using it.
+ class Listener {
+ public:
+ virtual ~Listener() {}
+
+ // Called when link coloring database has been created or replaced. The
+ // argument is the new table handle.
+ virtual void NewTable(base::SharedMemory*) = 0;
+
+ // Called when new link has been added. The argument is the fingerprint
+ // (hash) of the link.
+ virtual void Add(Fingerprint fingerprint) = 0;
+
+ // Called when link coloring state has been reset. This may occur when
+ // entire or parts of history were deleted.
+ virtual void Reset() = 0;
+ };
// The |file_thread| may be NULL, in which case write operations will be
// synchronous.
+ // The |listener| may not be NULL.
VisitedLinkMaster(base::Thread* file_thread,
- PostNewTableEvent* poster,
+ Listener* listener,
Profile* profile);
// In unit test mode, we allow the caller to optionally specify the database
@@ -59,7 +77,7 @@ class VisitedLinkMaster : public VisitedLinkCommon {
// history if the file can't be loaded. This should generally be set for
// testing except when you want to test the rebuild process explicitly.
VisitedLinkMaster(base::Thread* file_thread,
- PostNewTableEvent* poster,
+ Listener* listener,
HistoryService* history_service,
bool suppress_rebuild,
const FilePath& filename,
@@ -150,7 +168,7 @@ class VisitedLinkMaster : public VisitedLinkCommon {
// Backend for the constructors initializing the members.
void InitMembers(base::Thread* file_thread,
- PostNewTableEvent* poster,
+ Listener* listener,
Profile* profile);
// If a rebuild is in progress, we save the URL in the temporary list.
@@ -294,7 +312,7 @@ class VisitedLinkMaster : public VisitedLinkCommon {
return hash - 1;
}
- PostNewTableEvent* post_new_table_event_;
+ Listener* listener_;
#ifndef NDEBUG
// Indicates whether any asynchronous operation has ever been completed.
diff --git a/chrome/browser/visitedlink_perftest.cc b/chrome/browser/visitedlink_perftest.cc
index cb2da7f..c1c6afa 100644
--- a/chrome/browser/visitedlink_perftest.cc
+++ b/chrome/browser/visitedlink_perftest.cc
@@ -30,10 +30,20 @@ GURL TestURL(const char* prefix, int i) {
return GURL(StringPrintf("%s%d", prefix, i));
}
-// we have no slaves, so this broadcase is a NOP
-VisitedLinkMaster::PostNewTableEvent DummyBroadcastNewTableEvent;
-void DummyBroadcastNewTableEvent(base::SharedMemory *table) {
-}
+// We have no slaves, so all methods on this listener are a no-ops.
+class DummyVisitedLinkEventListener : public VisitedLinkMaster::Listener {
+ public:
+ DummyVisitedLinkEventListener() {}
+ virtual void NewTable(base::SharedMemory* table) {}
+ virtual void Add(VisitedLinkCommon::Fingerprint) {}
+ virtual void Reset() {}
+
+ static DummyVisitedLinkEventListener* GetInstance() {
+ static DummyVisitedLinkEventListener instance;
+ return &instance;
+ }
+};
+
// Call at the beginning of the test to retrieve the database name.
void InitDBName(std::wstring* db_name) {
@@ -80,8 +90,8 @@ class VisitedLink : public testing::Test {
// useful to make another set of tests to test these things in isolation.
TEST_F(VisitedLink, TestAddAndQuery) {
// init
- VisitedLinkMaster master(NULL, DummyBroadcastNewTableEvent, NULL, true,
- FilePath(db_name_), 0);
+ VisitedLinkMaster master(NULL, DummyVisitedLinkEventListener::GetInstance(),
+ NULL, true, FilePath(db_name_), 0);
ASSERT_TRUE(master.Init());
PerfTimeLogger timer("Visited_link_add_and_query");
@@ -111,8 +121,8 @@ TEST_F(VisitedLink, TestLoad) {
{
PerfTimeLogger table_initialization_timer("Table_initialization");
- VisitedLinkMaster master(NULL, DummyBroadcastNewTableEvent, NULL, true,
- FilePath(db_name_), 0);
+ VisitedLinkMaster master(NULL, DummyVisitedLinkEventListener::GetInstance(),
+ NULL, true, FilePath(db_name_), 0);
// time init with empty table
PerfTimeLogger initTimer("Empty_visited_link_init");
@@ -151,8 +161,12 @@ TEST_F(VisitedLink, TestLoad) {
{
PerfTimer cold_timer;
- VisitedLinkMaster master(NULL, DummyBroadcastNewTableEvent, NULL, true,
- FilePath(db_name_), 0);
+ VisitedLinkMaster master(NULL,
+ DummyVisitedLinkEventListener::GetInstance(),
+ NULL,
+ true,
+ FilePath(db_name_),
+ 0);
bool success = master.Init();
TimeDelta elapsed = cold_timer.Elapsed();
ASSERT_TRUE(success);
@@ -164,8 +178,12 @@ TEST_F(VisitedLink, TestLoad) {
{
PerfTimer hot_timer;
- VisitedLinkMaster master(NULL, DummyBroadcastNewTableEvent, NULL, true,
- FilePath(db_name_), 0);
+ VisitedLinkMaster master(NULL,
+ DummyVisitedLinkEventListener::GetInstance(),
+ NULL,
+ true,
+ FilePath(db_name_),
+ 0);
bool success = master.Init();
TimeDelta elapsed = hot_timer.Elapsed();
ASSERT_TRUE(success);
diff --git a/chrome/browser/visitedlink_unittest.cc b/chrome/browser/visitedlink_unittest.cc
index 5c4ddce..45e620a 100644
--- a/chrome/browser/visitedlink_unittest.cc
+++ b/chrome/browser/visitedlink_unittest.cc
@@ -13,6 +13,10 @@
#include "base/shared_memory.h"
#include "base/string_util.h"
#include "chrome/browser/visitedlink_master.h"
+#include "chrome/browser/visitedlink_event_listener.h"
+#include "chrome/browser/renderer_host/browser_render_process_host.h"
+#include "chrome/browser/renderer_host/test_render_view_host.h"
+#include "chrome/common/render_messages.h"
#include "chrome/renderer/visitedlink_slave.h"
#include "googleurl/src/gurl.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -31,19 +35,39 @@ GURL TestURL(int i) {
std::vector<VisitedLinkSlave*> g_slaves;
-VisitedLinkMaster::PostNewTableEvent SynchronousBroadcastNewTableEvent;
-void SynchronousBroadcastNewTableEvent(base::SharedMemory* table) {
- if (table) {
- for (std::vector<VisitedLinkSlave>::size_type i = 0;
- i < g_slaves.size(); i++) {
- base::SharedMemoryHandle new_handle = base::SharedMemory::NULLHandle();
- table->ShareToProcess(base::GetCurrentProcessHandle(), &new_handle);
- g_slaves[i]->Init(new_handle);
+} // namespace
+
+class TrackingVisitedLinkEventListener : public VisitedLinkMaster::Listener {
+ public:
+ TrackingVisitedLinkEventListener()
+ : reset_count_(0),
+ add_count_(0) {}
+
+ virtual void NewTable(base::SharedMemory* table) {
+ if (table) {
+ for (std::vector<VisitedLinkSlave>::size_type i = 0;
+ i < g_slaves.size(); i++) {
+ base::SharedMemoryHandle new_handle = base::SharedMemory::NULLHandle();
+ table->ShareToProcess(base::GetCurrentProcessHandle(), &new_handle);
+ g_slaves[i]->Init(new_handle);
+ }
}
}
-}
+ virtual void Add(VisitedLinkCommon::Fingerprint) { add_count_++; }
+ virtual void Reset() { reset_count_++; }
-} // namespace
+ void SetUp() {
+ reset_count_ = 0;
+ add_count_ = 0;
+ }
+
+ int reset_count() const { return reset_count_; }
+ int add_count() const { return add_count_; }
+
+ private:
+ int reset_count_;
+ int add_count_;
+};
class VisitedLinkTest : public testing::Test {
protected:
@@ -60,10 +84,9 @@ class VisitedLinkTest : public testing::Test {
// the VisitedLinkMaster constructor.
bool InitVisited(int initial_size, bool suppress_rebuild) {
// Initialize the visited link system.
- master_.reset(new VisitedLinkMaster(NULL,
- SynchronousBroadcastNewTableEvent,
- history_service_, suppress_rebuild,
- visited_file_, initial_size));
+ master_.reset(new VisitedLinkMaster(NULL, &listener_, history_service_,
+ suppress_rebuild, visited_file_,
+ initial_size));
return master_->Init();
}
@@ -138,6 +161,7 @@ class VisitedLinkTest : public testing::Test {
file_util::CreateDirectory(history_dir_);
visited_file_ = history_dir_.Append(FILE_PATH_LITERAL("VisitedLinks"));
+ listener_.SetUp();
}
virtual void TearDown() {
@@ -153,6 +177,7 @@ class VisitedLinkTest : public testing::Test {
scoped_ptr<VisitedLinkMaster> master_;
scoped_refptr<HistoryService> history_service_;
+ TrackingVisitedLinkEventListener listener_;
};
// This test creates and reads some databases to make sure the data is
@@ -373,3 +398,316 @@ TEST_F(VisitedLinkTest, Rebuild) {
// Make sure the extra one was *not* written (Reload won't test this).
EXPECT_FALSE(master_->IsVisited(TestURL(g_test_count)));
}
+
+TEST_F(VisitedLinkTest, Listener) {
+ ASSERT_TRUE(InitHistory());
+ ASSERT_TRUE(InitVisited(0, true));
+
+ // Add test URLs.
+ for (int i = 0; i < g_test_count; i++) {
+ master_->AddURL(TestURL(i));
+ ASSERT_EQ(i + 1, master_->GetUsedCount());
+ }
+
+ std::set<GURL> deleted_urls;
+ deleted_urls.insert(TestURL(0));
+ // Delete an URL.
+ master_->DeleteURLs(deleted_urls);
+ // ... and all of the remaining ones.
+ master_->DeleteAllURLs();
+
+ // Verify that VisitedLinkMaster::Listener::Add was called for each added URL.
+ EXPECT_EQ(g_test_count, listener_.add_count());
+ // Verify that VisitedLinkMaster::Listener::Reset was called both when one and
+ // all URLs are deleted.
+ EXPECT_EQ(2, listener_.reset_count());
+}
+
+class VisitCountingProfile : public TestingProfile {
+ public:
+ explicit VisitCountingProfile(VisitedLinkEventListener* event_listener)
+ : add_count_(0),
+ add_event_count_(0),
+ reset_event_count_(0),
+ event_listener_(event_listener) {}
+
+ virtual VisitedLinkMaster* GetVisitedLinkMaster() {
+ if (!visited_link_master_.get()) {
+ visited_link_master_.reset(
+ new VisitedLinkMaster(NULL, event_listener_, this));
+ visited_link_master_->Init();
+ }
+ return visited_link_master_.get();
+ }
+
+ void CountAddEvent(int by) {
+ add_count_ += by;
+ add_event_count_++;
+ }
+
+ void CountResetEvent() {
+ reset_event_count_++;
+ }
+
+ VisitedLinkMaster* master() const { return visited_link_master_.get(); }
+ int add_count() const { return add_count_; }
+ int add_event_count() const { return add_event_count_; }
+ int reset_event_count() const { return reset_event_count_; }
+
+ private:
+ int add_count_;
+ int add_event_count_;
+ int reset_event_count_;
+ VisitedLinkEventListener* event_listener_;
+ scoped_ptr<VisitedLinkMaster> visited_link_master_;
+};
+
+class VisitCountingRenderProcessHost : public MockRenderProcessHost {
+ public:
+ explicit VisitCountingRenderProcessHost(Profile* profile)
+ : MockRenderProcessHost(profile) {}
+
+ virtual void AddVisitedLinks(
+ const VisitedLinkCommon::Fingerprints& visited_links) {
+ VisitCountingProfile* counting_profile =
+ static_cast<VisitCountingProfile*>(profile());
+ counting_profile->CountAddEvent(visited_links.size());
+ }
+ virtual void ResetVisitedLinks() {
+ VisitCountingProfile* counting_profile =
+ static_cast<VisitCountingProfile*>(profile());
+ counting_profile->CountResetEvent();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(VisitCountingRenderProcessHost);
+};
+
+// Stub out as little as possible, borrowing from MockRenderProcessHost.
+class VisitRelayingRenderProcessHost : public BrowserRenderProcessHost {
+ public:
+ explicit VisitRelayingRenderProcessHost(Profile* profile)
+ : BrowserRenderProcessHost(profile) {
+ static int prev_id = 0;
+ SetProcessID(++prev_id);
+ }
+ virtual ~VisitRelayingRenderProcessHost() {
+ RemoveFromList();
+ }
+
+ virtual bool Init() { return true; }
+
+ virtual void CancelResourceRequests(int render_widget_id) {
+ }
+
+ virtual void CrossSiteClosePageACK(int new_render_process_host_id,
+ int new_request_id) {
+ }
+
+ virtual bool WaitForPaintMsg(int render_widget_id,
+ const base::TimeDelta& max_delay,
+ IPC::Message* msg) {
+ return false;
+ }
+
+ virtual bool Send(IPC::Message* msg) {
+ VisitCountingProfile* counting_profile =
+ static_cast<VisitCountingProfile*>(profile());
+
+ if (msg->type() == ViewMsg_VisitedLink_Add::ID)
+ counting_profile->CountAddEvent(1);
+ else if (msg->type() == ViewMsg_VisitedLink_Reset::ID)
+ counting_profile->CountResetEvent();
+
+ delete msg;
+ return true;
+ }
+
+ virtual void SetBackgrounded(bool backgrounded) {
+ backgrounded_ = backgrounded;
+ }
+
+ private:
+ int add_relay_count_;
+ int reset_relay_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(VisitRelayingRenderProcessHost);
+};
+
+class VisitedLinkRenderProcessHostFactory
+ : public MockRenderProcessHostFactory {
+ public:
+ VisitedLinkRenderProcessHostFactory()
+ : MockRenderProcessHostFactory(),
+ relay_mode_(false) {}
+ virtual RenderProcessHost* CreateRenderProcessHost(Profile* profile) const {
+ if (relay_mode_)
+ return new VisitRelayingRenderProcessHost(profile);
+ else
+ return new VisitCountingRenderProcessHost(profile);
+ }
+
+ void set_relay_mode(bool mode) { relay_mode_ = mode; }
+
+ private:
+ bool relay_mode_;
+
+ DISALLOW_COPY_AND_ASSIGN(VisitedLinkRenderProcessHostFactory);
+};
+
+class VisitedLinkEventsTest : public RenderViewHostTestHarness {
+ public:
+ VisitedLinkEventsTest() : RenderViewHostTestHarness() {}
+ virtual void SetFactoryMode() {}
+ virtual void SetUp() {
+ SetFactoryMode();
+ event_listener_.reset(new VisitedLinkEventListener());
+ rvh_factory_.set_render_process_host_factory(&vc_rph_factory_);
+ profile_.reset(new VisitCountingProfile(event_listener_.get()));
+ RenderViewHostTestHarness::SetUp();
+ }
+
+ VisitCountingProfile* profile() const {
+ return static_cast<VisitCountingProfile*>(profile_.get());
+ }
+
+ void WaitForCoalescense() {
+ // Let the timer fire.
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ new MessageLoop::QuitTask(), 110);
+ MessageLoop::current()->Run();
+ }
+
+ protected:
+ VisitedLinkRenderProcessHostFactory vc_rph_factory_;
+
+ private:
+ scoped_ptr<VisitedLinkEventListener> event_listener_;
+
+ DISALLOW_COPY_AND_ASSIGN(VisitedLinkEventsTest);
+};
+
+class VisitedLinkRelayTest : public VisitedLinkEventsTest {
+ public:
+ virtual void SetFactoryMode() { vc_rph_factory_.set_relay_mode(true); }
+};
+
+TEST_F(VisitedLinkEventsTest, Coalescense) {
+ // add some URLs to master.
+ VisitedLinkMaster* master = profile_->GetVisitedLinkMaster();
+ // Add a few URLs.
+ master->AddURL(GURL("http://acidtests.org/"));
+ master->AddURL(GURL("http://google.com/"));
+ master->AddURL(GURL("http://chromium.org/"));
+ // Just for kicks, add a duplicate URL. This shouldn't increase the resulting
+ master->AddURL(GURL("http://acidtests.org/"));
+
+ // Make sure that coalescing actually occurs. There should be no links or
+ // events received by the renderer.
+ EXPECT_EQ(0, profile()->add_count());
+ EXPECT_EQ(0, profile()->add_event_count());
+
+ WaitForCoalescense();
+
+ // We now should have 3 entries added in 1 event.
+ EXPECT_EQ(3, profile()->add_count());
+ EXPECT_EQ(1, profile()->add_event_count());
+
+ // Test whether the coalescing continues by adding a few more URLs.
+ master->AddURL(GURL("http://google.com/chrome/"));
+ master->AddURL(GURL("http://webkit.org/"));
+ master->AddURL(GURL("http://acid3.acidtests.org/"));
+
+ WaitForCoalescense();
+
+ // We should have 6 entries added in 2 events.
+ EXPECT_EQ(6, profile()->add_count());
+ EXPECT_EQ(2, profile()->add_event_count());
+
+ // Test whether duplicate entries produce add events.
+ master->AddURL(GURL("http://acidtests.org/"));
+
+ WaitForCoalescense();
+
+ // We should have no change in results.
+ EXPECT_EQ(6, profile()->add_count());
+ EXPECT_EQ(2, profile()->add_event_count());
+
+ // Ensure that the coalescing does not resume after resetting.
+ master->AddURL(GURL("http://build.chromium.org/"));
+ master->DeleteAllURLs();
+
+ WaitForCoalescense();
+
+ // We should have no change in results except for one new reset event.
+ EXPECT_EQ(6, profile()->add_count());
+ EXPECT_EQ(2, profile()->add_event_count());
+ EXPECT_EQ(1, profile()->reset_event_count());
+}
+
+TEST_F(VisitedLinkRelayTest, Basics) {
+ VisitedLinkMaster* master = profile_->GetVisitedLinkMaster();
+ // Add a few URLs.
+ master->AddURL(GURL("http://acidtests.org/"));
+ master->AddURL(GURL("http://google.com/"));
+ master->AddURL(GURL("http://chromium.org/"));
+
+ WaitForCoalescense();
+
+ // We now should have 1 add event.
+ EXPECT_EQ(1, profile()->add_event_count());
+ EXPECT_EQ(0, profile()->reset_event_count());
+
+ master->DeleteAllURLs();
+
+ WaitForCoalescense();
+
+ // We should have no change in add results, plus one new reset event.
+ EXPECT_EQ(1, profile()->add_event_count());
+ EXPECT_EQ(1, profile()->reset_event_count());
+}
+
+TEST_F(VisitedLinkRelayTest, TabVisibility) {
+ VisitedLinkMaster* master = profile_->GetVisitedLinkMaster();
+
+ // Simulate tab becoming inactive.
+ rvh()->WasHidden();
+
+ // Add a few URLs.
+ master->AddURL(GURL("http://acidtests.org/"));
+ master->AddURL(GURL("http://google.com/"));
+ master->AddURL(GURL("http://chromium.org/"));
+
+ WaitForCoalescense();
+
+ // We shouldn't have any events.
+ EXPECT_EQ(0, profile()->add_event_count());
+ EXPECT_EQ(0, profile()->reset_event_count());
+
+ // Simulate the tab becoming active.
+ rvh()->WasRestored();
+
+ // We should now have 3 add events, still no reset events.
+ EXPECT_EQ(1, profile()->add_event_count());
+ EXPECT_EQ(0, profile()->reset_event_count());
+
+ // Deactivate the tab again.
+ rvh()->WasHidden();
+
+ // Add a bunch of URLs (over 50) to exhaust the link event buffer.
+ for (int i = 0; i < 100; i++)
+ master->AddURL(TestURL(i));
+
+ WaitForCoalescense();
+
+ // Again, no change in events until tab is active.
+ EXPECT_EQ(1, profile()->add_event_count());
+ EXPECT_EQ(0, profile()->reset_event_count());
+
+ // Activate the tab.
+ rvh()->WasRestored();
+
+ // We should have only one more reset event.
+ EXPECT_EQ(1, profile()->add_event_count());
+ EXPECT_EQ(1, profile()->reset_event_count());
+}