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/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 "testing/gtest/include/gtest/gtest.h"
30 #include "url/gurl.h"
31
32 using content::BrowserThread;
33 using content::MockRenderProcessHost;
34 using content::RenderViewHostTester;
35
36 namespace visitedlink {
37
38 namespace {
39
40 typedef std::vector<GURL> URLs;
41
42 // a nice long URL that we can append numbers to to get new URLs
43 const char g_test_prefix[] =
44 "http://www.google.com/products/foo/index.html?id=45028640526508376&seq=";
45 const int g_test_count = 1000;
46
47 // Returns a test URL for index |i|
TestURL(int i)48 GURL TestURL(int i) {
49 return GURL(base::StringPrintf("%s%d", g_test_prefix, i));
50 }
51
52 std::vector<VisitedLinkSlave*> g_slaves;
53
54 class TestVisitedLinkDelegate : public VisitedLinkDelegate {
55 public:
56 virtual void RebuildTable(
57 const scoped_refptr<URLEnumerator>& enumerator) OVERRIDE;
58
59 void AddURLForRebuild(const GURL& url);
60
61 private:
62
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 BrowserThread::GetBlockingPool()->FlushForTesting();
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
589 DISALLOW_COPY_AND_ASSIGN(VisitedLinkRenderProcessHostFactory);
590 };
591
592 class VisitedLinkEventsTest : public content::RenderViewHostTestHarness {
593 public:
SetUp()594 virtual void SetUp() {
595 SetRenderProcessHostFactory(&vc_rph_factory_);
596 content::RenderViewHostTestHarness::SetUp();
597 }
598
CreateBrowserContext()599 virtual content::BrowserContext* CreateBrowserContext() OVERRIDE {
600 VisitCountingContext* context = new VisitCountingContext();
601 master_.reset(new VisitedLinkMaster(context, &delegate_, true));
602 master_->Init();
603 return context;
604 }
605
context()606 VisitCountingContext* context() {
607 return static_cast<VisitCountingContext*>(browser_context());
608 }
609
master() const610 VisitedLinkMaster* master() const {
611 return master_.get();
612 }
613
WaitForCoalescense()614 void WaitForCoalescense() {
615 // Let the timer fire.
616 //
617 // TODO(ajwong): This is horrid! What is the right synchronization method?
618 base::RunLoop run_loop;
619 base::MessageLoop::current()->PostDelayedTask(
620 FROM_HERE,
621 run_loop.QuitClosure(),
622 base::TimeDelta::FromMilliseconds(110));
623 run_loop.Run();
624 }
625
626 protected:
627 VisitedLinkRenderProcessHostFactory vc_rph_factory_;
628
629 private:
630 TestVisitedLinkDelegate delegate_;
631 scoped_ptr<VisitedLinkMaster> master_;
632 };
633
TEST_F(VisitedLinkEventsTest,Coalescense)634 TEST_F(VisitedLinkEventsTest, Coalescense) {
635 // add some URLs to master.
636 // Add a few URLs.
637 master()->AddURL(GURL("http://acidtests.org/"));
638 master()->AddURL(GURL("http://google.com/"));
639 master()->AddURL(GURL("http://chromium.org/"));
640 // Just for kicks, add a duplicate URL. This shouldn't increase the resulting
641 master()->AddURL(GURL("http://acidtests.org/"));
642
643 // Make sure that coalescing actually occurs. There should be no links or
644 // events received by the renderer.
645 EXPECT_EQ(0, context()->add_count());
646 EXPECT_EQ(0, context()->add_event_count());
647
648 WaitForCoalescense();
649
650 // We now should have 3 entries added in 1 event.
651 EXPECT_EQ(3, context()->add_count());
652 EXPECT_EQ(1, context()->add_event_count());
653
654 // Test whether the coalescing continues by adding a few more URLs.
655 master()->AddURL(GURL("http://google.com/chrome/"));
656 master()->AddURL(GURL("http://webkit.org/"));
657 master()->AddURL(GURL("http://acid3.acidtests.org/"));
658
659 WaitForCoalescense();
660
661 // We should have 6 entries added in 2 events.
662 EXPECT_EQ(6, context()->add_count());
663 EXPECT_EQ(2, context()->add_event_count());
664
665 // Test whether duplicate entries produce add events.
666 master()->AddURL(GURL("http://acidtests.org/"));
667
668 WaitForCoalescense();
669
670 // We should have no change in results.
671 EXPECT_EQ(6, context()->add_count());
672 EXPECT_EQ(2, context()->add_event_count());
673
674 // Ensure that the coalescing does not resume after resetting.
675 master()->AddURL(GURL("http://build.chromium.org/"));
676 master()->DeleteAllURLs();
677
678 WaitForCoalescense();
679
680 // We should have no change in results except for one new reset event.
681 EXPECT_EQ(6, context()->add_count());
682 EXPECT_EQ(2, context()->add_event_count());
683 EXPECT_EQ(1, context()->reset_event_count());
684 }
685
TEST_F(VisitedLinkEventsTest,Basics)686 TEST_F(VisitedLinkEventsTest, Basics) {
687 RenderViewHostTester::For(rvh())->CreateRenderView(base::string16(),
688 MSG_ROUTING_NONE,
689 -1);
690
691 // Add a few URLs.
692 master()->AddURL(GURL("http://acidtests.org/"));
693 master()->AddURL(GURL("http://google.com/"));
694 master()->AddURL(GURL("http://chromium.org/"));
695
696 WaitForCoalescense();
697
698 // We now should have 1 add event.
699 EXPECT_EQ(1, context()->add_event_count());
700 EXPECT_EQ(0, context()->reset_event_count());
701
702 master()->DeleteAllURLs();
703
704 WaitForCoalescense();
705
706 // We should have no change in add results, plus one new reset event.
707 EXPECT_EQ(1, context()->add_event_count());
708 EXPECT_EQ(1, context()->reset_event_count());
709 }
710
TEST_F(VisitedLinkEventsTest,TabVisibility)711 TEST_F(VisitedLinkEventsTest, TabVisibility) {
712 RenderViewHostTester::For(rvh())->CreateRenderView(base::string16(),
713 MSG_ROUTING_NONE,
714 -1);
715
716 // Simulate tab becoming inactive.
717 RenderViewHostTester::For(rvh())->SimulateWasHidden();
718
719 // Add a few URLs.
720 master()->AddURL(GURL("http://acidtests.org/"));
721 master()->AddURL(GURL("http://google.com/"));
722 master()->AddURL(GURL("http://chromium.org/"));
723
724 WaitForCoalescense();
725
726 // We shouldn't have any events.
727 EXPECT_EQ(0, context()->add_event_count());
728 EXPECT_EQ(0, context()->reset_event_count());
729
730 // Simulate the tab becoming active.
731 RenderViewHostTester::For(rvh())->SimulateWasShown();
732
733 // We should now have 3 add events, still no reset events.
734 EXPECT_EQ(1, context()->add_event_count());
735 EXPECT_EQ(0, context()->reset_event_count());
736
737 // Deactivate the tab again.
738 RenderViewHostTester::For(rvh())->SimulateWasHidden();
739
740 // Add a bunch of URLs (over 50) to exhaust the link event buffer.
741 for (int i = 0; i < 100; i++)
742 master()->AddURL(TestURL(i));
743
744 WaitForCoalescense();
745
746 // Again, no change in events until tab is active.
747 EXPECT_EQ(1, context()->add_event_count());
748 EXPECT_EQ(0, context()->reset_event_count());
749
750 // Activate the tab.
751 RenderViewHostTester::For(rvh())->SimulateWasShown();
752
753 // We should have only one more reset event.
754 EXPECT_EQ(1, context()->add_event_count());
755 EXPECT_EQ(1, context()->reset_event_count());
756 }
757
758 // Tests that VisitedLink ignores renderer process creation notification for a
759 // different context.
TEST_F(VisitedLinkEventsTest,IgnoreRendererCreationFromDifferentContext)760 TEST_F(VisitedLinkEventsTest, IgnoreRendererCreationFromDifferentContext) {
761 VisitCountingContext different_context;
762 VisitRelayingRenderProcessHost different_process_host(&different_context);
763
764 content::NotificationService::current()->Notify(
765 content::NOTIFICATION_RENDERER_PROCESS_CREATED,
766 content::Source<content::RenderProcessHost>(&different_process_host),
767 content::NotificationService::NoDetails());
768 WaitForCoalescense();
769
770 EXPECT_EQ(0, different_context.new_table_count());
771
772 }
773
774 } // namespace visitedlink
775