| // 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. |
| |
| #include <stddef.h> |
| |
| #include "base/macros.h" |
| #include "base/strings/pattern.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/browser_process.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.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/browser_window.h" |
| #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/task_manager/task_manager_columns.h" |
| #include "chrome/browser/ui/task_manager/task_manager_table_model.h" |
| #include "chrome/browser/ui/views/task_manager_view.h" |
| #include "chrome/common/chrome_features.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 "components/sessions/content/session_tab_helper.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_delegate.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/no_renderer_crashes_assertion.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 "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/events/test/event_generator.h" |
| #include "ui/views/controls/table/table_view.h" |
| #include "ui/views/test/widget_test.h" |
| |
| namespace task_manager { |
| |
| using browsertest_util::WaitForTaskManagerRows; |
| |
| class TaskManagerViewTest : public InProcessBrowserTest { |
| public: |
| TaskManagerViewTest() { |
| #if defined(OS_MAC) |
| feature_list_.InitAndEnableFeature(features::kViewsTaskManager); |
| #endif |
| } |
| ~TaskManagerViewTest() override {} |
| |
| void SetUpOnMainThread() override { |
| 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(); |
| content::RunAllPendingInMessageLoop(); |
| ASSERT_FALSE(GetView()); |
| } |
| |
| TaskManagerView* GetView() const { |
| return TaskManagerView::GetInstanceForTests(); |
| } |
| |
| views::TableView* GetTable() const { |
| return GetView() ? GetView()->tab_table_ : nullptr; |
| } |
| |
| void PressKillButton() { GetView()->Accept(); } |
| |
| 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(TaskManagerView* view, int col_id) { |
| DCHECK(view); |
| view->table_model_->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 sessions::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 = sessions::SessionTabHelper::IdForTab(tab); |
| std::unique_ptr<TaskManagerTester> tester = |
| TaskManagerTester::Create(base::RepeatingClosure()); |
| for (int i = 0; i < tester->GetRowCount(); ++i) { |
| if (tester->GetTabId(i) == tab_id) |
| return i; |
| } |
| return -1; |
| } |
| |
| void HideTaskManagerSync() { |
| views::test::WidgetDestroyedWaiter waiter(GetView()->GetWidget()); |
| chrome::HideTaskManager(); |
| waiter.Wait(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TaskManagerViewTest); |
| }; |
| |
| // Tests that all defined columns have a corresponding string IDs for keying |
| // into the user preferences dictionary. |
| IN_PROC_BROWSER_TEST_F(TaskManagerViewTest, 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(TaskManagerViewTest, TableStartsWithDefaultColumns) { |
| ASSERT_NO_FATAL_FAILURE(ClearStoredColumnSettings()); |
| |
| chrome::ShowTaskManager(browser()); |
| views::TableView* table = GetTable(); |
| ASSERT_TRUE(table); |
| |
| EXPECT_FALSE(table->GetIsSorted()); |
| for (size_t i = 0; i < kColumnsSize; ++i) { |
| EXPECT_EQ(kColumns[i].default_visibility, |
| table->IsColumnVisible(kColumns[i].id)); |
| } |
| } |
| |
| // 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(TaskManagerViewTest, ColumnsSettingsAreRestored) { |
| ASSERT_NO_FATAL_FAILURE(ClearStoredColumnSettings()); |
| |
| chrome::ShowTaskManager(browser()); |
| TaskManagerView* view = GetView(); |
| ASSERT_TRUE(view); |
| views::TableView* table = GetTable(); |
| ASSERT_TRUE(table); |
| |
| // Toggle the visibility of all columns. |
| EXPECT_FALSE(table->GetIsSorted()); |
| for (size_t i = 0; i < kColumnsSize; ++i) { |
| EXPECT_EQ(kColumns[i].default_visibility, |
| table->IsColumnVisible(kColumns[i].id)); |
| ToggleColumnVisibility(view, kColumns[i].id); |
| } |
| |
| // Sort by the first visible and initially ascending sortable column. |
| bool is_sorted = false; |
| int sorted_col_id = -1; |
| for (size_t i = 0; i < table->visible_columns().size(); ++i) { |
| const ui::TableColumn& column = table->visible_columns()[i].column; |
| if (column.sortable && column.initial_sort_is_ascending) { |
| // Toggle the sort twice for a descending sort. |
| table->ToggleSortOrder(static_cast<int>(i)); |
| table->ToggleSortOrder(static_cast<int>(i)); |
| is_sorted = true; |
| sorted_col_id = column.id; |
| break; |
| } |
| } |
| |
| if (is_sorted) { |
| EXPECT_TRUE(table->GetIsSorted()); |
| EXPECT_FALSE(table->sort_descriptors().front().ascending); |
| EXPECT_EQ(table->sort_descriptors().front().column_id, sorted_col_id); |
| } |
| |
| // Close the task manager view and re-open. Expect the inverse of the default |
| // visibility, and the last sort order. |
| chrome::HideTaskManager(); |
| content::RunAllPendingInMessageLoop(); |
| ASSERT_FALSE(GetView()); |
| chrome::ShowTaskManager(browser()); |
| view = GetView(); |
| ASSERT_TRUE(view); |
| table = GetTable(); |
| ASSERT_TRUE(table); |
| |
| if (is_sorted) { |
| EXPECT_TRUE(table->GetIsSorted()); |
| EXPECT_FALSE(table->sort_descriptors().front().ascending); |
| EXPECT_EQ(table->sort_descriptors().front().column_id, sorted_col_id); |
| } |
| for (size_t i = 0; i < kColumnsSize; ++i) { |
| EXPECT_EQ(!kColumns[i].default_visibility, |
| table->IsColumnVisible(kColumns[i].id)); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TaskManagerViewTest, InitialSelection) { |
| // Navigate the first tab. |
| ui_test_utils::NavigateToURL( |
| browser(), embedded_test_server()->GetURL("a.com", "/title2.html")); |
| |
| ui_test_utils::NavigateToURLWithDisposition( |
| browser(), embedded_test_server()->GetURL("b.com", "/title3.html"), |
| WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); |
| |
| // When the task manager is initially shown, the row for the active tab should |
| // be selected. |
| chrome::ShowTaskManager(browser()); |
| |
| EXPECT_EQ(1UL, GetTable()->selection_model().size()); |
| EXPECT_EQ(GetTable()->GetFirstSelectedRow(), |
| FindRowForTab(browser()->tab_strip_model()->GetWebContentsAt(1))); |
| |
| // Activate tab 0. The selection should not change. |
| browser()->tab_strip_model()->ActivateTabAt( |
| 0, {TabStripModel::GestureType::kOther}); |
| EXPECT_EQ(1UL, GetTable()->selection_model().size()); |
| EXPECT_EQ(GetTable()->GetFirstSelectedRow(), |
| FindRowForTab(browser()->tab_strip_model()->GetWebContentsAt(1))); |
| |
| // If the user re-triggers chrome::ShowTaskManager (e.g. via shift-esc), this |
| // should set the TaskManager selection to the active tab. |
| chrome::ShowTaskManager(browser()); |
| |
| EXPECT_EQ(1UL, GetTable()->selection_model().size()); |
| EXPECT_EQ(GetTable()->GetFirstSelectedRow(), |
| FindRowForTab(browser()->tab_strip_model()->GetWebContentsAt(0))); |
| } |
| |
| // Test is flaky. https://crbug.com/998403 |
| IN_PROC_BROWSER_TEST_F(TaskManagerViewTest, DISABLED_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_LOAD_STOP); |
| ui_test_utils::NavigateToURLWithDisposition( |
| browser(), embedded_test_server()->GetURL("c.com", "/title2.html"), |
| WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); |
| |
| // 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::RepeatingClosure()); |
| 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()->Select(FindRowForTab(tabs[1])); |
| EXPECT_EQ(GetTable()->GetFirstSelectedRow(), FindRowForTab(tabs[1])); |
| EXPECT_EQ(1UL, GetTable()->selection_model().size()); |
| |
| // 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(GetTable()->GetFirstSelectedRow(), FindRowForTab(tabs[1])); |
| } |
| ASSERT_NO_FATAL_FAILURE(WaitForTaskManagerRows((rows += 3), pattern)); |
| EXPECT_EQ(GetTable()->GetFirstSelectedRow(), FindRowForTab(tabs[1])); |
| EXPECT_EQ(1UL, GetTable()->selection_model().size()); |
| |
| // 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(GetTable()->GetFirstSelectedRow(), FindRowForTab(tabs[1])); |
| } |
| ASSERT_NO_FATAL_FAILURE(WaitForTaskManagerRows((rows += 2), pattern)); |
| EXPECT_EQ(GetTable()->GetFirstSelectedRow(), FindRowForTab(tabs[1])); |
| EXPECT_EQ(1UL, GetTable()->selection_model().size()); |
| |
| // 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(GetTable()->GetFirstSelectedRow(), FindRowForTab(tabs[1])); |
| ASSERT_NO_FATAL_FAILURE(WaitForTaskManagerRows((rows += 1), pattern)); |
| EXPECT_EQ(GetTable()->GetFirstSelectedRow(), FindRowForTab(tabs[1])); |
| EXPECT_EQ(1UL, GetTable()->selection_model().size()); |
| |
| { |
| content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes; |
| |
| // Press the button, which kills the process of the selected row. |
| PressKillButton(); |
| |
| // Two rows should disappear. |
| ASSERT_NO_FATAL_FAILURE(WaitForTaskManagerRows((rows -= 2), pattern)); |
| } |
| |
| // A later row should now be selected. The selection should be after the 4 |
| // rows sharing the tabs[0] process, and it should be at or before |
| // the tabs[2] row. |
| ASSERT_LT(FindRowForTab(tabs[0]) + 3, GetTable()->GetFirstSelectedRow()); |
| ASSERT_LE(GetTable()->GetFirstSelectedRow(), FindRowForTab(tabs[2])); |
| |
| // Now select tabs[2]. |
| GetTable()->Select(FindRowForTab(tabs[2])); |
| |
| // 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(GetTable()->GetFirstSelectedRow(), 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(GetTable()->GetFirstSelectedRow(), FindRowForTab(tabs[2])); |
| } |
| |
| // Make sure the task manager's bounds are saved across instances on Chrome OS. |
| IN_PROC_BROWSER_TEST_F(TaskManagerViewTest, RestoreBounds) { |
| chrome::ShowTaskManager(browser()); |
| |
| const gfx::Rect default_bounds = |
| GetView()->GetWidget()->GetWindowBoundsInScreen(); |
| const gfx::Rect non_default_bounds = default_bounds + gfx::Vector2d(0, 17); |
| |
| GetView()->GetWidget()->SetBounds(non_default_bounds); |
| HideTaskManagerSync(); |
| |
| chrome::ShowTaskManager(browser()); |
| EXPECT_EQ(non_default_bounds, |
| GetView()->GetWidget()->GetWindowBoundsInScreen()); |
| |
| // Also make sure that the task manager is not restored off-screen. |
| // This is a regression test for https://crbug.com/308606 |
| display::Display display = |
| display::Screen::GetScreen()->GetDisplayMatching(non_default_bounds); |
| const gfx::Rect offscreen_bounds = |
| default_bounds + gfx::Vector2d(0, display.bounds().bottom()); |
| GetView()->GetWidget()->SetBounds(offscreen_bounds); |
| HideTaskManagerSync(); |
| |
| chrome::ShowTaskManager(browser()); |
| gfx::Rect restored_bounds = GetView()->GetWidget()->GetWindowBoundsInScreen(); |
| EXPECT_NE(offscreen_bounds, restored_bounds); |
| EXPECT_TRUE(display.bounds().Contains(restored_bounds)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TaskManagerViewTest, CloseByAccelerator) { |
| chrome::ShowTaskManager(browser()); |
| |
| EXPECT_FALSE(GetView()->GetWidget()->IsClosed()); |
| |
| GetView()->AcceleratorPressed( |
| ui::Accelerator(ui::VKEY_W, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN)); |
| |
| EXPECT_TRUE(GetView()->GetWidget()->IsClosed()); |
| } |
| } // namespace task_manager |