• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <cstdio>
6 #include <string>
7 #include <vector>
8 
9 #include "base/files/file_util.h"
10 #include "base/memory/shared_memory.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/path_service.h"
13 #include "base/process/process_handle.h"
14 #include "base/run_loop.h"
15 #include "base/strings/string_util.h"
16 #include "base/time/time.h"
17 #include "components/visitedlink/browser/visitedlink_delegate.h"
18 #include "components/visitedlink/browser/visitedlink_event_listener.h"
19 #include "components/visitedlink/browser/visitedlink_master.h"
20 #include "components/visitedlink/common/visitedlink_messages.h"
21 #include "components/visitedlink/renderer/visitedlink_slave.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/notification_service.h"
24 #include "content/public/browser/notification_types.h"
25 #include "content/public/test/mock_render_process_host.h"
26 #include "content/public/test/test_browser_context.h"
27 #include "content/public/test/test_browser_thread_bundle.h"
28 #include "content/public/test/test_renderer_host.h"
29 #include "content/public/test/test_utils.h"
30 #include "testing/gtest/include/gtest/gtest.h"
31 #include "url/gurl.h"
32 
33 using content::BrowserThread;
34 using content::MockRenderProcessHost;
35 using content::RenderViewHostTester;
36 
37 namespace visitedlink {
38 
39 namespace {
40 
41 typedef std::vector<GURL> URLs;
42 
43 // a nice long URL that we can append numbers to to get new URLs
44 const char g_test_prefix[] =
45   "http://www.google.com/products/foo/index.html?id=45028640526508376&seq=";
46 const int g_test_count = 1000;
47 
48 // Returns a test URL for index |i|
TestURL(int i)49 GURL TestURL(int i) {
50   return GURL(base::StringPrintf("%s%d", g_test_prefix, i));
51 }
52 
53 std::vector<VisitedLinkSlave*> g_slaves;
54 
55 class TestVisitedLinkDelegate : public VisitedLinkDelegate {
56  public:
57   virtual void RebuildTable(
58       const scoped_refptr<URLEnumerator>& enumerator) OVERRIDE;
59 
60   void AddURLForRebuild(const GURL& url);
61 
62  private:
63   URLs rebuild_urls_;
64 };
65 
RebuildTable(const scoped_refptr<URLEnumerator> & enumerator)66 void TestVisitedLinkDelegate::RebuildTable(
67     const scoped_refptr<URLEnumerator>& enumerator) {
68   for (URLs::const_iterator itr = rebuild_urls_.begin();
69        itr != rebuild_urls_.end();
70        ++itr)
71     enumerator->OnURL(*itr);
72   enumerator->OnComplete(true);
73 }
74 
AddURLForRebuild(const GURL & url)75 void TestVisitedLinkDelegate::AddURLForRebuild(const GURL& url) {
76   rebuild_urls_.push_back(url);
77 }
78 
79 class TestURLIterator : public VisitedLinkMaster::URLIterator {
80  public:
81   explicit TestURLIterator(const URLs& urls);
82 
83   virtual const GURL& NextURL() OVERRIDE;
84   virtual bool HasNextURL() const OVERRIDE;
85 
86  private:
87   URLs::const_iterator iterator_;
88   URLs::const_iterator end_;
89 };
90 
TestURLIterator(const URLs & urls)91 TestURLIterator::TestURLIterator(const URLs& urls)
92     : iterator_(urls.begin()),
93       end_(urls.end()) {
94 }
95 
NextURL()96 const GURL& TestURLIterator::NextURL() {
97   return *(iterator_++);
98 }
99 
HasNextURL() const100 bool TestURLIterator::HasNextURL() const {
101   return iterator_ != end_;
102 }
103 
104 }  // namespace
105 
106 class TrackingVisitedLinkEventListener : public VisitedLinkMaster::Listener {
107  public:
TrackingVisitedLinkEventListener()108   TrackingVisitedLinkEventListener()
109       : reset_count_(0),
110         add_count_(0) {}
111 
NewTable(base::SharedMemory * table)112   virtual void NewTable(base::SharedMemory* table) OVERRIDE {
113     if (table) {
114       for (std::vector<VisitedLinkSlave>::size_type i = 0;
115            i < g_slaves.size(); i++) {
116         base::SharedMemoryHandle new_handle = base::SharedMemory::NULLHandle();
117         table->ShareToProcess(base::GetCurrentProcessHandle(), &new_handle);
118         g_slaves[i]->OnUpdateVisitedLinks(new_handle);
119       }
120     }
121   }
Add(VisitedLinkCommon::Fingerprint)122   virtual void Add(VisitedLinkCommon::Fingerprint) OVERRIDE { add_count_++; }
Reset()123   virtual void Reset() OVERRIDE { reset_count_++; }
124 
SetUp()125   void SetUp() {
126     reset_count_ = 0;
127     add_count_ = 0;
128   }
129 
reset_count() const130   int reset_count() const { return reset_count_; }
add_count() const131   int add_count() const { return add_count_; }
132 
133  private:
134   int reset_count_;
135   int add_count_;
136 };
137 
138 class VisitedLinkTest : public testing::Test {
139  protected:
140   // Initializes the visited link objects. Pass in the size that you want a
141   // freshly created table to be. 0 means use the default.
142   //
143   // |suppress_rebuild| is set when we're not testing rebuilding, see
144   // the VisitedLinkMaster constructor.
InitVisited(int initial_size,bool suppress_rebuild)145   bool InitVisited(int initial_size, bool suppress_rebuild) {
146     // Initialize the visited link system.
147     master_.reset(new VisitedLinkMaster(new TrackingVisitedLinkEventListener(),
148                                         &delegate_,
149                                         true,
150                                         suppress_rebuild, visited_file_,
151                                         initial_size));
152     return master_->Init();
153   }
154 
155   // May be called multiple times (some tests will do this to clear things,
156   // and TearDown will do this to make sure eveything is shiny before quitting.
ClearDB()157   void ClearDB() {
158     if (master_.get())
159       master_.reset(NULL);
160 
161     // Wait for all pending file I/O to be completed.
162     content::RunAllBlockingPoolTasksUntilIdle();
163   }
164 
165   // Loads the database from disk and makes sure that the same URLs are present
166   // as were generated by TestIO_Create(). This also checks the URLs with a
167   // slave to make sure it reads the data properly.
Reload()168   void Reload() {
169     // Clean up after our caller, who may have left the database open.
170     ClearDB();
171 
172     ASSERT_TRUE(InitVisited(0, true));
173     master_->DebugValidate();
174 
175     // check that the table has the proper number of entries
176     int used_count = master_->GetUsedCount();
177     ASSERT_EQ(used_count, g_test_count);
178 
179     // Create a slave database.
180     VisitedLinkSlave slave;
181     base::SharedMemoryHandle new_handle = base::SharedMemory::NULLHandle();
182     master_->shared_memory()->ShareToProcess(
183         base::GetCurrentProcessHandle(), &new_handle);
184     slave.OnUpdateVisitedLinks(new_handle);
185     g_slaves.push_back(&slave);
186 
187     bool found;
188     for (int i = 0; i < g_test_count; i++) {
189       GURL cur = TestURL(i);
190       found = master_->IsVisited(cur);
191       EXPECT_TRUE(found) << "URL " << i << "not found in master.";
192 
193       found = slave.IsVisited(cur);
194       EXPECT_TRUE(found) << "URL " << i << "not found in slave.";
195     }
196 
197     // test some random URL so we know that it returns false sometimes too
198     found = master_->IsVisited(GURL("http://unfound.site/"));
199     ASSERT_FALSE(found);
200     found = slave.IsVisited(GURL("http://unfound.site/"));
201     ASSERT_FALSE(found);
202 
203     master_->DebugValidate();
204 
205     g_slaves.clear();
206   }
207 
208   // testing::Test
SetUp()209   virtual void SetUp() {
210     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
211 
212     history_dir_ = temp_dir_.path().AppendASCII("VisitedLinkTest");
213     ASSERT_TRUE(base::CreateDirectory(history_dir_));
214 
215     visited_file_ = history_dir_.Append(FILE_PATH_LITERAL("VisitedLinks"));
216   }
217 
TearDown()218   virtual void TearDown() {
219     ClearDB();
220   }
221 
222   base::ScopedTempDir temp_dir_;
223 
224   // Filenames for the services;
225   base::FilePath history_dir_;
226   base::FilePath visited_file_;
227 
228   scoped_ptr<VisitedLinkMaster> master_;
229   TestVisitedLinkDelegate delegate_;
230   content::TestBrowserThreadBundle thread_bundle_;
231 };
232 
233 // This test creates and reads some databases to make sure the data is
234 // preserved throughout those operations.
TEST_F(VisitedLinkTest,DatabaseIO)235 TEST_F(VisitedLinkTest, DatabaseIO) {
236   ASSERT_TRUE(InitVisited(0, true));
237 
238   for (int i = 0; i < g_test_count; i++)
239     master_->AddURL(TestURL(i));
240 
241   // Test that the database was written properly
242   Reload();
243 }
244 
245 // Checks that we can delete things properly when there are collisions.
TEST_F(VisitedLinkTest,Delete)246 TEST_F(VisitedLinkTest, Delete) {
247   static const int32 kInitialSize = 17;
248   ASSERT_TRUE(InitVisited(kInitialSize, true));
249 
250   // Add a cluster from 14-17 wrapping around to 0. These will all hash to the
251   // same value.
252   const VisitedLinkCommon::Fingerprint kFingerprint0 = kInitialSize * 0 + 14;
253   const VisitedLinkCommon::Fingerprint kFingerprint1 = kInitialSize * 1 + 14;
254   const VisitedLinkCommon::Fingerprint kFingerprint2 = kInitialSize * 2 + 14;
255   const VisitedLinkCommon::Fingerprint kFingerprint3 = kInitialSize * 3 + 14;
256   const VisitedLinkCommon::Fingerprint kFingerprint4 = kInitialSize * 4 + 14;
257   master_->AddFingerprint(kFingerprint0, false);  // @14
258   master_->AddFingerprint(kFingerprint1, false);  // @15
259   master_->AddFingerprint(kFingerprint2, false);  // @16
260   master_->AddFingerprint(kFingerprint3, false);  // @0
261   master_->AddFingerprint(kFingerprint4, false);  // @1
262 
263   // Deleting 14 should move the next value up one slot (we do not specify an
264   // order).
265   EXPECT_EQ(kFingerprint3, master_->hash_table_[0]);
266   master_->DeleteFingerprint(kFingerprint3, false);
267   VisitedLinkCommon::Fingerprint zero_fingerprint = 0;
268   EXPECT_EQ(zero_fingerprint, master_->hash_table_[1]);
269   EXPECT_NE(zero_fingerprint, master_->hash_table_[0]);
270 
271   // Deleting the other four should leave the table empty.
272   master_->DeleteFingerprint(kFingerprint0, false);
273   master_->DeleteFingerprint(kFingerprint1, false);
274   master_->DeleteFingerprint(kFingerprint2, false);
275   master_->DeleteFingerprint(kFingerprint4, false);
276 
277   EXPECT_EQ(0, master_->used_items_);
278   for (int i = 0; i < kInitialSize; i++)
279     EXPECT_EQ(zero_fingerprint, master_->hash_table_[i]) <<
280         "Hash table has values in it.";
281 }
282 
283 // When we delete more than kBigDeleteThreshold we trigger different behavior
284 // where the entire file is rewritten.
TEST_F(VisitedLinkTest,BigDelete)285 TEST_F(VisitedLinkTest, BigDelete) {
286   ASSERT_TRUE(InitVisited(16381, true));
287 
288   // Add the base set of URLs that won't be deleted.
289   // Reload() will test for these.
290   for (int32 i = 0; i < g_test_count; i++)
291     master_->AddURL(TestURL(i));
292 
293   // Add more URLs than necessary to trigger this case.
294   const int kTestDeleteCount = VisitedLinkMaster::kBigDeleteThreshold + 2;
295   URLs urls_to_delete;
296   for (int32 i = g_test_count; i < g_test_count + kTestDeleteCount; i++) {
297     GURL url(TestURL(i));
298     master_->AddURL(url);
299     urls_to_delete.push_back(url);
300   }
301 
302   TestURLIterator iterator(urls_to_delete);
303   master_->DeleteURLs(&iterator);
304   master_->DebugValidate();
305 
306   Reload();
307 }
308 
TEST_F(VisitedLinkTest,DeleteAll)309 TEST_F(VisitedLinkTest, DeleteAll) {
310   ASSERT_TRUE(InitVisited(0, true));
311 
312   {
313     VisitedLinkSlave slave;
314     base::SharedMemoryHandle new_handle = base::SharedMemory::NULLHandle();
315     master_->shared_memory()->ShareToProcess(
316         base::GetCurrentProcessHandle(), &new_handle);
317     slave.OnUpdateVisitedLinks(new_handle);
318     g_slaves.push_back(&slave);
319 
320     // Add the test URLs.
321     for (int i = 0; i < g_test_count; i++) {
322       master_->AddURL(TestURL(i));
323       ASSERT_EQ(i + 1, master_->GetUsedCount());
324     }
325     master_->DebugValidate();
326 
327     // Make sure the slave picked up the adds.
328     for (int i = 0; i < g_test_count; i++)
329       EXPECT_TRUE(slave.IsVisited(TestURL(i)));
330 
331     // Clear the table and make sure the slave picked it up.
332     master_->DeleteAllURLs();
333     EXPECT_EQ(0, master_->GetUsedCount());
334     for (int i = 0; i < g_test_count; i++) {
335       EXPECT_FALSE(master_->IsVisited(TestURL(i)));
336       EXPECT_FALSE(slave.IsVisited(TestURL(i)));
337     }
338 
339     // Close the database.
340     g_slaves.clear();
341     ClearDB();
342   }
343 
344   // Reopen and validate.
345   ASSERT_TRUE(InitVisited(0, true));
346   master_->DebugValidate();
347   EXPECT_EQ(0, master_->GetUsedCount());
348   for (int i = 0; i < g_test_count; i++)
349     EXPECT_FALSE(master_->IsVisited(TestURL(i)));
350 }
351 
352 // This tests that the master correctly resizes its tables when it gets too
353 // full, notifies its slaves of the change, and updates the disk.
TEST_F(VisitedLinkTest,Resizing)354 TEST_F(VisitedLinkTest, Resizing) {
355   // Create a very small database.
356   const int32 initial_size = 17;
357   ASSERT_TRUE(InitVisited(initial_size, true));
358 
359   // ...and a slave
360   VisitedLinkSlave slave;
361   base::SharedMemoryHandle new_handle = base::SharedMemory::NULLHandle();
362   master_->shared_memory()->ShareToProcess(
363       base::GetCurrentProcessHandle(), &new_handle);
364   slave.OnUpdateVisitedLinks(new_handle);
365   g_slaves.push_back(&slave);
366 
367   int32 used_count = master_->GetUsedCount();
368   ASSERT_EQ(used_count, 0);
369 
370   for (int i = 0; i < g_test_count; i++) {
371     master_->AddURL(TestURL(i));
372     used_count = master_->GetUsedCount();
373     ASSERT_EQ(i + 1, used_count);
374   }
375 
376   // Verify that the table got resized sufficiently.
377   int32 table_size;
378   VisitedLinkCommon::Fingerprint* table;
379   master_->GetUsageStatistics(&table_size, &table);
380   used_count = master_->GetUsedCount();
381   ASSERT_GT(table_size, used_count);
382   ASSERT_EQ(used_count, g_test_count) <<
383                 "table count doesn't match the # of things we added";
384 
385   // Verify that the slave got the resize message and has the same
386   // table information.
387   int32 child_table_size;
388   VisitedLinkCommon::Fingerprint* child_table;
389   slave.GetUsageStatistics(&child_table_size, &child_table);
390   ASSERT_EQ(table_size, child_table_size);
391   for (int32 i = 0; i < table_size; i++) {
392     ASSERT_EQ(table[i], child_table[i]);
393   }
394 
395   master_->DebugValidate();
396   g_slaves.clear();
397 
398   // This tests that the file is written correctly by reading it in using
399   // a new database.
400   Reload();
401 }
402 
403 // Tests that if the database doesn't exist, it will be rebuilt from history.
TEST_F(VisitedLinkTest,Rebuild)404 TEST_F(VisitedLinkTest, Rebuild) {
405   // Add half of our URLs to history. This needs to be done before we
406   // initialize the visited link DB.
407   int history_count = g_test_count / 2;
408   for (int i = 0; i < history_count; i++)
409     delegate_.AddURLForRebuild(TestURL(i));
410 
411   // Initialize the visited link DB. Since the visited links file doesn't exist
412   // and we don't suppress history rebuilding, this will load from history.
413   ASSERT_TRUE(InitVisited(0, false));
414 
415   // While the table is rebuilding, add the rest of the URLs to the visited
416   // link system. This isn't guaranteed to happen during the rebuild, so we
417   // can't be 100% sure we're testing the right thing, but in practice is.
418   // All the adds above will generally take some time queuing up on the
419   // history thread, and it will take a while to catch up to actually
420   // processing the rebuild that has queued behind it. We will generally
421   // finish adding all of the URLs before it has even found the first URL.
422   for (int i = history_count; i < g_test_count; i++)
423     master_->AddURL(TestURL(i));
424 
425   // Add one more and then delete it.
426   master_->AddURL(TestURL(g_test_count));
427   URLs urls_to_delete;
428   urls_to_delete.push_back(TestURL(g_test_count));
429   TestURLIterator iterator(urls_to_delete);
430   master_->DeleteURLs(&iterator);
431 
432   // Wait for the rebuild to complete. The task will terminate the message
433   // loop when the rebuild is done. There's no chance that the rebuild will
434   // complete before we set the task because the rebuild completion message
435   // is posted to the message loop; until we Run() it, rebuild can not
436   // complete.
437   base::RunLoop run_loop;
438   master_->set_rebuild_complete_task(run_loop.QuitClosure());
439   run_loop.Run();
440 
441   // Test that all URLs were written to the database properly.
442   Reload();
443 
444   // Make sure the extra one was *not* written (Reload won't test this).
445   EXPECT_FALSE(master_->IsVisited(TestURL(g_test_count)));
446 }
447 
448 // Test that importing a large number of URLs will work
TEST_F(VisitedLinkTest,BigImport)449 TEST_F(VisitedLinkTest, BigImport) {
450   ASSERT_TRUE(InitVisited(0, false));
451 
452   // Before the table rebuilds, add a large number of URLs
453   int total_count = VisitedLinkMaster::kDefaultTableSize + 10;
454   for (int i = 0; i < total_count; i++)
455     master_->AddURL(TestURL(i));
456 
457   // Wait for the rebuild to complete.
458   base::RunLoop run_loop;
459   master_->set_rebuild_complete_task(run_loop.QuitClosure());
460   run_loop.Run();
461 
462   // Ensure that the right number of URLs are present
463   int used_count = master_->GetUsedCount();
464   ASSERT_EQ(used_count, total_count);
465 }
466 
TEST_F(VisitedLinkTest,Listener)467 TEST_F(VisitedLinkTest, Listener) {
468   ASSERT_TRUE(InitVisited(0, true));
469 
470   // Add test URLs.
471   for (int i = 0; i < g_test_count; i++) {
472     master_->AddURL(TestURL(i));
473     ASSERT_EQ(i + 1, master_->GetUsedCount());
474   }
475 
476   // Delete an URL.
477   URLs urls_to_delete;
478   urls_to_delete.push_back(TestURL(0));
479   TestURLIterator iterator(urls_to_delete);
480   master_->DeleteURLs(&iterator);
481 
482   // ... and all of the remaining ones.
483   master_->DeleteAllURLs();
484 
485   TrackingVisitedLinkEventListener* listener =
486       static_cast<TrackingVisitedLinkEventListener*>(master_->GetListener());
487 
488   // Verify that VisitedLinkMaster::Listener::Add was called for each added URL.
489   EXPECT_EQ(g_test_count, listener->add_count());
490   // Verify that VisitedLinkMaster::Listener::Reset was called both when one and
491   // all URLs are deleted.
492   EXPECT_EQ(2, listener->reset_count());
493 }
494 
495 class VisitCountingContext : public content::TestBrowserContext {
496  public:
VisitCountingContext()497   VisitCountingContext()
498       : add_count_(0),
499         add_event_count_(0),
500         reset_event_count_(0),
501         new_table_count_(0) {}
502 
CountAddEvent(int by)503   void CountAddEvent(int by) {
504     add_count_ += by;
505     add_event_count_++;
506   }
507 
CountResetEvent()508   void CountResetEvent() {
509     reset_event_count_++;
510   }
511 
CountNewTable()512   void CountNewTable() {
513     new_table_count_++;
514   }
515 
add_count() const516   int add_count() const { return add_count_; }
add_event_count() const517   int add_event_count() const { return add_event_count_; }
reset_event_count() const518   int reset_event_count() const { return reset_event_count_; }
new_table_count() const519   int new_table_count() const { return new_table_count_; }
520 
521  private:
522   int add_count_;
523   int add_event_count_;
524   int reset_event_count_;
525   int new_table_count_;
526 };
527 
528 // Stub out as little as possible, borrowing from RenderProcessHost.
529 class VisitRelayingRenderProcessHost : public MockRenderProcessHost {
530  public:
VisitRelayingRenderProcessHost(content::BrowserContext * browser_context)531   explicit VisitRelayingRenderProcessHost(
532       content::BrowserContext* browser_context)
533           : MockRenderProcessHost(browser_context), widgets_(0) {
534     content::NotificationService::current()->Notify(
535         content::NOTIFICATION_RENDERER_PROCESS_CREATED,
536         content::Source<RenderProcessHost>(this),
537         content::NotificationService::NoDetails());
538   }
~VisitRelayingRenderProcessHost()539   virtual ~VisitRelayingRenderProcessHost() {
540     content::NotificationService::current()->Notify(
541         content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
542         content::Source<content::RenderProcessHost>(this),
543         content::NotificationService::NoDetails());
544   }
545 
WidgetRestored()546   virtual void WidgetRestored() OVERRIDE { widgets_++; }
WidgetHidden()547   virtual void WidgetHidden() OVERRIDE { widgets_--; }
VisibleWidgetCount() const548   virtual int VisibleWidgetCount() const OVERRIDE { return widgets_; }
549 
Send(IPC::Message * msg)550   virtual bool Send(IPC::Message* msg) OVERRIDE {
551     VisitCountingContext* counting_context =
552         static_cast<VisitCountingContext*>(
553             GetBrowserContext());
554 
555     if (msg->type() == ChromeViewMsg_VisitedLink_Add::ID) {
556       PickleIterator iter(*msg);
557       std::vector<uint64> fingerprints;
558       CHECK(IPC::ReadParam(msg, &iter, &fingerprints));
559       counting_context->CountAddEvent(fingerprints.size());
560     } else if (msg->type() == ChromeViewMsg_VisitedLink_Reset::ID) {
561       counting_context->CountResetEvent();
562     } else if (msg->type() == ChromeViewMsg_VisitedLink_NewTable::ID) {
563       counting_context->CountNewTable();
564     }
565 
566     delete msg;
567     return true;
568   }
569 
570  private:
571   int widgets_;
572 
573   DISALLOW_COPY_AND_ASSIGN(VisitRelayingRenderProcessHost);
574 };
575 
576 class VisitedLinkRenderProcessHostFactory
577     : public content::RenderProcessHostFactory {
578  public:
VisitedLinkRenderProcessHostFactory()579   VisitedLinkRenderProcessHostFactory()
580       : content::RenderProcessHostFactory() {}
CreateRenderProcessHost(content::BrowserContext * browser_context,content::SiteInstance * site_instance) const581   virtual content::RenderProcessHost* CreateRenderProcessHost(
582       content::BrowserContext* browser_context,
583       content::SiteInstance* site_instance) const OVERRIDE {
584     return new VisitRelayingRenderProcessHost(browser_context);
585   }
586 
587  private:
588   DISALLOW_COPY_AND_ASSIGN(VisitedLinkRenderProcessHostFactory);
589 };
590 
591 class VisitedLinkEventsTest : public content::RenderViewHostTestHarness {
592  public:
SetUp()593   virtual void SetUp() {
594     SetRenderProcessHostFactory(&vc_rph_factory_);
595     content::RenderViewHostTestHarness::SetUp();
596   }
597 
CreateBrowserContext()598   virtual content::BrowserContext* CreateBrowserContext() OVERRIDE {
599     VisitCountingContext* context = new VisitCountingContext();
600     master_.reset(new VisitedLinkMaster(context, &delegate_, true));
601     master_->Init();
602     return context;
603   }
604 
context()605   VisitCountingContext* context() {
606     return static_cast<VisitCountingContext*>(browser_context());
607   }
608 
master() const609   VisitedLinkMaster* master() const {
610     return master_.get();
611   }
612 
WaitForCoalescense()613   void WaitForCoalescense() {
614     // Let the timer fire.
615     //
616     // TODO(ajwong): This is horrid! What is the right synchronization method?
617     base::RunLoop run_loop;
618     base::MessageLoop::current()->PostDelayedTask(
619         FROM_HERE,
620         run_loop.QuitClosure(),
621         base::TimeDelta::FromMilliseconds(110));
622     run_loop.Run();
623   }
624 
625  protected:
626   VisitedLinkRenderProcessHostFactory vc_rph_factory_;
627 
628  private:
629   TestVisitedLinkDelegate delegate_;
630   scoped_ptr<VisitedLinkMaster> master_;
631 };
632 
TEST_F(VisitedLinkEventsTest,Coalescense)633 TEST_F(VisitedLinkEventsTest, Coalescense) {
634   // add some URLs to master.
635   // Add a few URLs.
636   master()->AddURL(GURL("http://acidtests.org/"));
637   master()->AddURL(GURL("http://google.com/"));
638   master()->AddURL(GURL("http://chromium.org/"));
639   // Just for kicks, add a duplicate URL. This shouldn't increase the resulting
640   master()->AddURL(GURL("http://acidtests.org/"));
641 
642   // Make sure that coalescing actually occurs. There should be no links or
643   // events received by the renderer.
644   EXPECT_EQ(0, context()->add_count());
645   EXPECT_EQ(0, context()->add_event_count());
646 
647   WaitForCoalescense();
648 
649   // We now should have 3 entries added in 1 event.
650   EXPECT_EQ(3, context()->add_count());
651   EXPECT_EQ(1, context()->add_event_count());
652 
653   // Test whether the coalescing continues by adding a few more URLs.
654   master()->AddURL(GURL("http://google.com/chrome/"));
655   master()->AddURL(GURL("http://webkit.org/"));
656   master()->AddURL(GURL("http://acid3.acidtests.org/"));
657 
658   WaitForCoalescense();
659 
660   // We should have 6 entries added in 2 events.
661   EXPECT_EQ(6, context()->add_count());
662   EXPECT_EQ(2, context()->add_event_count());
663 
664   // Test whether duplicate entries produce add events.
665   master()->AddURL(GURL("http://acidtests.org/"));
666 
667   WaitForCoalescense();
668 
669   // We should have no change in results.
670   EXPECT_EQ(6, context()->add_count());
671   EXPECT_EQ(2, context()->add_event_count());
672 
673   // Ensure that the coalescing does not resume after resetting.
674   master()->AddURL(GURL("http://build.chromium.org/"));
675   master()->DeleteAllURLs();
676 
677   WaitForCoalescense();
678 
679   // We should have no change in results except for one new reset event.
680   EXPECT_EQ(6, context()->add_count());
681   EXPECT_EQ(2, context()->add_event_count());
682   EXPECT_EQ(1, context()->reset_event_count());
683 }
684 
TEST_F(VisitedLinkEventsTest,Basics)685 TEST_F(VisitedLinkEventsTest, Basics) {
686   RenderViewHostTester::For(rvh())->CreateRenderView(
687       base::string16(), MSG_ROUTING_NONE, MSG_ROUTING_NONE, -1, false);
688 
689   // Add a few URLs.
690   master()->AddURL(GURL("http://acidtests.org/"));
691   master()->AddURL(GURL("http://google.com/"));
692   master()->AddURL(GURL("http://chromium.org/"));
693 
694   WaitForCoalescense();
695 
696   // We now should have 1 add event.
697   EXPECT_EQ(1, context()->add_event_count());
698   EXPECT_EQ(0, context()->reset_event_count());
699 
700   master()->DeleteAllURLs();
701 
702   WaitForCoalescense();
703 
704   // We should have no change in add results, plus one new reset event.
705   EXPECT_EQ(1, context()->add_event_count());
706   EXPECT_EQ(1, context()->reset_event_count());
707 }
708 
TEST_F(VisitedLinkEventsTest,TabVisibility)709 TEST_F(VisitedLinkEventsTest, TabVisibility) {
710   RenderViewHostTester::For(rvh())->CreateRenderView(
711       base::string16(), MSG_ROUTING_NONE, MSG_ROUTING_NONE, -1, false);
712 
713   // Simulate tab becoming inactive.
714   RenderViewHostTester::For(rvh())->SimulateWasHidden();
715 
716   // Add a few URLs.
717   master()->AddURL(GURL("http://acidtests.org/"));
718   master()->AddURL(GURL("http://google.com/"));
719   master()->AddURL(GURL("http://chromium.org/"));
720 
721   WaitForCoalescense();
722 
723   // We shouldn't have any events.
724   EXPECT_EQ(0, context()->add_event_count());
725   EXPECT_EQ(0, context()->reset_event_count());
726 
727   // Simulate the tab becoming active.
728   RenderViewHostTester::For(rvh())->SimulateWasShown();
729 
730   // We should now have 3 add events, still no reset events.
731   EXPECT_EQ(1, context()->add_event_count());
732   EXPECT_EQ(0, context()->reset_event_count());
733 
734   // Deactivate the tab again.
735   RenderViewHostTester::For(rvh())->SimulateWasHidden();
736 
737   // Add a bunch of URLs (over 50) to exhaust the link event buffer.
738   for (int i = 0; i < 100; i++)
739     master()->AddURL(TestURL(i));
740 
741   WaitForCoalescense();
742 
743   // Again, no change in events until tab is active.
744   EXPECT_EQ(1, context()->add_event_count());
745   EXPECT_EQ(0, context()->reset_event_count());
746 
747   // Activate the tab.
748   RenderViewHostTester::For(rvh())->SimulateWasShown();
749 
750   // We should have only one more reset event.
751   EXPECT_EQ(1, context()->add_event_count());
752   EXPECT_EQ(1, context()->reset_event_count());
753 }
754 
755 // Tests that VisitedLink ignores renderer process creation notification for a
756 // different context.
TEST_F(VisitedLinkEventsTest,IgnoreRendererCreationFromDifferentContext)757 TEST_F(VisitedLinkEventsTest, IgnoreRendererCreationFromDifferentContext) {
758   VisitCountingContext different_context;
759   VisitRelayingRenderProcessHost different_process_host(&different_context);
760 
761   content::NotificationService::current()->Notify(
762       content::NOTIFICATION_RENDERER_PROCESS_CREATED,
763       content::Source<content::RenderProcessHost>(&different_process_host),
764       content::NotificationService::NoDetails());
765   WaitForCoalescense();
766 
767   EXPECT_EQ(0, different_context.new_table_count());
768 }
769 
770 }  // namespace visitedlink
771