blob: 409d35586b06595d9749ba97449ade565103ad68 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/memory/tab_manager.h"
#include <algorithm>
#include <map>
#include <vector>
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial.h"
#include "base/strings/string16.h"
#include "base/test/mock_entropy_provider.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/memory/tab_manager_web_contents_data.h"
#include "chrome/browser/memory/tab_stats.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/variations/variations_associated_data.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using content::WebContents;
using content::WebContentsTester;
namespace memory {
namespace {
class TabStripDummyDelegate : public TestTabStripModelDelegate {
public:
TabStripDummyDelegate() {}
bool RunUnloadListenerBeforeClosing(WebContents* contents) override {
return false;
}
private:
DISALLOW_COPY_AND_ASSIGN(TabStripDummyDelegate);
};
class MockTabStripModelObserver : public TabStripModelObserver {
public:
MockTabStripModelObserver()
: nb_events_(0), old_contents_(nullptr), new_contents_(nullptr) {}
int NbEvents() const { return nb_events_; }
WebContents* OldContents() const { return old_contents_; }
WebContents* NewContents() const { return new_contents_; }
void Reset() {
nb_events_ = 0;
old_contents_ = nullptr;
new_contents_ = nullptr;
}
// TabStripModelObserver implementation:
void TabReplacedAt(TabStripModel* tab_strip_model,
WebContents* old_contents,
WebContents* new_contents,
int index) override {
nb_events_++;
old_contents_ = old_contents;
new_contents_ = new_contents;
}
private:
int nb_events_;
WebContents* old_contents_;
WebContents* new_contents_;
DISALLOW_COPY_AND_ASSIGN(MockTabStripModelObserver);
};
// A mock task runner. This isn't directly a TaskRunner as the reference
// counting confuses gmock.
class LenientMockTaskRunner {
public:
LenientMockTaskRunner() {}
MOCK_METHOD3(PostDelayedTask,
bool(const tracked_objects::Location&,
const base::Closure&,
base::TimeDelta));
private:
DISALLOW_COPY_AND_ASSIGN(LenientMockTaskRunner);
};
using MockTaskRunner = testing::StrictMock<LenientMockTaskRunner>;
// Represents a pending task.
using Task = std::pair<base::TimeTicks, base::Closure>;
// Comparator used for sorting Task objects. Can't use std::pair's default
// comparison operators because Closure's are comparable.
struct TaskComparator {
bool operator()(const Task& t1, const Task& t2) const {
// Reverse the sort order so this can be used with a min-heap.
return t1.first > t2.first;
}
};
// A TaskRunner implementation that delegates to a MockTaskRunner for asserting
// on task insertion order, and which stores tasks for actual later execution.
class TaskRunnerProxy : public base::TaskRunner {
public:
TaskRunnerProxy(MockTaskRunner* mock,
base::SimpleTestTickClock* clock)
: mock_(mock), clock_(clock) {}
bool RunsTasksOnCurrentThread() const override { return true; }
bool PostDelayedTask(const tracked_objects::Location& location,
const base::Closure& closure,
base::TimeDelta delta) override {
mock_->PostDelayedTask(location, closure, delta);
base::TimeTicks when = clock_->NowTicks() + delta;
tasks_.push_back(Task(when, closure));
// Use 'greater' comparator to make this a min heap.
std::push_heap(tasks_.begin(), tasks_.end(), TaskComparator());
return true;
}
// Runs tasks up to the current time. Returns the number of tasks executed.
size_t RunTasks() {
base::TimeTicks now = clock_->NowTicks();
size_t count = 0;
while (!tasks_.empty() && tasks_.front().first <= now) {
tasks_.front().second.Run();
std::pop_heap(tasks_.begin(), tasks_.end(), TaskComparator());
tasks_.pop_back();
++count;
}
return count;
}
// Advances the clock and runs the next task in the queue. Can run more than
// one task if multiple tasks have the same scheduled time.
size_t RunNextTask() {
// Get the time of the next task that can possibly run.
base::TimeTicks when = tasks_.front().first;
// Advance the clock to exactly that time.
base::TimeTicks now = clock_->NowTicks();
if (now < when) {
base::TimeDelta delta = when - now;
clock_->Advance(delta);
}
// Run whatever tasks are now eligible to run.
return RunTasks();
}
size_t size() const { return tasks_.size(); }
private:
~TaskRunnerProxy() override {}
MockTaskRunner* mock_;
base::SimpleTestTickClock* clock_;
// A min-heap of outstanding tasks.
using Task = std::pair<base::TimeTicks, base::Closure>;
std::vector<Task> tasks_;
DISALLOW_COPY_AND_ASSIGN(TaskRunnerProxy);
};
enum TestIndicies {
kSelected,
kAutoDiscardable,
kPinned,
kApp,
kPlayingAudio,
kFormEntry,
kRecent,
kOld,
kReallyOld,
kOldButPinned,
kInternalPage,
};
} // namespace
class LenientTabManagerTest : public ChromeRenderViewHostTestHarness {
public:
WebContents* CreateWebContents() {
return WebContents::Create(WebContents::CreateParams(profile()));
}
MOCK_METHOD2(NotifyRendererProcess,
void(const content::RenderProcessHost*,
base::MemoryPressureListener::MemoryPressureLevel));
};
using TabManagerTest = testing::StrictMock<LenientTabManagerTest>;
// TODO(georgesak): Add tests for protection to tabs with form input and
// playing audio;
// Tests the sorting comparator to make sure it's producing the desired order.
TEST_F(TabManagerTest, Comparator) {
TabStatsList test_list;
const base::TimeTicks now = base::TimeTicks::Now();
// Add kSelected last to verify that the array is being sorted.
{
TabStats stats;
stats.last_active = now;
stats.is_pinned = true;
stats.child_process_host_id = kPinned;
test_list.push_back(stats);
}
{
TabStats stats;
stats.last_active = now;
stats.is_app = true;
stats.child_process_host_id = kApp;
test_list.push_back(stats);
}
{
TabStats stats;
stats.last_active = now;
stats.is_media = true;
stats.child_process_host_id = kPlayingAudio;
test_list.push_back(stats);
}
{
TabStats stats;
stats.last_active = now;
stats.has_form_entry = true;
stats.child_process_host_id = kFormEntry;
test_list.push_back(stats);
}
{
TabStats stats;
stats.last_active = now - base::TimeDelta::FromSeconds(10);
stats.child_process_host_id = kRecent;
test_list.push_back(stats);
}
{
TabStats stats;
stats.last_active = now - base::TimeDelta::FromMinutes(15);
stats.child_process_host_id = kOld;
test_list.push_back(stats);
}
{
TabStats stats;
stats.last_active = now - base::TimeDelta::FromDays(365);
stats.child_process_host_id = kReallyOld;
test_list.push_back(stats);
}
{
TabStats stats;
stats.is_pinned = true;
stats.last_active = now - base::TimeDelta::FromDays(365);
stats.child_process_host_id = kOldButPinned;
test_list.push_back(stats);
}
{
TabStats stats;
stats.last_active = now;
stats.is_internal_page = true;
stats.child_process_host_id = kInternalPage;
test_list.push_back(stats);
}
{
TabStats stats;
stats.last_active = now;
stats.is_auto_discardable = false;
stats.child_process_host_id = kAutoDiscardable;
test_list.push_back(stats);
}
// This entry sorts to the front, so by adding it last, it verifies that the
// array is being sorted.
{
TabStats stats;
stats.last_active = now;
stats.is_selected = true;
stats.child_process_host_id = kSelected;
test_list.push_back(stats);
}
std::sort(test_list.begin(), test_list.end(), TabManager::CompareTabStats);
int index = 0;
EXPECT_EQ(kSelected, test_list[index++].child_process_host_id);
EXPECT_EQ(kAutoDiscardable, test_list[index++].child_process_host_id);
EXPECT_EQ(kFormEntry, test_list[index++].child_process_host_id);
EXPECT_EQ(kPlayingAudio, test_list[index++].child_process_host_id);
EXPECT_EQ(kPinned, test_list[index++].child_process_host_id);
EXPECT_EQ(kOldButPinned, test_list[index++].child_process_host_id);
EXPECT_EQ(kApp, test_list[index++].child_process_host_id);
EXPECT_EQ(kRecent, test_list[index++].child_process_host_id);
EXPECT_EQ(kOld, test_list[index++].child_process_host_id);
EXPECT_EQ(kReallyOld, test_list[index++].child_process_host_id);
EXPECT_EQ(kInternalPage, test_list[index++].child_process_host_id);
}
TEST_F(TabManagerTest, IsInternalPage) {
EXPECT_TRUE(TabManager::IsInternalPage(GURL(chrome::kChromeUIDownloadsURL)));
EXPECT_TRUE(TabManager::IsInternalPage(GURL(chrome::kChromeUIHistoryURL)));
EXPECT_TRUE(TabManager::IsInternalPage(GURL(chrome::kChromeUINewTabURL)));
EXPECT_TRUE(TabManager::IsInternalPage(GURL(chrome::kChromeUISettingsURL)));
// Debugging URLs are not included.
#if defined(OS_CHROMEOS)
EXPECT_FALSE(TabManager::IsInternalPage(GURL(chrome::kChromeUIDiscardsURL)));
#endif
EXPECT_FALSE(
TabManager::IsInternalPage(GURL(chrome::kChromeUINetInternalsURL)));
// Prefix matches are included.
EXPECT_TRUE(
TabManager::IsInternalPage(GURL("chrome://settings/fakeSetting")));
}
// Ensures discarding tabs leaves TabStripModel in a good state.
TEST_F(TabManagerTest, DiscardWebContentsAt) {
TabManager tab_manager;
TabStripDummyDelegate delegate;
TabStripModel tabstrip(&delegate, profile());
tabstrip.AddObserver(&tab_manager);
// Fill it with some tabs.
WebContents* contents1 = CreateWebContents();
tabstrip.AppendWebContents(contents1, true);
WebContents* contents2 = CreateWebContents();
tabstrip.AppendWebContents(contents2, true);
// Start watching for events after the appends to avoid observing state
// transitions that aren't relevant to this test.
MockTabStripModelObserver tabstrip_observer;
tabstrip.AddObserver(&tabstrip_observer);
// Discard one of the tabs.
WebContents* null_contents1 = tab_manager.DiscardWebContentsAt(0, &tabstrip);
ASSERT_EQ(2, tabstrip.count());
EXPECT_TRUE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(0)));
EXPECT_FALSE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(1)));
ASSERT_EQ(null_contents1, tabstrip.GetWebContentsAt(0));
ASSERT_EQ(contents2, tabstrip.GetWebContentsAt(1));
ASSERT_EQ(1, tabstrip_observer.NbEvents());
EXPECT_EQ(contents1, tabstrip_observer.OldContents());
EXPECT_EQ(null_contents1, tabstrip_observer.NewContents());
tabstrip_observer.Reset();
// Discard the same tab again, after resetting its discard state.
tab_manager.GetWebContentsData(tabstrip.GetWebContentsAt(0))
->SetDiscardState(false);
WebContents* null_contents2 = tab_manager.DiscardWebContentsAt(0, &tabstrip);
ASSERT_EQ(2, tabstrip.count());
EXPECT_TRUE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(0)));
EXPECT_FALSE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(1)));
ASSERT_EQ(null_contents2, tabstrip.GetWebContentsAt(0));
ASSERT_EQ(contents2, tabstrip.GetWebContentsAt(1));
ASSERT_EQ(1, tabstrip_observer.NbEvents());
EXPECT_EQ(null_contents1, tabstrip_observer.OldContents());
EXPECT_EQ(null_contents2, tabstrip_observer.NewContents());
// Activating the tab should clear its discard state.
tabstrip.ActivateTabAt(0, true /* user_gesture */);
ASSERT_EQ(2, tabstrip.count());
EXPECT_FALSE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(0)));
EXPECT_FALSE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(1)));
// Don't discard active tab.
tab_manager.DiscardWebContentsAt(0, &tabstrip);
ASSERT_EQ(2, tabstrip.count());
EXPECT_FALSE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(0)));
EXPECT_FALSE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(1)));
tabstrip.CloseAllTabs();
EXPECT_TRUE(tabstrip.empty());
}
// Makes sure that reloading a discarded tab without activating it unmarks the
// tab as discarded so it won't reload on activation.
TEST_F(TabManagerTest, ReloadDiscardedTabContextMenu) {
// Note that we do not add |tab_manager| as an observer to |tabstrip| here as
// the event we are trying to test for is not related to the tab strip, but
// the web content instead and therefore should be handled by WebContentsData
// (which observes the web content).
TabManager tab_manager;
TabStripDummyDelegate delegate;
TabStripModel tabstrip(&delegate, profile());
// Create 2 tabs because the active tab cannot be discarded.
tabstrip.AppendWebContents(CreateWebContents(), true);
content::WebContents* test_contents =
WebContentsTester::CreateTestWebContents(browser_context(), nullptr);
tabstrip.AppendWebContents(test_contents, false); // Opened in background.
// Navigate to a web page. This is necessary to set a current entry in memory
// so the reload can happen.
WebContentsTester::For(test_contents)
->NavigateAndCommit(GURL("chrome://newtab"));
EXPECT_FALSE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(1)));
tab_manager.DiscardWebContentsAt(1, &tabstrip);
EXPECT_TRUE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(1)));
tabstrip.GetWebContentsAt(1)->GetController().Reload(false);
EXPECT_FALSE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(1)));
tabstrip.CloseAllTabs();
EXPECT_TRUE(tabstrip.empty());
}
// Makes sure that the last active time property is saved even though the tab is
// discarded.
TEST_F(TabManagerTest, DiscardedTabKeepsLastActiveTime) {
TabManager tab_manager;
TabStripDummyDelegate delegate;
TabStripModel tabstrip(&delegate, profile());
tabstrip.AddObserver(&tab_manager);
tabstrip.AppendWebContents(CreateWebContents(), true);
WebContents* test_contents = CreateWebContents();
tabstrip.AppendWebContents(test_contents, false);
// Simulate an old inactive tab about to get discarded.
base::TimeTicks new_last_active_time =
base::TimeTicks::Now() - base::TimeDelta::FromMinutes(35);
test_contents->SetLastActiveTime(new_last_active_time);
EXPECT_EQ(new_last_active_time, test_contents->GetLastActiveTime());
WebContents* null_contents = tab_manager.DiscardWebContentsAt(1, &tabstrip);
EXPECT_EQ(new_last_active_time, null_contents->GetLastActiveTime());
tabstrip.CloseAllTabs();
EXPECT_TRUE(tabstrip.empty());
}
// Test to see if a tab can only be discarded once. On Windows and Mac, this
// defaults to true unless overridden through a variation parameter. On other
// platforms, it's always false.
#if defined(OS_WIN) || defined(OS_MACOSX)
TEST_F(TabManagerTest, CanOnlyDiscardOnce) {
TabManager tab_manager;
const std::string kTrialName = features::kAutomaticTabDiscarding.name;
// Not setting the variation parameter.
{
bool discard_once_value = tab_manager.CanOnlyDiscardOnce();
EXPECT_TRUE(discard_once_value);
}
// Setting the variation parameter to true.
{
std::unique_ptr<base::FieldTrialList> field_trial_list_;
field_trial_list_.reset(
new base::FieldTrialList(
base::MakeUnique<base::MockEntropyProvider>()));
variations::testing::ClearAllVariationParams();
std::map<std::string, std::string> params;
params["AllowMultipleDiscards"] = "true";
ASSERT_TRUE(variations::AssociateVariationParams(kTrialName, "A", params));
base::FieldTrialList::CreateFieldTrial(kTrialName, "A");
bool discard_once_value = tab_manager.CanOnlyDiscardOnce();
EXPECT_FALSE(discard_once_value);
}
// Setting the variation parameter to something else.
{
std::unique_ptr<base::FieldTrialList> field_trial_list_;
field_trial_list_.reset(
new base::FieldTrialList(
base::MakeUnique<base::MockEntropyProvider>()));
variations::testing::ClearAllVariationParams();
std::map<std::string, std::string> params;
params["AllowMultipleDiscards"] = "somethingElse";
ASSERT_TRUE(variations::AssociateVariationParams(kTrialName, "B", params));
base::FieldTrialList::CreateFieldTrial(kTrialName, "B");
bool discard_once_value = tab_manager.CanOnlyDiscardOnce();
EXPECT_TRUE(discard_once_value);
}
}
#else
TEST_F(TabManagerTest, CanOnlyDiscardOnce) {
TabManager tab_manager;
bool discard_once_value = tab_manager.CanOnlyDiscardOnce();
EXPECT_FALSE(discard_once_value);
}
#endif // defined(OS_WIN) || defined(OS_MACOSX)
namespace {
using MemoryPressureLevel = base::MemoryPressureListener::MemoryPressureLevel;
// Function that always indicates the absence of memory pressure.
MemoryPressureLevel ReturnNoPressure() {
return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
}
// Function that simply parrots back an externally specified memory pressure
// level.
MemoryPressureLevel ReturnSpecifiedPressure(
const MemoryPressureLevel* level) {
return *level;
}
} // namespace
// ChildProcessNotification is disabled on Chrome OS. crbug.com/588172.
#if defined(OS_CHROMEOS)
#define MAYBE_ChildProcessNotifications DISABLED_ChildProcessNotifications
#else
#define MAYBE_ChildProcessNotifications ChildProcessNotifications
#endif
// Ensure that memory pressure notifications are forwarded to child processes.
TEST_F(TabManagerTest, MAYBE_ChildProcessNotifications) {
TabManager tm;
// Set up the tab strip.
TabStripDummyDelegate delegate;
TabStripModel tabstrip(&delegate, profile());
// Create test clock, task runner.
base::SimpleTestTickClock test_clock;
MockTaskRunner mock_task_runner;
scoped_refptr<TaskRunnerProxy> task_runner(
new TaskRunnerProxy(&mock_task_runner, &test_clock));
// Configure the TabManager for testing.
tabstrip.AddObserver(&tm);
tm.test_tab_strip_models_.push_back(
TabManager::TestTabStripModel(&tabstrip, false /* !is_app */));
tm.test_tick_clock_ = &test_clock;
tm.task_runner_ = task_runner;
tm.notify_renderer_process_ = base::Bind(
&TabManagerTest::NotifyRendererProcess, base::Unretained(this));
// Create two dummy tabs.
auto* tab0 = CreateWebContents();
auto* tab1 = CreateWebContents();
auto* tab2 = CreateWebContents();
tabstrip.AppendWebContents(tab0, true); // Foreground tab.
tabstrip.AppendWebContents(tab1, false); // Background tab.
tabstrip.AppendWebContents(tab2, false); // Background tab.
const content::RenderProcessHost* renderer1 = tab1->GetRenderProcessHost();
const content::RenderProcessHost* renderer2 = tab2->GetRenderProcessHost();
// Make sure that tab2 has a lower priority than tab1 by its access time.
test_clock.Advance(base::TimeDelta::FromMilliseconds(1));
tab2->SetLastActiveTime(test_clock.NowTicks());
test_clock.Advance(base::TimeDelta::FromMilliseconds(1));
tab1->SetLastActiveTime(test_clock.NowTicks());
// Expect that the tab manager has not yet encountered memory pressure.
EXPECT_FALSE(tm.under_memory_pressure_);
EXPECT_EQ(0u, tm.notified_renderers_.size());
// Simulate a memory pressure situation that will immediately end. This should
// cause no notifications or scheduled tasks.
auto level = base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE;
tm.get_current_pressure_level_ = base::Bind(&ReturnNoPressure);
tm.OnMemoryPressure(level);
testing::Mock::VerifyAndClearExpectations(&mock_task_runner);
EXPECT_FALSE(tm.under_memory_pressure_);
EXPECT_EQ(0u, task_runner->size());
EXPECT_EQ(0u, tm.notified_renderers_.size());
// START OF MEMORY PRESSURE
// Simulate a memory pressure situation that persists. This should cause a
// task to be scheduled, and a background renderer to be notified.
tm.get_current_pressure_level_ = base::Bind(
&ReturnSpecifiedPressure, base::Unretained(&level));
EXPECT_CALL(mock_task_runner, PostDelayedTask(
testing::_,
testing::_,
base::TimeDelta::FromSeconds(tm.kRendererNotificationDelayInSeconds)));
EXPECT_CALL(*this, NotifyRendererProcess(renderer2, level));
tm.OnMemoryPressure(level);
testing::Mock::VerifyAndClearExpectations(&mock_task_runner);
testing::Mock::VerifyAndClearExpectations(this);
EXPECT_TRUE(tm.under_memory_pressure_);
EXPECT_EQ(1u, task_runner->size());
EXPECT_EQ(1u, tm.notified_renderers_.size());
EXPECT_EQ(1u, tm.notified_renderers_.count(renderer2));
// REPEATED MEMORY PRESSURE SIGNAL
// Simulate another memory pressure event. This should not cause any
// additional tasks to be scheduled, nor any further renderer notifications.
tm.OnMemoryPressure(level);
testing::Mock::VerifyAndClearExpectations(&mock_task_runner);
testing::Mock::VerifyAndClearExpectations(this);
EXPECT_TRUE(tm.under_memory_pressure_);
EXPECT_EQ(1u, task_runner->size());
EXPECT_EQ(1u, tm.notified_renderers_.size());
// FIRST SCHEDULED NOTIFICATION
// Run the scheduled task. This should cause another notification, but this
// time to the foreground tab. It should also cause another scheduled event.
EXPECT_CALL(mock_task_runner, PostDelayedTask(
testing::_,
testing::_,
base::TimeDelta::FromSeconds(tm.kRendererNotificationDelayInSeconds)));
EXPECT_CALL(*this, NotifyRendererProcess(renderer1, level));
EXPECT_EQ(1u, task_runner->RunNextTask());
testing::Mock::VerifyAndClearExpectations(&mock_task_runner);
testing::Mock::VerifyAndClearExpectations(this);
EXPECT_TRUE(tm.under_memory_pressure_);
EXPECT_EQ(1u, task_runner->size());
EXPECT_EQ(2u, tm.notified_renderers_.size());
EXPECT_EQ(1u, tm.notified_renderers_.count(renderer1));
// SECOND SCHEDULED NOTIFICATION
// Run the scheduled task. This should cause another notification, to the
// background tab. It should also cause another scheduled event.
EXPECT_CALL(mock_task_runner, PostDelayedTask(
testing::_,
testing::_,
base::TimeDelta::FromSeconds(tm.kRendererNotificationDelayInSeconds)));
EXPECT_CALL(*this, NotifyRendererProcess(renderer2, level));
EXPECT_EQ(1u, task_runner->RunNextTask());
testing::Mock::VerifyAndClearExpectations(&mock_task_runner);
testing::Mock::VerifyAndClearExpectations(this);
EXPECT_TRUE(tm.under_memory_pressure_);
EXPECT_EQ(1u, task_runner->size());
EXPECT_EQ(1u, tm.notified_renderers_.size());
// Expect that the background tab has been notified. The list of notified
// renderers maintained by the TabManager should also have been reset because
// the notification logic restarts after all renderers are notified.
EXPECT_EQ(1u, tm.notified_renderers_.count(renderer2));
// END OF MEMORY PRESSURE
// End the memory pressure condition and run the scheduled event. This should
// clean up the various state data. It should not schedule another event nor
// fire any notifications.
level = base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
EXPECT_EQ(1u, task_runner->RunNextTask());
testing::Mock::VerifyAndClearExpectations(&mock_task_runner);
testing::Mock::VerifyAndClearExpectations(this);
EXPECT_FALSE(tm.under_memory_pressure_);
EXPECT_EQ(0u, task_runner->size());
EXPECT_EQ(0u, tm.notified_renderers_.size());
// Clean up the tabstrip.
tabstrip.CloseAllTabs();
ASSERT_TRUE(tabstrip.empty());
}
TEST_F(TabManagerTest, NextPurgeAndSuspendState) {
TabManager tab_manager;
TabStripDummyDelegate delegate;
TabStripModel tabstrip(&delegate, profile());
tabstrip.AddObserver(&tab_manager);
WebContents* test_contents = CreateWebContents();
tabstrip.AppendWebContents(test_contents, false);
base::TimeDelta threshold = base::TimeDelta::FromSeconds(180);
base::SimpleTestTickClock test_clock;
tab_manager.GetWebContentsData(test_contents)
->SetPurgeAndSuspendState(TabManager::RUNNING);
tab_manager.GetWebContentsData(test_contents)
->SetLastPurgeAndSuspendModifiedTimeForTesting(test_clock.NowTicks());
test_clock.Advance(base::TimeDelta::FromSeconds(180));
EXPECT_EQ(TabManager::RUNNING,
tab_manager.GetNextPurgeAndSuspendState(
test_contents, test_clock.NowTicks(), threshold));
test_clock.Advance(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(TabManager::SUSPENDED,
tab_manager.GetNextPurgeAndSuspendState(
test_contents, test_clock.NowTicks(), threshold));
tab_manager.GetWebContentsData(test_contents)
->SetPurgeAndSuspendState(TabManager::SUSPENDED);
tab_manager.GetWebContentsData(test_contents)
->SetLastPurgeAndSuspendModifiedTimeForTesting(test_clock.NowTicks());
test_clock.Advance(base::TimeDelta::FromSeconds(120));
EXPECT_EQ(TabManager::SUSPENDED,
tab_manager.GetNextPurgeAndSuspendState(
test_contents, test_clock.NowTicks(), threshold));
test_clock.Advance(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(TabManager::RESUMED,
tab_manager.GetNextPurgeAndSuspendState(
test_contents, test_clock.NowTicks(), threshold));
tab_manager.GetWebContentsData(test_contents)
->SetPurgeAndSuspendState(TabManager::RESUMED);
tab_manager.GetWebContentsData(test_contents)
->SetLastPurgeAndSuspendModifiedTimeForTesting(test_clock.NowTicks());
test_clock.Advance(base::TimeDelta::FromSeconds(10));
EXPECT_EQ(TabManager::RESUMED,
tab_manager.GetNextPurgeAndSuspendState(
test_contents, test_clock.NowTicks(), threshold));
test_clock.Advance(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(TabManager::SUSPENDED,
tab_manager.GetNextPurgeAndSuspendState(
test_contents, test_clock.NowTicks(), threshold));
// Clean up the tabstrip.
tabstrip.CloseAllTabs();
EXPECT_TRUE(tabstrip.empty());
}
TEST_F(TabManagerTest, ActivateTabResetPurgeAndSuspendState) {
TabManager tab_manager;
TabStripDummyDelegate delegate;
TabStripModel tabstrip(&delegate, profile());
tabstrip.AddObserver(&tab_manager);
WebContents* tab1 = CreateWebContents();
WebContents* tab2 = CreateWebContents();
tabstrip.AppendWebContents(tab1, true);
tabstrip.AppendWebContents(tab2, false);
base::SimpleTestTickClock test_clock;
// Initially PurgeAndSuspend state should be RUNNING.
EXPECT_EQ(TabManager::RUNNING,
tab_manager.GetWebContentsData(tab2)->GetPurgeAndSuspendState());
tab_manager.GetWebContentsData(tab2)->SetPurgeAndSuspendState(
TabManager::SUSPENDED);
tab_manager.GetWebContentsData(tab2)
->SetLastPurgeAndSuspendModifiedTimeForTesting(test_clock.NowTicks());
// Activate tab2. Tab2's PurgeAndSuspend state should be RUNNING.
tabstrip.ActivateTabAt(1, true /* user_gesture */);
EXPECT_EQ(TabManager::RUNNING,
tab_manager.GetWebContentsData(tab2)->GetPurgeAndSuspendState());
tab_manager.GetWebContentsData(tab1)->SetPurgeAndSuspendState(
TabManager::RESUMED);
tab_manager.GetWebContentsData(tab1)
->SetLastPurgeAndSuspendModifiedTimeForTesting(test_clock.NowTicks());
// Activate tab1. Tab1's PurgeAndSuspend state should be RUNNING.
tabstrip.ActivateTabAt(0, true /* user_gesture */);
EXPECT_EQ(TabManager::RUNNING,
tab_manager.GetWebContentsData(tab1)->GetPurgeAndSuspendState());
// Clean up the tabstrip.
tabstrip.CloseAllTabs();
EXPECT_TRUE(tabstrip.empty());
}
} // namespace memory