blob: 719afca4859fa9b267e4e3ef41a344c88252c032 [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 <vector>
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/string16.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/url_constants.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/web_contents_tester.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);
};
enum TestIndicies {
kSelected,
kPinned,
kApp,
kPlayingAudio,
kFormEntry,
kRecent,
kOld,
kReallyOld,
kOldButPinned,
kInternalPage,
};
} // namespace
class TabManagerTest : public ChromeRenderViewHostTestHarness {
public:
WebContents* CreateWebContents() {
return WebContents::Create(WebContents::CreateParams(profile()));
}
};
// 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);
}
// 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(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());
}
} // namespace memory