diff options
author | dglazkov@chromium.org <dglazkov@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-03 17:38:39 +0000 |
---|---|---|
committer | dglazkov@chromium.org <dglazkov@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-03 17:38:39 +0000 |
commit | 3e90d4a00082fd985ab610cd2faff84b5c597a4e (patch) | |
tree | 4d2f0da17f4fb68bab53381c9f49351380a24127 /chrome/browser | |
parent | fd694982d0e1ee8b3e1753d9c48b01c1a633ac27 (diff) | |
download | chromium_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')
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()); +} |