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