OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/memory/tab_manager.h" | 5 #include "chrome/browser/memory/tab_manager.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <map> | 8 #include <map> |
9 #include <vector> | 9 #include <vector> |
10 | 10 |
(...skipping 13 matching lines...) Expand all Loading... |
24 #include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h" | 24 #include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h" |
25 #include "chrome/common/chrome_features.h" | 25 #include "chrome/common/chrome_features.h" |
26 #include "chrome/common/url_constants.h" | 26 #include "chrome/common/url_constants.h" |
27 #include "chrome/test/base/chrome_render_view_host_test_harness.h" | 27 #include "chrome/test/base/chrome_render_view_host_test_harness.h" |
28 #include "chrome/test/base/testing_profile.h" | 28 #include "chrome/test/base/testing_profile.h" |
29 #include "components/variations/variations_associated_data.h" | 29 #include "components/variations/variations_associated_data.h" |
30 #include "content/public/browser/render_process_host.h" | 30 #include "content/public/browser/render_process_host.h" |
31 #include "content/public/browser/web_contents.h" | 31 #include "content/public/browser/web_contents.h" |
32 #include "content/public/test/mock_render_process_host.h" | 32 #include "content/public/test/mock_render_process_host.h" |
33 #include "content/public/test/web_contents_tester.h" | 33 #include "content/public/test/web_contents_tester.h" |
34 #include "testing/gmock/include/gmock/gmock.h" | |
35 #include "testing/gtest/include/gtest/gtest.h" | 34 #include "testing/gtest/include/gtest/gtest.h" |
36 #include "url/gurl.h" | 35 #include "url/gurl.h" |
37 | 36 |
38 using content::WebContents; | 37 using content::WebContents; |
39 using content::WebContentsTester; | 38 using content::WebContentsTester; |
40 | 39 |
41 namespace memory { | 40 namespace memory { |
42 namespace { | 41 namespace { |
43 | 42 |
44 class TabStripDummyDelegate : public TestTabStripModelDelegate { | 43 class TabStripDummyDelegate : public TestTabStripModelDelegate { |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
79 } | 78 } |
80 | 79 |
81 private: | 80 private: |
82 int nb_events_; | 81 int nb_events_; |
83 WebContents* old_contents_; | 82 WebContents* old_contents_; |
84 WebContents* new_contents_; | 83 WebContents* new_contents_; |
85 | 84 |
86 DISALLOW_COPY_AND_ASSIGN(MockTabStripModelObserver); | 85 DISALLOW_COPY_AND_ASSIGN(MockTabStripModelObserver); |
87 }; | 86 }; |
88 | 87 |
89 // A mock task runner. This isn't directly a TaskRunner as the reference | |
90 // counting confuses gmock. | |
91 class LenientMockTaskRunner { | |
92 public: | |
93 LenientMockTaskRunner() {} | |
94 MOCK_METHOD2(PostDelayedTask, | |
95 bool(const tracked_objects::Location&, base::TimeDelta)); | |
96 | |
97 private: | |
98 DISALLOW_COPY_AND_ASSIGN(LenientMockTaskRunner); | |
99 }; | |
100 using MockTaskRunner = testing::StrictMock<LenientMockTaskRunner>; | |
101 | |
102 // Represents a pending task. | |
103 using Task = std::pair<base::TimeTicks, base::OnceClosure>; | |
104 | |
105 // Comparator used for sorting Task objects. Can't use std::pair's default | |
106 // comparison operators because Closure's are comparable. | |
107 struct TaskComparator { | |
108 bool operator()(const Task& t1, const Task& t2) const { | |
109 // Reverse the sort order so this can be used with a min-heap. | |
110 return t1.first > t2.first; | |
111 } | |
112 }; | |
113 | |
114 // A TaskRunner implementation that delegates to a MockTaskRunner for asserting | |
115 // on task insertion order, and which stores tasks for actual later execution. | |
116 class TaskRunnerProxy : public base::TaskRunner { | |
117 public: | |
118 TaskRunnerProxy(MockTaskRunner* mock, | |
119 base::SimpleTestTickClock* clock) | |
120 : mock_(mock), clock_(clock) {} | |
121 bool RunsTasksInCurrentSequence() const override { return true; } | |
122 bool PostDelayedTask(const tracked_objects::Location& location, | |
123 base::OnceClosure closure, | |
124 base::TimeDelta delta) override { | |
125 mock_->PostDelayedTask(location, delta); | |
126 base::TimeTicks when = clock_->NowTicks() + delta; | |
127 tasks_.push_back(Task(when, std::move(closure))); | |
128 // Use 'greater' comparator to make this a min heap. | |
129 std::push_heap(tasks_.begin(), tasks_.end(), TaskComparator()); | |
130 return true; | |
131 } | |
132 | |
133 // Runs tasks up to the current time. Returns the number of tasks executed. | |
134 size_t RunTasks() { | |
135 base::TimeTicks now = clock_->NowTicks(); | |
136 size_t count = 0; | |
137 while (!tasks_.empty() && tasks_.front().first <= now) { | |
138 std::move(tasks_.front().second).Run(); | |
139 std::pop_heap(tasks_.begin(), tasks_.end(), TaskComparator()); | |
140 tasks_.pop_back(); | |
141 ++count; | |
142 } | |
143 return count; | |
144 } | |
145 | |
146 // Advances the clock and runs the next task in the queue. Can run more than | |
147 // one task if multiple tasks have the same scheduled time. | |
148 size_t RunNextTask() { | |
149 // Get the time of the next task that can possibly run. | |
150 base::TimeTicks when = tasks_.front().first; | |
151 | |
152 // Advance the clock to exactly that time. | |
153 base::TimeTicks now = clock_->NowTicks(); | |
154 if (now < when) { | |
155 base::TimeDelta delta = when - now; | |
156 clock_->Advance(delta); | |
157 } | |
158 | |
159 // Run whatever tasks are now eligible to run. | |
160 return RunTasks(); | |
161 } | |
162 | |
163 size_t size() const { return tasks_.size(); } | |
164 | |
165 private: | |
166 ~TaskRunnerProxy() override {} | |
167 | |
168 MockTaskRunner* mock_; | |
169 base::SimpleTestTickClock* clock_; | |
170 | |
171 // A min-heap of outstanding tasks. | |
172 using Task = std::pair<base::TimeTicks, base::OnceClosure>; | |
173 std::vector<Task> tasks_; | |
174 | |
175 DISALLOW_COPY_AND_ASSIGN(TaskRunnerProxy); | |
176 }; | |
177 | |
178 enum TestIndicies { | 88 enum TestIndicies { |
179 kSelected, | 89 kSelected, |
180 kAutoDiscardable, | 90 kAutoDiscardable, |
181 kPinned, | 91 kPinned, |
182 kApp, | 92 kApp, |
183 kPlayingAudio, | 93 kPlayingAudio, |
184 kFormEntry, | 94 kFormEntry, |
185 kRecent, | 95 kRecent, |
186 kOld, | 96 kOld, |
187 kReallyOld, | 97 kReallyOld, |
188 kOldButPinned, | 98 kOldButPinned, |
189 kInternalPage, | 99 kInternalPage, |
190 }; | 100 }; |
| 101 |
191 } // namespace | 102 } // namespace |
192 | 103 |
193 class LenientTabManagerTest : public ChromeRenderViewHostTestHarness { | 104 class TabManagerTest : public ChromeRenderViewHostTestHarness { |
194 public: | 105 public: |
195 WebContents* CreateWebContents() { | 106 WebContents* CreateWebContents() { |
196 return WebContents::Create(WebContents::CreateParams(profile())); | 107 return WebContents::Create(WebContents::CreateParams(profile())); |
197 } | 108 } |
198 | |
199 MOCK_METHOD2(NotifyRendererProcess, | |
200 void(const content::RenderProcessHost*, | |
201 base::MemoryPressureListener::MemoryPressureLevel)); | |
202 }; | 109 }; |
203 using TabManagerTest = testing::StrictMock<LenientTabManagerTest>; | |
204 | 110 |
205 // TODO(georgesak): Add tests for protection to tabs with form input and | 111 // TODO(georgesak): Add tests for protection to tabs with form input and |
206 // playing audio; | 112 // playing audio; |
207 | 113 |
208 // Tests the sorting comparator to make sure it's producing the desired order. | 114 // Tests the sorting comparator to make sure it's producing the desired order. |
209 TEST_F(TabManagerTest, Comparator) { | 115 TEST_F(TabManagerTest, Comparator) { |
210 TabStatsList test_list; | 116 TabStatsList test_list; |
211 const base::TimeTicks now = base::TimeTicks::Now(); | 117 const base::TimeTicks now = base::TimeTicks::Now(); |
212 | 118 |
213 // Add kSelected last to verify that the array is being sorted. | 119 // Add kSelected last to verify that the array is being sorted. |
(...skipping 287 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
501 } | 407 } |
502 #else | 408 #else |
503 TEST_F(TabManagerTest, CanOnlyDiscardOnce) { | 409 TEST_F(TabManagerTest, CanOnlyDiscardOnce) { |
504 TabManager tab_manager; | 410 TabManager tab_manager; |
505 | 411 |
506 bool discard_once_value = tab_manager.CanOnlyDiscardOnce(); | 412 bool discard_once_value = tab_manager.CanOnlyDiscardOnce(); |
507 EXPECT_FALSE(discard_once_value); | 413 EXPECT_FALSE(discard_once_value); |
508 } | 414 } |
509 #endif // defined(OS_WIN) || defined(OS_MACOSX) | 415 #endif // defined(OS_WIN) || defined(OS_MACOSX) |
510 | 416 |
511 namespace { | |
512 | |
513 using MemoryPressureLevel = base::MemoryPressureListener::MemoryPressureLevel; | |
514 | |
515 // Function that always indicates the absence of memory pressure. | |
516 MemoryPressureLevel ReturnNoPressure() { | |
517 return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE; | |
518 } | |
519 | |
520 // Function that simply parrots back an externally specified memory pressure | |
521 // level. | |
522 MemoryPressureLevel ReturnSpecifiedPressure( | |
523 const MemoryPressureLevel* level) { | |
524 return *level; | |
525 } | |
526 | |
527 } // namespace | |
528 | |
529 // ChildProcessNotification is disabled on Chrome OS. crbug.com/588172. | |
530 #if defined(OS_CHROMEOS) | |
531 #define MAYBE_ChildProcessNotifications DISABLED_ChildProcessNotifications | |
532 #else | |
533 #define MAYBE_ChildProcessNotifications ChildProcessNotifications | |
534 #endif | |
535 | |
536 // Ensure that memory pressure notifications are forwarded to child processes. | |
537 TEST_F(TabManagerTest, MAYBE_ChildProcessNotifications) { | |
538 TabManager tm; | |
539 | |
540 // Set up the tab strip. | |
541 TabStripDummyDelegate delegate; | |
542 TabStripModel tabstrip(&delegate, profile()); | |
543 | |
544 // Create test clock, task runner. | |
545 base::SimpleTestTickClock test_clock; | |
546 MockTaskRunner mock_task_runner; | |
547 scoped_refptr<TaskRunnerProxy> task_runner( | |
548 new TaskRunnerProxy(&mock_task_runner, &test_clock)); | |
549 | |
550 // Configure the TabManager for testing. | |
551 tabstrip.AddObserver(&tm); | |
552 tm.test_tab_strip_models_.push_back( | |
553 TabManager::TestTabStripModel(&tabstrip, false /* !is_app */)); | |
554 tm.test_tick_clock_ = &test_clock; | |
555 tm.task_runner_ = task_runner; | |
556 tm.notify_renderer_process_ = base::Bind( | |
557 &TabManagerTest::NotifyRendererProcess, base::Unretained(this)); | |
558 | |
559 // Create two dummy tabs. | |
560 auto* tab0 = CreateWebContents(); | |
561 auto* tab1 = CreateWebContents(); | |
562 auto* tab2 = CreateWebContents(); | |
563 tabstrip.AppendWebContents(tab0, true); // Foreground tab. | |
564 tabstrip.AppendWebContents(tab1, false); // Background tab. | |
565 tabstrip.AppendWebContents(tab2, false); // Background tab. | |
566 const content::RenderProcessHost* renderer1 = tab1->GetRenderProcessHost(); | |
567 const content::RenderProcessHost* renderer2 = tab2->GetRenderProcessHost(); | |
568 | |
569 // Make sure that tab2 has a lower priority than tab1 by its access time. | |
570 test_clock.Advance(base::TimeDelta::FromMilliseconds(1)); | |
571 tab2->SetLastActiveTime(test_clock.NowTicks()); | |
572 test_clock.Advance(base::TimeDelta::FromMilliseconds(1)); | |
573 tab1->SetLastActiveTime(test_clock.NowTicks()); | |
574 | |
575 // Expect that the tab manager has not yet encountered memory pressure. | |
576 EXPECT_FALSE(tm.under_memory_pressure_); | |
577 EXPECT_EQ(0u, tm.notified_renderers_.size()); | |
578 | |
579 // Simulate a memory pressure situation that will immediately end. This should | |
580 // cause no notifications or scheduled tasks. | |
581 auto level = base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE; | |
582 tm.get_current_pressure_level_ = base::Bind(&ReturnNoPressure); | |
583 tm.OnMemoryPressure(level); | |
584 testing::Mock::VerifyAndClearExpectations(&mock_task_runner); | |
585 EXPECT_FALSE(tm.under_memory_pressure_); | |
586 EXPECT_EQ(0u, task_runner->size()); | |
587 EXPECT_EQ(0u, tm.notified_renderers_.size()); | |
588 | |
589 // START OF MEMORY PRESSURE | |
590 | |
591 // Simulate a memory pressure situation that persists. This should cause a | |
592 // task to be scheduled, and a background renderer to be notified. | |
593 tm.get_current_pressure_level_ = base::Bind( | |
594 &ReturnSpecifiedPressure, base::Unretained(&level)); | |
595 EXPECT_CALL(mock_task_runner, PostDelayedTask( | |
596 testing::_, | |
597 base::TimeDelta::FromSeconds(tm.kRendererNotificationDelayInSeconds))); | |
598 EXPECT_CALL(*this, NotifyRendererProcess(renderer2, level)); | |
599 tm.OnMemoryPressure(level); | |
600 testing::Mock::VerifyAndClearExpectations(&mock_task_runner); | |
601 testing::Mock::VerifyAndClearExpectations(this); | |
602 EXPECT_TRUE(tm.under_memory_pressure_); | |
603 EXPECT_EQ(1u, task_runner->size()); | |
604 EXPECT_EQ(1u, tm.notified_renderers_.size()); | |
605 EXPECT_EQ(1u, tm.notified_renderers_.count(renderer2)); | |
606 | |
607 // REPEATED MEMORY PRESSURE SIGNAL | |
608 | |
609 // Simulate another memory pressure event. This should not cause any | |
610 // additional tasks to be scheduled, nor any further renderer notifications. | |
611 tm.OnMemoryPressure(level); | |
612 testing::Mock::VerifyAndClearExpectations(&mock_task_runner); | |
613 testing::Mock::VerifyAndClearExpectations(this); | |
614 EXPECT_TRUE(tm.under_memory_pressure_); | |
615 EXPECT_EQ(1u, task_runner->size()); | |
616 EXPECT_EQ(1u, tm.notified_renderers_.size()); | |
617 | |
618 // FIRST SCHEDULED NOTIFICATION | |
619 | |
620 // Run the scheduled task. This should cause another notification, but this | |
621 // time to the foreground tab. It should also cause another scheduled event. | |
622 EXPECT_CALL(mock_task_runner, PostDelayedTask( | |
623 testing::_, | |
624 base::TimeDelta::FromSeconds(tm.kRendererNotificationDelayInSeconds))); | |
625 EXPECT_CALL(*this, NotifyRendererProcess(renderer1, level)); | |
626 EXPECT_EQ(1u, task_runner->RunNextTask()); | |
627 testing::Mock::VerifyAndClearExpectations(&mock_task_runner); | |
628 testing::Mock::VerifyAndClearExpectations(this); | |
629 EXPECT_TRUE(tm.under_memory_pressure_); | |
630 EXPECT_EQ(1u, task_runner->size()); | |
631 EXPECT_EQ(2u, tm.notified_renderers_.size()); | |
632 EXPECT_EQ(1u, tm.notified_renderers_.count(renderer1)); | |
633 | |
634 // SECOND SCHEDULED NOTIFICATION | |
635 | |
636 // Run the scheduled task. This should cause another notification, to the | |
637 // background tab. It should also cause another scheduled event. | |
638 EXPECT_CALL(mock_task_runner, PostDelayedTask( | |
639 testing::_, | |
640 base::TimeDelta::FromSeconds(tm.kRendererNotificationDelayInSeconds))); | |
641 EXPECT_CALL(*this, NotifyRendererProcess(renderer2, level)); | |
642 EXPECT_EQ(1u, task_runner->RunNextTask()); | |
643 testing::Mock::VerifyAndClearExpectations(&mock_task_runner); | |
644 testing::Mock::VerifyAndClearExpectations(this); | |
645 EXPECT_TRUE(tm.under_memory_pressure_); | |
646 EXPECT_EQ(1u, task_runner->size()); | |
647 EXPECT_EQ(1u, tm.notified_renderers_.size()); | |
648 | |
649 // Expect that the background tab has been notified. The list of notified | |
650 // renderers maintained by the TabManager should also have been reset because | |
651 // the notification logic restarts after all renderers are notified. | |
652 EXPECT_EQ(1u, tm.notified_renderers_.count(renderer2)); | |
653 | |
654 // END OF MEMORY PRESSURE | |
655 | |
656 // End the memory pressure condition and run the scheduled event. This should | |
657 // clean up the various state data. It should not schedule another event nor | |
658 // fire any notifications. | |
659 level = base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE; | |
660 EXPECT_EQ(1u, task_runner->RunNextTask()); | |
661 testing::Mock::VerifyAndClearExpectations(&mock_task_runner); | |
662 testing::Mock::VerifyAndClearExpectations(this); | |
663 EXPECT_FALSE(tm.under_memory_pressure_); | |
664 EXPECT_EQ(0u, task_runner->size()); | |
665 EXPECT_EQ(0u, tm.notified_renderers_.size()); | |
666 | |
667 | |
668 // Clean up the tabstrip. | |
669 tabstrip.CloseAllTabs(); | |
670 ASSERT_TRUE(tabstrip.empty()); | |
671 } | |
672 | |
673 TEST_F(TabManagerTest, DefaultTimeToPurgeInCorrectRange) { | 417 TEST_F(TabManagerTest, DefaultTimeToPurgeInCorrectRange) { |
674 TabManager tab_manager; | 418 TabManager tab_manager; |
675 base::TimeDelta time_to_purge = | 419 base::TimeDelta time_to_purge = |
676 tab_manager.GetTimeToPurge(TabManager::kDefaultMinTimeToPurge); | 420 tab_manager.GetTimeToPurge(TabManager::kDefaultMinTimeToPurge); |
677 EXPECT_GE(time_to_purge, base::TimeDelta::FromMinutes(30)); | 421 EXPECT_GE(time_to_purge, base::TimeDelta::FromMinutes(30)); |
678 EXPECT_LT(time_to_purge, base::TimeDelta::FromMinutes(60)); | 422 EXPECT_LT(time_to_purge, base::TimeDelta::FromMinutes(60)); |
679 } | 423 } |
680 | 424 |
681 TEST_F(TabManagerTest, ShouldPurgeAtDefaultTime) { | 425 TEST_F(TabManagerTest, ShouldPurgeAtDefaultTime) { |
682 TabManager tab_manager; | 426 TabManager tab_manager; |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
744 // Since tab2 is kept inactive and background for more than time-to-purge, | 488 // Since tab2 is kept inactive and background for more than time-to-purge, |
745 // tab2 should be purged. | 489 // tab2 should be purged. |
746 EXPECT_TRUE(tab_manager.GetWebContentsData(tab2)->is_purged()); | 490 EXPECT_TRUE(tab_manager.GetWebContentsData(tab2)->is_purged()); |
747 | 491 |
748 // Activate tab2. Tab2's PurgeAndSuspend state should be NOT_PURGED. | 492 // Activate tab2. Tab2's PurgeAndSuspend state should be NOT_PURGED. |
749 tabstrip.ActivateTabAt(1, true /* user_gesture */); | 493 tabstrip.ActivateTabAt(1, true /* user_gesture */); |
750 EXPECT_FALSE(tab_manager.GetWebContentsData(tab2)->is_purged()); | 494 EXPECT_FALSE(tab_manager.GetWebContentsData(tab2)->is_purged()); |
751 } | 495 } |
752 | 496 |
753 } // namespace memory | 497 } // namespace memory |
OLD | NEW |