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
33using content::BrowserThread;
34using content::MockRenderProcessHost;
35using content::RenderViewHostTester;
36
37namespace visitedlink {
38
39namespace {
40
41typedef std::vector<GURL> URLs;
42
43// a nice long URL that we can append numbers to to get new URLs
44const char g_test_prefix[] =
45  "http://www.google.com/products/foo/index.html?id=45028640526508376&seq=";
46const int g_test_count = 1000;
47
48// Returns a test URL for index |i|
49GURL TestURL(int i) {
50  return GURL(base::StringPrintf("%s%d", g_test_prefix, i));
51}
52
53std::vector<VisitedLinkSlave*> g_slaves;
54
55class 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
66void 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
75void TestVisitedLinkDelegate::AddURLForRebuild(const GURL& url) {
76  rebuild_urls_.push_back(url);
77}
78
79class 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
91TestURLIterator::TestURLIterator(const URLs& urls)
92    : iterator_(urls.begin()),
93      end_(urls.end()) {
94}
95
96const GURL& TestURLIterator::NextURL() {
97  return *(iterator_++);
98}
99
100bool TestURLIterator::HasNextURL() const {
101  return iterator_ != end_;
102}
103
104}  // namespace
105
106class TrackingVisitedLinkEventListener : public VisitedLinkMaster::Listener {
107 public:
108  TrackingVisitedLinkEventListener()
109      : reset_count_(0),
110        add_count_(0) {}
111
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  }
122  virtual void Add(VisitedLinkCommon::Fingerprint) OVERRIDE { add_count_++; }
123  virtual void Reset() OVERRIDE { reset_count_++; }
124
125  void SetUp() {
126    reset_count_ = 0;
127    add_count_ = 0;
128  }
129
130  int reset_count() const { return reset_count_; }
131  int add_count() const { return add_count_; }
132
133 private:
134  int reset_count_;
135  int add_count_;
136};
137
138class 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.
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.
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.
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
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
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.
235TEST_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.
246TEST_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.
285TEST_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
309TEST_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.
354TEST_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.
404TEST_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
449TEST_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
467TEST_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
495class VisitCountingContext : public content::TestBrowserContext {
496 public:
497  VisitCountingContext()
498      : add_count_(0),
499        add_event_count_(0),
500        reset_event_count_(0),
501        new_table_count_(0) {}
502
503  void CountAddEvent(int by) {
504    add_count_ += by;
505    add_event_count_++;
506  }
507
508  void CountResetEvent() {
509    reset_event_count_++;
510  }
511
512  void CountNewTable() {
513    new_table_count_++;
514  }
515
516  int add_count() const { return add_count_; }
517  int add_event_count() const { return add_event_count_; }
518  int reset_event_count() const { return reset_event_count_; }
519  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.
529class VisitRelayingRenderProcessHost : public MockRenderProcessHost {
530 public:
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  }
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
546  virtual void WidgetRestored() OVERRIDE { widgets_++; }
547  virtual void WidgetHidden() OVERRIDE { widgets_--; }
548  virtual int VisibleWidgetCount() const OVERRIDE { return widgets_; }
549
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
576class VisitedLinkRenderProcessHostFactory
577    : public content::RenderProcessHostFactory {
578 public:
579  VisitedLinkRenderProcessHostFactory()
580      : content::RenderProcessHostFactory() {}
581  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
591class VisitedLinkEventsTest : public content::RenderViewHostTestHarness {
592 public:
593  virtual void SetUp() {
594    SetRenderProcessHostFactory(&vc_rph_factory_);
595    content::RenderViewHostTestHarness::SetUp();
596  }
597
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
605  VisitCountingContext* context() {
606    return static_cast<VisitCountingContext*>(browser_context());
607  }
608
609  VisitedLinkMaster* master() const {
610    return master_.get();
611  }
612
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
633TEST_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
685TEST_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
709TEST_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.
757TEST_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