blob: d24f83ce4451312bb275e5880c405387b28d0c53 [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/task_manager_view.h"
#include <stddef.h>
#include <string_view>
#include "base/containers/adapters.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile_window.h"
#include "chrome/browser/task_manager/common/task_manager_features.h"
#include "chrome/browser/task_manager/task_manager_interface.h"
#include "chrome/browser/task_manager/task_manager_observer.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/task_manager/task_manager_columns.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/accessibility/platform/ax_platform.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/base/models/table_model_observer.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/base/mojom/menu_source_type.mojom-forward.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_type.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/table/table_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/vector_icons.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/dialog_client_view.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "ash/public/cpp/shelf_item.h"
#include "ash/public/cpp/window_properties.h"
#include "chrome/browser/apps/icon_standardizer.h"
#include "chrome/grit/theme_resources.h"
#include "ui/aura/window.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image_skia.h"
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_WIN)
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/shell_integration_win.h"
#include "ui/base/win/shell.h"
#include "ui/views/win/hwnd_util.h"
#endif // BUILDFLAG(IS_WIN)
namespace task_manager {
namespace {
TaskManagerView* g_task_manager_view = nullptr;
} // namespace
const auto kTabDefinitions = std::to_array<TaskManagerView::FilterTab>({
{
.associated_category = DisplayCategory::kTabsAndExtensions,
.title_id = IDS_TASK_MANAGER_CATEGORY_TABS_AND_EXTENSIONS_NAME,
.icon = &kNewTabRefreshIcon,
},
{
.associated_category = DisplayCategory::kSystem,
#if BUILDFLAG(IS_CHROMEOS)
.title_id = IDS_TASK_MANAGER_CATEGORY_SYSTEM_NAME,
.icon = &kLaptopIcon,
#else
.title_id = IDS_TASK_MANAGER_CATEGORY_BROWSER_NAME,
.icon = &kBrowserLogoIcon,
#endif
},
{
.associated_category = DisplayCategory::kAll,
.title_id = IDS_TASK_MANAGER_CATEGORY_ALL_NAME,
.icon = &kViewListIcon,
},
});
TaskManagerView::~TaskManagerView() {
// Delete child views now, while our table model still exists.
tabs_ = nullptr; // Destroyed by `container` below.
RemoveAllChildViews();
// When the view is destroyed, the lifecycle of the Task Manager is complete.
task_manager::RecordCloseEvent(start_time_, base::TimeTicks::Now());
}
// static
task_manager::TaskManagerTableModel* TaskManagerView::Show(
Browser* browser,
StartAction start_action) {
if (g_task_manager_view) {
// If there's a Task manager window open already, just activate it.
g_task_manager_view->SelectTaskOfActiveTab(browser);
g_task_manager_view->GetWidget()->Activate();
return g_task_manager_view->table_model_.get();
}
g_task_manager_view = new TaskManagerView(start_action);
// On Chrome OS, pressing Search-Esc when there are no open browser windows
// will open the task manager on the root window for new windows.
gfx::NativeWindow context =
browser ? browser->window()->GetNativeWindow() : gfx::NativeWindow();
CreateDialogWidget(g_task_manager_view, context, gfx::NativeView());
g_task_manager_view->GetDialogClientView()->SetBackgroundColor(
kColorTaskManagerBackground);
g_task_manager_view->InitAlwaysOnTopState();
#if BUILDFLAG(IS_WIN)
// Set the app id for the task manager to the app id of its parent browser. If
// no parent is specified, the app id will default to that of the initial
// process.
if (browser) {
ui::win::SetAppIdForWindow(
shell_integration::win::GetAppUserModelIdForBrowser(
browser->profile()->GetPath()),
views::HWNDForWidget(g_task_manager_view->GetWidget()));
}
#endif
g_task_manager_view->SelectTaskOfActiveTab(browser);
g_task_manager_view->GetWidget()->Show();
if (g_task_manager_view->table_config_.layout_refresh &&
ui::AXPlatform::GetInstance().IsScreenReaderActive()) {
// For a11y: with the refreshed layout, the top-left most item should be
// focused by default so screen readers read out the layout ltr (or flipped
// for rtl).
g_task_manager_view->tabs_->GetSelectedTab()->RequestFocus();
}
#if BUILDFLAG(IS_CHROMEOS)
aura::Window* window = g_task_manager_view->GetWidget()->GetNativeWindow();
// An app id for task manager windows, also used to identify the shelf item.
// Generated as crx_file::id_util::GenerateId("org.chromium.taskmanager")
static constexpr char kTaskManagerId[] = "ijaigheoohcacdnplfbdimmcfldnnhdi";
const ash::ShelfID shelf_id(kTaskManagerId);
window->SetProperty(ash::kShelfIDKey, shelf_id.Serialize());
window->SetProperty(ash::kAppIDKey, shelf_id.app_id);
window->SetProperty<int>(ash::kShelfItemTypeKey, ash::TYPE_DIALOG);
window->SetTitle(l10n_util::GetStringUTF16(IDS_TASK_MANAGER_TITLE));
#endif
return g_task_manager_view->table_model_.get();
}
// static
void TaskManagerView::Hide() {
if (g_task_manager_view) {
g_task_manager_view->GetWidget()->Close();
}
}
bool TaskManagerView::IsColumnVisible(int column_id) const {
return tab_table_->IsColumnVisible(column_id);
}
bool TaskManagerView::SetColumnVisibility(int column_id, bool new_visibility) {
// Check if there is at least 1 visible column before changing the visibility.
// If this column would be the last column to be visible and its hiding, then
// prevent this column visibility change. see crbug.com/1320307 for details.
if (!new_visibility && tab_table_->visible_columns().size() <= 1) {
return false;
}
const bool currently_visible = tab_table_->IsColumnVisible(column_id);
tab_table_->SetColumnVisibility(column_id, new_visibility);
return new_visibility != currently_visible;
}
bool TaskManagerView::IsTableSorted() const {
return tab_table_->GetIsSorted();
}
TableSortDescriptor TaskManagerView::GetSortDescriptor() const {
if (!IsTableSorted()) {
return TableSortDescriptor();
}
const auto& descriptor = tab_table_->sort_descriptors().front();
return TableSortDescriptor(descriptor.column_id, descriptor.ascending);
}
void TaskManagerView::SetSortDescriptor(const TableSortDescriptor& descriptor) {
views::TableView::SortDescriptors descriptor_list;
// If |sorted_column_id| is the default value, it means to clear the sort.
if (descriptor.sorted_column_id != TableSortDescriptor().sorted_column_id) {
descriptor_list.emplace_back(descriptor.sorted_column_id,
descriptor.is_ascending);
}
tab_table_->SetSortDescriptors(descriptor_list);
}
gfx::Size TaskManagerView::CalculatePreferredSize(
const views::SizeBounds& available_size) const {
// The TaskManagerView's preferred size is used to size the hosting Widget
// when the Widget does not have `initial_restored_bounds_` set. The minimum
// width below ensures that there is sufficient space for the task manager's
// columns when the above restored bounds have not been set.
return gfx::Size(640, 270);
}
bool TaskManagerView::AcceleratorPressed(const ui::Accelerator& accelerator) {
const bool is_valid_modifier =
accelerator.modifiers() == ui::EF_CONTROL_DOWN ||
accelerator.modifiers() == (ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
DCHECK(is_valid_modifier);
DCHECK_EQ(ui::VKEY_W, accelerator.key_code());
GetWidget()->Close();
return true;
}
views::View* TaskManagerView::GetInitiallyFocusedView() {
// Set initial focus to |table_view_| so that screen readers can navigate the
// UI when the dialog is opened without having to manually assign focus first.
return tab_table_;
}
bool TaskManagerView::ExecuteWindowsCommand(int command_id) {
return false;
}
ui::ImageModel TaskManagerView::GetWindowIcon() {
TRACE_EVENT0("ui", "TaskManagerView::GetWindowIcon");
#if BUILDFLAG(IS_CHROMEOS)
// TODO(crbug.com/40739545): Move apps::CreateStandardIconImage to some
// where lower in the stack.
return ui::ImageModel::FromImageSkia(apps::CreateStandardIconImage(
*ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
IDR_ASH_SHELF_ICON_TASK_MANAGER)));
#else
return views::DialogDelegateView::GetWindowIcon();
#endif
}
std::string TaskManagerView::GetWindowName() const {
return prefs::kTaskManagerWindowPlacement;
}
bool TaskManagerView::Accept() {
EndSelectedProcess();
// Just kill the process, don't close the task manager dialog.
return false;
}
bool TaskManagerView::IsDialogButtonEnabled(
ui::mojom::DialogButton button) const {
return IsEndProcessButtonEnabled();
}
void TaskManagerView::WindowClosing() {
// Now that the window is closed, we can allow a new one to be opened.
// (WindowClosing comes in asynchronously from the call to Close() and we
// may have already opened a new instance).
if (g_task_manager_view == this) {
// We don't have to delete |g_task_manager_view| as we don't own it. It's
// owned by the Views hierarchy.
g_task_manager_view = nullptr;
}
table_model_->StoreColumnsSettings();
}
void TaskManagerView::GetGroupRange(size_t model_index,
views::GroupRange* range) {
table_model_->GetRowsGroupRange(model_index, &range->start, &range->length);
}
void TaskManagerView::OnSelectionChanged() {
DialogModelChanged();
}
void TaskManagerView::OnDoubleClick() {
ActivateSelectedTab();
}
void TaskManagerView::OnKeyDown(ui::KeyboardCode keycode) {
if (keycode == ui::VKEY_RETURN) {
ActivateSelectedTab();
}
}
void TaskManagerView::OnWidgetInitialized() {
GetOkButton()->GetViewAccessibility().SetDescription(
l10n_util::GetStringUTF16(IDS_TASK_MANAGER_KILL_ACCESSIBILITY_NAME));
}
void TaskManagerView::ShowContextMenuForViewImpl(
views::View* source,
const gfx::Point& point,
ui::mojom::MenuSourceType source_type) {
menu_model_ = std::make_unique<ui::SimpleMenuModel>(this);
for (const auto& table_column : columns_) {
menu_model_->AddCheckItem(table_column.id,
l10n_util::GetStringUTF16(table_column.id));
}
menu_runner_ = std::make_unique<views::MenuRunner>(
menu_model_.get(), views::MenuRunner::CONTEXT_MENU);
menu_runner_->RunMenuAt(GetWidget(), nullptr, gfx::Rect(point, gfx::Size()),
views::MenuAnchorPosition::kTopLeft, source_type);
}
bool TaskManagerView::IsCommandIdChecked(int id) const {
return tab_table_->IsColumnVisible(id);
}
bool TaskManagerView::IsCommandIdEnabled(int id) const {
return true;
}
void TaskManagerView::ExecuteCommand(int id, int event_flags) {
table_model_->ToggleColumnVisibility(id);
}
void TaskManagerView::MenuClosed(ui::SimpleMenuModel* source) {
menu_model_.reset();
menu_runner_.reset();
}
void TaskManagerView::SearchBarOnInputChanged(std::u16string_view query) {
search_terms_ = query;
PerformFilter(
kTabDefinitions[tabs_->GetSelectedTabIndex()].associated_category);
}
TaskManagerView::TaskManagerView(StartAction start_action)
: tab_table_(nullptr),
tab_table_parent_(nullptr),
table_config_(GetTableConfigs()),
is_always_on_top_(false) {
task_manager::RecordNewOpenEvent(start_action);
set_use_custom_frame(false);
SetHasWindowSizeControls(true);
#if !BUILDFLAG(IS_CHROMEOS)
// On Chrome OS, the widget's frame should not show the window title.
SetTitle(IDS_TASK_MANAGER_TITLE);
#endif
// Avoid calling Accept() when closing the dialog, since Accept() here means
// "kill task" (!).
// TODO(ellyjones): Remove this once the Accept() override is removed from
// this class.
SetCloseCallback(base::DoNothing());
Init();
}
// static
TaskManagerView* TaskManagerView::GetInstanceForTests() {
return g_task_manager_view;
}
// static
TaskManagerView::TableConfigs TaskManagerView::GetTableConfigs() {
const bool tm_refresh_enabled =
base::FeatureList::IsEnabled(features::kTaskManagerDesktopRefresh);
return TableConfigs{
.table_has_border = !tm_refresh_enabled,
.header_style = tm_refresh_enabled,
.table_refresh = tm_refresh_enabled,
.scroll_view_rounded = tm_refresh_enabled,
.layout_refresh = tm_refresh_enabled,
.dialog_button_disabled = tm_refresh_enabled,
.sort_on_cpu_by_default = tm_refresh_enabled,
};
}
void TaskManagerView::TabSelectedAt(int index) {
PerformFilter(kTabDefinitions[index].associated_category);
}
void TaskManagerView::CreateHeader(const ChromeLayoutProvider* provider) {
const int separator_spacing =
provider->GetDistanceMetric(DISTANCE_RELATED_CONTROL_HORIZONTAL_SMALL);
// The strategy to get the TabStrip Selector and the Separator to overlap
// involves using a parent FillLayout (which will allow the child views to
// overlap), and then attaching the content container (tabs, search bar, and
// end process button), along with a separator container (where the separator
// is locked at the bottom using a BoxLayout).
auto parent_container =
views::Builder<views::View>().SetUseDefaultFillLayout(true).Build();
auto content_container = CreateHeaderContent(provider);
auto separator_container = CreateHeaderSeparatorUnderlay(/*height=*/2);
// Attach separator below header, and then contents over top.
parent_container->AddChildView(std::move(separator_container));
parent_container->AddChildView(std::move(content_container));
// Add some spacing at the bottom between the header and the next view.
parent_container->SetProperty(views::kMarginsKey,
gfx::Insets::TLBR(0, 0, separator_spacing, 0));
// Attach header to the top of the dialog contents.
AddChildView(std::move(parent_container));
}
std::unique_ptr<views::View> TaskManagerView::CreateHeaderContent(
const ChromeLayoutProvider* provider) {
// Header Parent
auto header_layout = std::make_unique<views::BoxLayout>();
header_layout->SetOrientation(views::LayoutOrientation::kHorizontal);
header_layout->set_cross_axis_alignment(views::LayoutAlignment::kCenter);
auto container = std::make_unique<views::View>();
auto tabs = CreateTabbedPane(
provider,
/*title_insets=*/
gfx::Insets::TLBR(0, views::TabbedPaneTab::kDefaultTitleLeftMargin, 0, 0),
/*tab_outsets=*/gfx::Outsets::VH(0, 16));
// Empty Container, Search Bar
auto empty_view = std::make_unique<views::View>();
auto search_bar_container = CreateSearchBar(provider);
// Allow empty spacing and the search bar to flex freely.
header_layout->SetFlexForView(empty_view.get(), 7);
header_layout->SetFlexForView(search_bar_container.get(), 3);
// Set the layout manager for the content container to BoxLayout.
container->SetLayoutManager(std::move(header_layout));
// Compose all parts into header.
tabs_ = container->AddChildView(std::move(tabs));
container->AddChildView(std::move(empty_view));
container->AddChildView(std::move(search_bar_container));
return container;
}
std::unique_ptr<views::View> TaskManagerView::CreateHeaderSeparatorUnderlay(
int height) {
auto separator_container = std::make_unique<views::View>();
auto separator_container_layout = std::make_unique<views::BoxLayout>();
separator_container_layout->SetOrientation(
views::LayoutOrientation::kHorizontal);
separator_container_layout->set_cross_axis_alignment(
views::BoxLayout::MainAxisAlignment::kEnd);
auto separator =
views::Builder<views::Separator>().SetPreferredLength(height).Build();
separator_container_layout->SetFlexForView(separator.get(), 1);
separator_container->SetLayoutManager(std::move(separator_container_layout));
separator_container->AddChildView(std::move(separator));
return separator_container;
}
void TaskManagerView::PerformFilter(DisplayCategory category) {
SaveCategoryToLocalState(category);
// When `select_on_remove_` is enabled, the selection will automatically jump
// to some next/previous row if available. However, this setting needs to be
// temporarily disabled during model updates to achieve the desired selection
// changes. Specifically:
// 1. When a tab is changed, some number of rows will get added and removed.
// The intended behavior is to clear the selection between this tab switch.
// 2. When the search term changes, if the selection should always be kept if
// it's in the current list. If it's not (i.e. "Tab: unusual" is selected, and
// the search term changes from "un" -> "unh"), then it should be removed, but
// no other selection should be applied (a.k.a the selection should clear).
tab_table_->SetSelectOnRemove(false);
if (table_model_->UpdateModel(category, search_terms_)) {
// Model row count may differ, leading to off-screen row rendering.
// Recompute scroll position.
tab_table_->InvalidateLayout();
}
tab_table_->SetSelectOnRemove(true);
}
std::unique_ptr<views::TabbedPaneTabStrip> TaskManagerView::CreateTabbedPane(
const ChromeLayoutProvider* provider,
const gfx::Insets& title_insets,
const gfx::Outsets& tab_outsets) {
const int tab_height =
provider->GetDistanceMetric(DISTANCE_TASK_MANAGER_TAB_HEIGHT);
const auto dialog_insets = provider->GetInsetsMetric(INSETS_TASK_MANAGER);
auto tabs = std::make_unique<views::TabbedPaneTabStrip>(
views::TabbedPane::Orientation::kHorizontal,
views::TabbedPane::TabStripStyle::kWithIcon,
/*tabbed_pane=*/nullptr);
tabs->SetDefaultFlex(0);
tabs->SetDrawTabDivider(false);
for (const auto& tab_definition : kTabDefinitions) {
auto* tab = tabs->AddTab(l10n_util::GetStringUTF16(tab_definition.title_id),
tab_definition.icon);
tab->SetTitleMargin(title_insets);
tab->SetTabOutsets(tab_outsets);
// Assume some arbitrary spec_height. Set the height of the tabs as
// (spec_height - task_manager_dialog_insets), so that the focus ring around
// the tabbed pane isn't touching the title bar.
tab->SetHeight(tab_height - dialog_insets.top());
}
tabs->set_listener(this);
return tabs;
}
std::unique_ptr<views::View> TaskManagerView::CreateSearchBar(
const ChromeLayoutProvider* provider) {
const int horizontal_spacing = provider->GetDistanceMetric(
DISTANCE_TASK_MANAGER_SEARCH_BAR_ICON_AND_BUTTON_HORIZONTAL_SPACING);
const int search_bar_container_radius = provider->GetCornerRadiusMetric(
views::ShapeContextTokens::kOmniboxExpandedRadius);
auto search_bar_layout = std::make_unique<views::BoxLayout>();
search_bar_layout->SetOrientation(views::LayoutOrientation::kHorizontal);
search_bar_layout->set_cross_axis_alignment(views::LayoutAlignment::kStart);
auto search_bar_container = std::make_unique<views::View>();
search_bar_container->SetBackground(views::CreateRoundedRectBackground(
kColorTaskManagerSearchBarBackground, search_bar_container_radius));
search_bar_container->SetLayoutManager(std::move(search_bar_layout));
const gfx::Size search_bar_size{
provider->GetDistanceMetric(DISTANCE_TASK_MANAGER_SEARCH_BAR_MIN_WIDTH),
provider->GetDistanceMetric(DISTANCE_TASK_MANAGER_SEARCH_BAR_MIN_HEIGHT)};
search_bar_container->SetPreferredSize(search_bar_size);
auto search_bar = std::make_unique<TaskManagerSearchBarView>(
l10n_util::GetStringUTF16(IDS_TASK_MANAGER_SEARCH_PLACEHOLDER),
gfx::Insets::VH(0, horizontal_spacing), *this);
search_bar_container->AddChildView(std::move(search_bar));
return search_bar_container;
}
std::unique_ptr<views::ScrollView> TaskManagerView::CreateProcessView(
std::unique_ptr<views::TableView> tab_table,
bool table_has_border,
bool layout_refresh) {
auto scroll_view = views::TableView::CreateScrollViewWithTable(
std::move(tab_table), table_has_border);
if (layout_refresh) {
scroll_view->SetLayoutManager(std::make_unique<views::FillLayout>());
scroll_view->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kUnbounded));
}
return scroll_view;
}
void TaskManagerView::Init() {
// Create the table columns.
for (size_t i = 0; i < kColumnsSize; ++i) {
const auto& col_data = kColumns[i];
columns_.emplace_back(col_data.id, col_data.align, col_data.width,
col_data.percent);
columns_.back().sortable = col_data.sortable;
columns_.back().initial_sort_is_ascending =
col_data.initial_sort_is_ascending;
}
// Create the table view.
auto tab_table = std::make_unique<views::TableView>(
nullptr, columns_, views::TableType::kIconAndText, false);
tab_table_ = tab_table.get();
table_model_ = std::make_unique<TaskManagerTableModel>(
this, table_config_.layout_refresh ? DisplayCategory::kTabsAndExtensions
: DisplayCategory::kAll);
tab_table->SetModel(table_model_.get());
tab_table->SetGrouper(this);
tab_table->SetGrouperVisibility(!table_config_.layout_refresh);
tab_table->SetSortOnPaint(true);
if (table_config_.layout_refresh) {
// Disables alternating row colors on all platforms, including macOS.
tab_table->SetAlternatingRowColorsEnabled(base::PassKey<TaskManagerView>(),
false);
tab_table->SetMouseHoveringEnabled(true);
tab_table->SetRowPadding(views::DISTANCE_TABLE_VERTICAL_TEXT_PADDING);
}
tab_table->set_observer(this);
tab_table->set_context_menu_controller(this);
set_context_menu_controller(this);
SetButtons(static_cast<int>(ui::mojom::DialogButton::kOk));
SetButtonLabel(ui::mojom::DialogButton::kOk,
l10n_util::GetStringUTF16(table_config_.layout_refresh
? IDS_TASK_MANAGER_KILL_V2
: IDS_TASK_MANAGER_KILL));
const auto* provider = ChromeLayoutProvider::Get();
const float corner_radius =
provider->GetCornerRadiusMetric(views::Emphasis::kHigh);
if (table_config_.header_style) {
views::TableHeaderStyle header_style(
/*cell_vertical_padding=*/14, /*cell_horizontal_padding=*/12,
/*resize_bar_vertical_padding=*/16,
/*separator_horizontal_padding=*/0,
/*font_weight=*/gfx::Font::Weight::MEDIUM,
/*separator_horizontal_color_id=*/ui::kColorSysDivider,
/*separator_vertical_color_id=*/ui::kColorSysDivider,
/*background_color_id=*/kColorTaskManagerTableHeaderBackground,
/*focus_ring_upper_corner_radius=*/corner_radius,
/*header_sort_state=*/
base::FeatureList::IsEnabled(features::kTaskManagerDesktopRefresh));
tab_table->SetHeaderStyle(header_style);
}
if (table_config_.table_refresh) {
views::TableStyle table_style = {
.background_tokens =
views::TableBackgroundStyle{
.background = kColorTaskManagerTableBackground,
.alternate = kColorTaskManagerTableBackgroundAlternate,
.selected_focused =
kColorTaskManagerTableBackgroundSelectedFocused,
.selected_unfocused =
kColorTaskManagerTableBackgroundSelectedUnfocused,
},
.icons_have_background = true,
.inset_focus_ring = true,
};
tab_table->SetTableStyle(table_style);
}
// Margins around all contents
const gfx::Insets dialog_insets = provider->GetInsetsMetric(
table_config_.layout_refresh ? static_cast<int>(INSETS_TASK_MANAGER)
: views::INSETS_DIALOG);
// We don't use ChromeLayoutProvider::GetDialogInsetsForContentType because we
// don't have a title.
const auto content_insets = gfx::Insets::TLBR(
dialog_insets.top(), dialog_insets.left(),
provider->GetDistanceMetric(
views::DISTANCE_DIALOG_CONTENT_MARGIN_BOTTOM_CONTROL),
dialog_insets.right());
SetBorder(views::CreateEmptyBorder(content_insets));
// Setup Layout Manager for Dialog
if (table_config_.layout_refresh) {
views::FlexLayout* content_layout =
SetLayoutManager(std::make_unique<views::FlexLayout>());
content_layout->SetOrientation(views::LayoutOrientation::kVertical);
CreateHeader(provider);
} else {
SetUseDefaultFillLayout(true);
}
// Add Process List (a.k.a Scroll View)
tab_table_parent_ = AddChildView(
CreateProcessView(std::move(tab_table), table_config_.table_has_border,
table_config_.layout_refresh));
if (table_config_.scroll_view_rounded) {
tab_table_parent_->SetPaintToLayer(ui::LAYER_TEXTURED);
ui::Layer* scroll_view_layer = tab_table_parent_->layer();
scroll_view_layer->SetRoundedCornerRadius(
gfx::RoundedCornersF(corner_radius));
scroll_view_layer->SetIsFastRoundedCorner(true);
}
table_model_->RetrieveSavedColumnsSettingsAndUpdateTable(
table_config_.sort_on_cpu_by_default);
if (table_config_.layout_refresh) {
RestoreSavedCategory();
}
AddAccelerator(ui::Accelerator(ui::VKEY_W, ui::EF_CONTROL_DOWN));
AddAccelerator(
ui::Accelerator(ui::VKEY_W, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN));
}
void TaskManagerView::InitAlwaysOnTopState() {
RetrieveSavedAlwaysOnTopState();
GetWidget()->SetZOrderLevel(is_always_on_top_
? ui::ZOrderLevel::kFloatingWindow
: ui::ZOrderLevel::kNormal);
}
void TaskManagerView::ActivateSelectedTab() {
const std::optional<size_t> active_row =
tab_table_->selection_model().active();
if (active_row.has_value()) {
table_model_->ActivateTask(active_row.value());
}
}
void TaskManagerView::SelectTaskOfActiveTab(Browser* browser) {
if (browser) {
tab_table_->Select(table_model_->GetRowForWebContents(
browser->tab_strip_model()->GetActiveWebContents()));
}
}
void TaskManagerView::RetrieveSavedAlwaysOnTopState() {
is_always_on_top_ = false;
if (!g_browser_process->local_state()) {
return;
}
const base::Value::Dict& dictionary =
g_browser_process->local_state()->GetDict(GetWindowName());
is_always_on_top_ = dictionary.FindBool("always_on_top").value_or(false);
}
void TaskManagerView::RestoreSavedCategory() {
if (!g_browser_process->local_state()) {
return;
}
int category =
g_browser_process->local_state()->GetInteger(prefs::kTaskManagerCategory);
int max = static_cast<int>(DisplayCategory::kMax);
// Bounds check the retrieved pref.
if (category < 0 || category > max) {
category = static_cast<int>(TaskManagerTableModel::kDefaultCategory);
}
const auto parsed_category = static_cast<DisplayCategory>(category);
// Finds the tab index of the category to restore, or does nothing if the
// category no longer exists as a tab.
for (size_t i = 0; i < kTabDefinitions.size(); ++i) {
if (kTabDefinitions[i].associated_category == parsed_category) {
tabs_->SelectTab(tabs_->GetTabAtIndex(i), /*animate=*/false);
break;
}
}
}
void TaskManagerView::SaveCategoryToLocalState(DisplayCategory category) {
PrefService* local_state = g_browser_process->local_state();
if (!local_state) {
return;
}
// Stores the current tab to be restored again at startup.
int category_to_save = static_cast<int>(category);
int max = static_cast<int>(DisplayCategory::kMax);
int default_category =
static_cast<int>(TaskManagerTableModel::kDefaultCategory);
// Bounds check to ensure that the category is set appropriately.
if (category_to_save < 0 || category_to_save > max) {
category_to_save = default_category;
}
local_state->SetInteger(prefs::kTaskManagerCategory, category_to_save);
}
void TaskManagerView::EndSelectedProcess() {
using SelectedIndices = ui::ListSelectionModel::SelectedIndices;
SelectedIndices selection(tab_table_->selection_model().selected_indices());
bool any_task_ended = false;
for (int index : base::Reversed(selection)) {
any_task_ended |= table_model_->KillTask(index);
}
// AX: Announce the result of ending a task group.
if (table_config_.layout_refresh) {
AnnounceTaskEnded(any_task_ended);
}
base::TimeTicks current_time = base::TimeTicks::Now();
if (end_process_count_ < 5) {
task_manager::RecordEndProcessEvent(latest_end_process_time_, current_time,
++end_process_count_);
}
latest_end_process_time_ = current_time;
}
void TaskManagerView::AnnounceTaskEnded(bool any_task_ended) {
GetViewAccessibility().AnnounceText(l10n_util::GetStringUTF16(
any_task_ended ? IDS_TASK_MANAGER_TASK_KILL_SUCCESS_ACCESSIBILITY_MESSAGE
: IDS_TASK_MANAGER_TASK_KILL_FAIL_ACCESSIBILITY_MESSAGE));
}
bool TaskManagerView::IsEndProcessButtonEnabled() const {
const ui::ListSelectionModel::SelectedIndices& selections(
tab_table_->selection_model().selected_indices());
for (const auto& selection : selections) {
if (!table_model_->IsTaskKillable(selection)) {
return false;
}
}
return !selections.empty() && TaskManagerInterface::IsEndProcessEnabled();
}
BEGIN_METADATA(TaskManagerView)
END_METADATA
} // namespace task_manager
namespace chrome {
#if BUILDFLAG(IS_MAC)
// These are used by the Mac versions of |ShowTaskManager| and |HideTaskManager|
// if they decide to show the Views task manager instead of the Cocoa one.
task_manager::TaskManagerTableModel* ShowTaskManagerViews(
Browser* browser,
task_manager::StartAction start_action) {
return task_manager::TaskManagerView::Show(browser, start_action);
}
void HideTaskManagerViews() {
task_manager::TaskManagerView::Hide();
}
#endif
} // namespace chrome