blob: 974d8a6951700957c48aeddf0cb18816487199ad [file] [log] [blame]
// Copyright 2015 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.
#import <Cocoa/Cocoa.h>
#include <stddef.h>
#include <algorithm>
#include "base/macros.h"
#include "base/strings/pattern.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "chrome/browser/task_manager/task_manager_browsertest_util.h"
#include "chrome/browser/task_manager/task_manager_tester.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/cocoa/task_manager_mac.h"
#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
#include "chrome/browser/ui/task_manager/task_manager_columns.h"
#include "chrome/browser/ui/task_manager/task_manager_table_model.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest_mac.h"
namespace task_manager {
using browsertest_util::WaitForTaskManagerRows;
class TaskManagerMacTest : public InProcessBrowserTest {
public:
TaskManagerMacTest() {}
~TaskManagerMacTest() override {}
void SetUpOnMainThread() override {
ASSERT_FALSE(GetTaskManagerMac());
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
}
void TearDownOnMainThread() override {
// Make sure the task manager is closed (if any).
chrome::HideTaskManager();
ASSERT_FALSE(GetTaskManagerMac());
}
TaskManagerMac* GetTaskManagerMac() const {
return TaskManagerMac::GetInstanceForTests();
}
NSTableView* GetTable() const {
return GetTaskManagerMac() ? [GetTaskManagerMac()->CocoaControllerForTests()
tableViewForTesting]
: nullptr;
}
int TableFirstSelectedRow() const {
return [[GetTable() selectedRowIndexes] firstIndex];
}
void PressKillButton() {
[[GetTaskManagerMac()->CocoaControllerForTests() endProcessButtonForTesting]
performClick:nil];
}
void ClearStoredColumnSettings() const {
PrefService* local_state = g_browser_process->local_state();
if (!local_state)
FAIL();
DictionaryPrefUpdate dict_update(local_state,
prefs::kTaskManagerColumnVisibility);
dict_update->Clear();
}
void ToggleColumnVisibility(TaskManagerMac* task_manager, int col_id) {
DCHECK(task_manager);
task_manager->GetTableModelForTests()->ToggleColumnVisibility(col_id);
}
// Looks up a tab based on its tab ID.
content::WebContents* FindWebContentsByTabId(SessionID tab_id) {
auto& all_tabs = AllTabContentses();
auto tab_id_matches = [tab_id](content::WebContents* web_contents) {
return SessionTabHelper::IdForTab(web_contents) == tab_id;
};
auto it = std::find_if(all_tabs.begin(), all_tabs.end(), tab_id_matches);
return (it == all_tabs.end()) ? nullptr : *it;
}
// Returns the current TaskManagerTableModel index for a particular tab. Don't
// cache this value, since it can change whenever the message loop runs.
int FindRowForTab(content::WebContents* tab) {
SessionID tab_id = SessionTabHelper::IdForTab(tab);
std::unique_ptr<TaskManagerTester> tester =
TaskManagerTester::Create(base::Closure());
for (int i = 0; i < tester->GetRowCount(); ++i) {
if (tester->GetTabId(i) == tab_id)
return i;
}
return -1;
}
private:
DISALLOW_COPY_AND_ASSIGN(TaskManagerMacTest);
};
// Tests that all defined columns have a corresponding string IDs for keying
// into the user preferences dictionary.
IN_PROC_BROWSER_TEST_F(TaskManagerMacTest, AllColumnsHaveStringIds) {
for (size_t i = 0; i < kColumnsSize; ++i)
EXPECT_NE("", GetColumnIdAsString(kColumns[i].id));
}
// In the case of no settings stored in the user preferences local store, test
// that the task manager table starts with the default columns visibility as
// stored in |kColumns|.
IN_PROC_BROWSER_TEST_F(TaskManagerMacTest, TableStartsWithDefaultColumns) {
ASSERT_NO_FATAL_FAILURE(ClearStoredColumnSettings());
chrome::ShowTaskManager(browser());
NSTableView* table = GetTable();
ASSERT_TRUE(table);
EXPECT_EQ(0u, [[table sortDescriptors] count]);
NSArray* tableColumns = [table tableColumns];
for (size_t i = 0; i < kColumnsSize; ++i) {
EXPECT_EQ(kColumns[i].id,
[[[tableColumns objectAtIndex:i] identifier] intValue]);
EXPECT_EQ(kColumns[i].default_visibility,
![[tableColumns objectAtIndex:i] isHidden]);
}
}
// Tests that changing columns visibility and sort order will be stored upon
// closing the task manager view and restored when re-opened.
IN_PROC_BROWSER_TEST_F(TaskManagerMacTest, ColumnsSettingsAreRestored) {
ASSERT_NO_FATAL_FAILURE(ClearStoredColumnSettings());
chrome::ShowTaskManager(browser());
TaskManagerMac* task_manager = GetTaskManagerMac();
ASSERT_TRUE(task_manager);
NSTableView* table = GetTable();
ASSERT_TRUE(table);
// Toggle the visibility of all columns.
EXPECT_EQ(0u, [[table sortDescriptors] count]);
NSArray* tableColumns = [table tableColumns];
for (size_t i = 0; i < kColumnsSize; ++i) {
EXPECT_EQ(kColumns[i].id,
[[[tableColumns objectAtIndex:i] identifier] intValue]);
EXPECT_EQ(kColumns[i].default_visibility,
![[tableColumns objectAtIndex:i] isHidden]);
ToggleColumnVisibility(task_manager, kColumns[i].id);
}
// Sort by the first visible and initially ascending sortable column. It would
// be nice to fake a click with -performClick: but that doesn't work (see
// http://www.cocoabuilder.com/archive/cocoa/177610-programmatically-click-column-header-in-nstableview.html).
bool is_sorted = false;
int sorted_col_id = -1;
for (NSTableColumn* column in tableColumns) {
if ([column isHidden])
continue;
if ([column sortDescriptorPrototype].ascending) {
// Toggle the sort for a descending sort.
NSSortDescriptor* newSortDescriptor =
[[column sortDescriptorPrototype] reversedSortDescriptor];
[table setSortDescriptors:@[ newSortDescriptor ]];
is_sorted = true;
sorted_col_id = [[column identifier] intValue];
break;
}
}
NSArray* expectedSortDescriptors = [table sortDescriptors];
EXPECT_EQ(is_sorted, [expectedSortDescriptors count] > 0);
// Close the task manager view and re-open. Expect the inverse of the default
// visibility, and the last sort order.
chrome::HideTaskManager();
ASSERT_FALSE(GetTaskManagerMac());
chrome::ShowTaskManager(browser());
task_manager = GetTaskManagerMac();
ASSERT_TRUE(task_manager);
table = GetTable();
ASSERT_TRUE(table);
if (is_sorted) {
EXPECT_NSEQ(expectedSortDescriptors, [table sortDescriptors]);
}
}
IN_PROC_BROWSER_TEST_F(TaskManagerMacTest, SelectionConsistency) {
ASSERT_NO_FATAL_FAILURE(ClearStoredColumnSettings());
chrome::ShowTaskManager(browser());
// Set up a total of three tabs in different processes.
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("a.com", "/title2.html"));
ui_test_utils::NavigateToURLWithDisposition(
browser(), embedded_test_server()->GetURL("b.com", "/title2.html"),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
ui_test_utils::NavigateToURLWithDisposition(
browser(), embedded_test_server()->GetURL("c.com", "/title2.html"),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
// Wait for their titles to appear in the TaskManager. There should be three
// rows.
auto pattern = browsertest_util::MatchTab("Title *");
int rows = 3;
ASSERT_NO_FATAL_FAILURE(WaitForTaskManagerRows(rows, pattern));
// Find the three tabs we set up, in TaskManager model order. Because we have
// not sorted the table yet, this should also be their UI display order.
std::unique_ptr<TaskManagerTester> tester =
TaskManagerTester::Create(base::Closure());
std::vector<content::WebContents*> tabs;
for (int i = 0; i < tester->GetRowCount(); ++i) {
// Filter based on our title.
if (!base::MatchPattern(tester->GetRowTitle(i), pattern))
continue;
content::WebContents* tab = FindWebContentsByTabId(tester->GetTabId(i));
EXPECT_NE(nullptr, tab);
tabs.push_back(tab);
}
EXPECT_EQ(3U, tabs.size());
// Select the middle row, and store its tab id.
[GetTable()
selectRowIndexes:[NSIndexSet indexSetWithIndex:FindRowForTab(tabs[1])]
byExtendingSelection:NO];
EXPECT_EQ(TableFirstSelectedRow(), FindRowForTab(tabs[1]));
EXPECT_EQ(1, [GetTable() numberOfSelectedRows]);
// Add 3 rows above the selection. The selected tab should not change.
for (int i = 0; i < 3; ++i) {
ASSERT_TRUE(content::ExecuteScript(tabs[0], "window.open('title3.html');"));
EXPECT_EQ(TableFirstSelectedRow(), FindRowForTab(tabs[1]));
}
ASSERT_NO_FATAL_FAILURE(WaitForTaskManagerRows((rows += 3), pattern));
EXPECT_EQ(TableFirstSelectedRow(), FindRowForTab(tabs[1]));
EXPECT_EQ(1, [GetTable() numberOfSelectedRows]);
// Add 2 rows below the selection. The selected tab should not change.
for (int i = 0; i < 2; ++i) {
ASSERT_TRUE(content::ExecuteScript(tabs[2], "window.open('title3.html');"));
EXPECT_EQ(TableFirstSelectedRow(), FindRowForTab(tabs[1]));
}
ASSERT_NO_FATAL_FAILURE(WaitForTaskManagerRows((rows += 2), pattern));
EXPECT_EQ(TableFirstSelectedRow(), FindRowForTab(tabs[1]));
EXPECT_EQ(1, [GetTable() numberOfSelectedRows]);
// Add a new row in the same process as the selection. The selected tab should
// not change.
ASSERT_TRUE(content::ExecuteScript(tabs[1], "window.open('title3.html');"));
EXPECT_EQ(TableFirstSelectedRow(), FindRowForTab(tabs[1]));
ASSERT_NO_FATAL_FAILURE(WaitForTaskManagerRows((rows += 1), pattern));
EXPECT_EQ(TableFirstSelectedRow(), FindRowForTab(tabs[1]));
EXPECT_EQ(2, [GetTable() numberOfSelectedRows]);
// Press the button, which kills the process of the selected row.
PressKillButton();
// Two rows should disappear.
ASSERT_NO_FATAL_FAILURE(WaitForTaskManagerRows((rows -= 2), pattern));
// No row should now be selected.
ASSERT_EQ(-1, TableFirstSelectedRow());
// Now select tabs[2].
[GetTable()
selectRowIndexes:[NSIndexSet indexSetWithIndex:FindRowForTab(tabs[2])]
byExtendingSelection:NO];
// Focus and reload one of the sad tabs. It should reappear in the TM. The
// other sad tab should not reappear.
tabs[1]->GetDelegate()->ActivateContents(tabs[1]);
chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
ASSERT_NO_FATAL_FAILURE(WaitForTaskManagerRows((rows += 1), pattern));
// tabs[2] should still be selected.
EXPECT_EQ(TableFirstSelectedRow(), FindRowForTab(tabs[2]));
// Close tabs[0]. The selection should not change.
chrome::CloseWebContents(browser(), tabs[0], false);
ASSERT_NO_FATAL_FAILURE(WaitForTaskManagerRows((rows -= 1), pattern));
EXPECT_EQ(TableFirstSelectedRow(), FindRowForTab(tabs[2]));
}
} // namespace task_manager