| 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 |