summaryrefslogtreecommitdiffstats
path: root/chrome/browser/gtk/task_manager_gtk.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/gtk/task_manager_gtk.cc')
-rw-r--r--chrome/browser/gtk/task_manager_gtk.cc909
1 files changed, 909 insertions, 0 deletions
diff --git a/chrome/browser/gtk/task_manager_gtk.cc b/chrome/browser/gtk/task_manager_gtk.cc
new file mode 100644
index 0000000..faffd1c
--- /dev/null
+++ b/chrome/browser/gtk/task_manager_gtk.cc
@@ -0,0 +1,909 @@
+// Copyright (c) 2010 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/gtk/task_manager_gtk.h"
+
+#include <gdk/gdkkeysyms.h>
+
+#include <algorithm>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "app/l10n_util.h"
+#include "app/menus/simple_menu_model.h"
+#include "app/resource_bundle.h"
+#include "base/auto_reset.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/gtk/gtk_chrome_link_button.h"
+#include "chrome/browser/gtk/gtk_theme_provider.h"
+#include "chrome/browser/gtk/gtk_tree.h"
+#include "chrome/browser/gtk/gtk_util.h"
+#include "chrome/browser/gtk/menu_gtk.h"
+#include "chrome/browser/memory_purger.h"
+#include "chrome/browser/pref_service.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "gfx/gtk_util.h"
+#include "grit/app_resources.h"
+#include "grit/chromium_strings.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace {
+
+// The task manager window default size.
+const int kDefaultWidth = 460;
+const int kDefaultHeight = 270;
+
+// The resource id for the 'End process' button.
+const gint kTaskManagerResponseKill = 1;
+
+// The resource id for the 'Stats for nerds' link button.
+const gint kTaskManagerAboutMemoryLink = 2;
+
+// The resource id for the 'Purge Memory' button
+const gint kTaskManagerPurgeMemory = 3;
+
+enum TaskManagerColumn {
+ kTaskManagerIcon,
+ kTaskManagerPage,
+ kTaskManagerSharedMem,
+ kTaskManagerPrivateMem,
+ kTaskManagerCPU,
+ kTaskManagerNetwork,
+ kTaskManagerProcessID,
+ kTaskManagerJavaScriptMemory,
+ kTaskManagerWebCoreImageCache,
+ kTaskManagerWebCoreScriptsCache,
+ kTaskManagerWebCoreCssCache,
+ kTaskManagerGoatsTeleported,
+ kTaskManagerColumnCount,
+};
+
+TaskManagerColumn TaskManagerResourceIDToColumnID(int id) {
+ switch (id) {
+ case IDS_TASK_MANAGER_PAGE_COLUMN:
+ return kTaskManagerPage;
+ case IDS_TASK_MANAGER_SHARED_MEM_COLUMN:
+ return kTaskManagerSharedMem;
+ case IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN:
+ return kTaskManagerPrivateMem;
+ case IDS_TASK_MANAGER_CPU_COLUMN:
+ return kTaskManagerCPU;
+ case IDS_TASK_MANAGER_NET_COLUMN:
+ return kTaskManagerNetwork;
+ case IDS_TASK_MANAGER_PROCESS_ID_COLUMN:
+ return kTaskManagerProcessID;
+ case IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN:
+ return kTaskManagerJavaScriptMemory;
+ case IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN:
+ return kTaskManagerWebCoreImageCache;
+ case IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN:
+ return kTaskManagerWebCoreScriptsCache;
+ case IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN:
+ return kTaskManagerWebCoreCssCache;
+ case IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN:
+ return kTaskManagerGoatsTeleported;
+ default:
+ NOTREACHED();
+ return static_cast<TaskManagerColumn>(-1);
+ }
+}
+
+int TaskManagerColumnIDToResourceID(int id) {
+ switch (id) {
+ case kTaskManagerPage:
+ return IDS_TASK_MANAGER_PAGE_COLUMN;
+ case kTaskManagerSharedMem:
+ return IDS_TASK_MANAGER_SHARED_MEM_COLUMN;
+ case kTaskManagerPrivateMem:
+ return IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN;
+ case kTaskManagerCPU:
+ return IDS_TASK_MANAGER_CPU_COLUMN;
+ case kTaskManagerNetwork:
+ return IDS_TASK_MANAGER_NET_COLUMN;
+ case kTaskManagerProcessID:
+ return IDS_TASK_MANAGER_PROCESS_ID_COLUMN;
+ case kTaskManagerJavaScriptMemory:
+ return IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN;
+ case kTaskManagerWebCoreImageCache:
+ return IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN;
+ case kTaskManagerWebCoreScriptsCache:
+ return IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN;
+ case kTaskManagerWebCoreCssCache:
+ return IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN;
+ case kTaskManagerGoatsTeleported:
+ return IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN;
+ default:
+ NOTREACHED();
+ return -1;
+ }
+}
+
+// Should be used for all gtk_tree_view functions that require a column index on
+// input.
+//
+// We need colid - 1 because the gtk_tree_view function is asking for the
+// column index, not the column id, and both kTaskManagerIcon and
+// kTaskManagerPage are in the same column index, so all column IDs are off by
+// one.
+int TreeViewColumnIndexFromID(TaskManagerColumn colid) {
+ return colid - 1;
+}
+
+// Shows or hides a treeview column.
+void TreeViewColumnSetVisible(GtkWidget* treeview, TaskManagerColumn colid,
+ bool visible) {
+ GtkTreeViewColumn* column = gtk_tree_view_get_column(
+ GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid));
+ gtk_tree_view_column_set_visible(column, visible);
+}
+
+bool TreeViewColumnIsVisible(GtkWidget* treeview, TaskManagerColumn colid) {
+ GtkTreeViewColumn* column = gtk_tree_view_get_column(
+ GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid));
+ return gtk_tree_view_column_get_visible(column);
+}
+
+void TreeViewInsertColumnWithPixbuf(GtkWidget* treeview, int resid) {
+ int colid = TaskManagerResourceIDToColumnID(resid);
+ GtkTreeViewColumn* column = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(column,
+ l10n_util::GetStringUTF8(resid).c_str());
+ GtkCellRenderer* image_renderer = gtk_cell_renderer_pixbuf_new();
+ gtk_tree_view_column_pack_start(column, image_renderer, FALSE);
+ gtk_tree_view_column_add_attribute(column, image_renderer,
+ "pixbuf", kTaskManagerIcon);
+ GtkCellRenderer* text_renderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
+ gtk_tree_view_column_add_attribute(column, text_renderer, "text", colid);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ // This is temporary: we'll turn expanding off after getting the size.
+ gtk_tree_view_column_set_expand(column, TRUE);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
+ gtk_tree_view_column_set_sort_column_id(column, colid);
+}
+
+// Inserts a column with a column id of |colid| and |name|.
+void TreeViewInsertColumnWithName(GtkWidget* treeview,
+ TaskManagerColumn colid, const char* name) {
+ GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview), -1,
+ name, renderer,
+ "text", colid,
+ NULL);
+ GtkTreeViewColumn* column = gtk_tree_view_get_column(
+ GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid));
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_sort_column_id(column, colid);
+}
+
+// Loads the column name from |resid| and uses the corresponding
+// TaskManagerColumn value as the column id to insert into the treeview.
+void TreeViewInsertColumn(GtkWidget* treeview, int resid) {
+ TreeViewInsertColumnWithName(treeview, TaskManagerResourceIDToColumnID(resid),
+ l10n_util::GetStringUTF8(resid).c_str());
+}
+
+// Set the current width of the column without forcing a minimum width as
+// gtk_tree_view_column_set_fixed_width() would. This would basically be
+// gtk_tree_view_column_set_width() except that there is no such function.
+void TreeViewColumnSetWidth(GtkTreeViewColumn* column, gint width) {
+ column->width = width;
+ column->resized_width = width;
+ column->use_resized_width = TRUE;
+ // Needed for use_resized_width to be effective.
+ gtk_widget_queue_resize(column->tree_view);
+}
+
+} // namespace
+
+class TaskManagerGtk::ContextMenuController
+ : public menus::SimpleMenuModel::Delegate {
+ public:
+ explicit ContextMenuController(TaskManagerGtk* task_manager)
+ : task_manager_(task_manager) {
+ menu_model_.reset(new menus::SimpleMenuModel(this));
+ for (int i = kTaskManagerPage; i < kTaskManagerColumnCount; i++) {
+ menu_model_->AddCheckItemWithStringId(
+ i, TaskManagerColumnIDToResourceID(i));
+ }
+ menu_.reset(new MenuGtk(NULL, menu_model_.get()));
+ }
+
+ virtual ~ContextMenuController() {}
+
+ void RunMenu() {
+ menu_->PopupAsContext(gtk_get_current_event_time());
+ }
+
+ void Cancel() {
+ task_manager_ = NULL;
+ menu_->Cancel();
+ }
+
+ private:
+ // menus::SimpleMenuModel::Delegate implementation:
+ virtual bool IsCommandIdEnabled(int command_id) const {
+ if (!task_manager_)
+ return false;
+
+ return true;
+ }
+
+ virtual bool IsCommandIdChecked(int command_id) const {
+ if (!task_manager_)
+ return false;
+
+ TaskManagerColumn colid = static_cast<TaskManagerColumn>(command_id);
+ return TreeViewColumnIsVisible(task_manager_->treeview_, colid);
+ }
+
+ virtual bool GetAcceleratorForCommandId(
+ int command_id,
+ menus::Accelerator* accelerator) {
+ return false;
+ }
+
+ virtual void ExecuteCommand(int command_id) {
+ if (!task_manager_)
+ return;
+
+ TaskManagerColumn colid = static_cast<TaskManagerColumn>(command_id);
+ bool visible = !TreeViewColumnIsVisible(task_manager_->treeview_, colid);
+ TreeViewColumnSetVisible(task_manager_->treeview_, colid, visible);
+ }
+
+ // The model and view for the right click context menu.
+ scoped_ptr<menus::SimpleMenuModel> menu_model_;
+ scoped_ptr<MenuGtk> menu_;
+
+ // The TaskManager the context menu was brought up for. Set to NULL when the
+ // menu is canceled.
+ TaskManagerGtk* task_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContextMenuController);
+};
+
+TaskManagerGtk::TaskManagerGtk()
+ : task_manager_(TaskManager::GetInstance()),
+ model_(TaskManager::GetInstance()->model()),
+ dialog_(NULL),
+ treeview_(NULL),
+ process_list_(NULL),
+ process_count_(0),
+ ignore_selection_changed_(false) {
+ Init();
+}
+
+// static
+TaskManagerGtk* TaskManagerGtk::instance_ = NULL;
+
+TaskManagerGtk::~TaskManagerGtk() {
+ model_->RemoveObserver(this);
+ task_manager_->OnWindowClosed();
+
+ gtk_accel_group_disconnect_key(accel_group_, GDK_w, GDK_CONTROL_MASK);
+ gtk_window_remove_accel_group(GTK_WINDOW(dialog_), accel_group_);
+ g_object_unref(accel_group_);
+ accel_group_ = NULL;
+
+ // Disconnect the destroy signal so it doesn't delete |this|.
+ g_signal_handler_disconnect(G_OBJECT(dialog_), destroy_handler_id_);
+ gtk_widget_destroy(dialog_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TaskManagerGtk, TaskManagerModelObserver implementation:
+
+void TaskManagerGtk::OnModelChanged() {
+ // Nothing to do.
+}
+
+void TaskManagerGtk::OnItemsChanged(int start, int length) {
+ GtkTreeIter iter;
+ gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &iter,
+ NULL, start);
+
+ for (int i = start; i < start + length; i++) {
+ SetRowDataFromModel(i, &iter);
+ gtk_tree_model_iter_next(GTK_TREE_MODEL(process_list_), &iter);
+ }
+}
+
+void TaskManagerGtk::OnItemsAdded(int start, int length) {
+ AutoReset<bool> autoreset(&ignore_selection_changed_, true);
+
+ GtkTreeIter iter;
+ if (start == 0) {
+ gtk_list_store_prepend(process_list_, &iter);
+ } else if (start >= process_count_) {
+ gtk_list_store_append(process_list_, &iter);
+ } else {
+ GtkTreeIter sibling;
+ gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &sibling,
+ NULL, start);
+ gtk_list_store_insert_before(process_list_, &iter, &sibling);
+ }
+
+ SetRowDataFromModel(start, &iter);
+
+ for (int i = start + 1; i < start + length; i++) {
+ gtk_list_store_insert_after(process_list_, &iter, &iter);
+ SetRowDataFromModel(i, &iter);
+ }
+
+ process_count_ += length;
+}
+
+void TaskManagerGtk::OnItemsRemoved(int start, int length) {
+ {
+ AutoReset<bool> autoreset(&ignore_selection_changed_, true);
+
+ GtkTreeIter iter;
+ gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &iter,
+ NULL, start);
+
+ for (int i = 0; i < length; i++) {
+ // |iter| is moved to the next valid node when the current node is
+ // removed.
+ gtk_list_store_remove(process_list_, &iter);
+ }
+
+ process_count_ -= length;
+ }
+
+ // It is possible that we have removed the current selection; run selection
+ // changed to detect that case.
+ OnSelectionChanged(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview_)));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TaskManagerGtk, public:
+
+// static
+void TaskManagerGtk::Show() {
+ if (instance_) {
+ // If there's a Task manager window open already, just activate it.
+ gtk_util::PresentWindow(instance_->dialog_, 0);
+ } else {
+ instance_ = new TaskManagerGtk;
+ instance_->model_->StartUpdating();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TaskManagerGtk, private:
+
+void TaskManagerGtk::Init() {
+ dialog_ = gtk_dialog_new_with_buttons(
+ l10n_util::GetStringUTF8(IDS_TASK_MANAGER_TITLE).c_str(),
+ // Task Manager window is shared between all browsers.
+ NULL,
+ GTK_DIALOG_NO_SEPARATOR,
+ NULL);
+
+ // Allow browser windows to go in front of the task manager dialog in
+ // metacity.
+ gtk_window_set_type_hint(GTK_WINDOW(dialog_), GDK_WINDOW_TYPE_HINT_NORMAL);
+
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kPurgeMemoryButton)) {
+ gtk_dialog_add_button(GTK_DIALOG(dialog_),
+ l10n_util::GetStringUTF8(IDS_TASK_MANAGER_PURGE_MEMORY).c_str(),
+ kTaskManagerPurgeMemory);
+ }
+
+ gtk_dialog_add_button(GTK_DIALOG(dialog_),
+ l10n_util::GetStringUTF8(IDS_TASK_MANAGER_KILL).c_str(),
+ kTaskManagerResponseKill);
+
+ // The response button should not be sensitive when the dialog is first opened
+ // because the selection is initially empty.
+ gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_),
+ kTaskManagerResponseKill, FALSE);
+
+ GtkWidget* link = gtk_chrome_link_button_new(
+ l10n_util::GetStringUTF8(IDS_TASK_MANAGER_ABOUT_MEMORY_LINK).c_str());
+ gtk_dialog_add_action_widget(GTK_DIALOG(dialog_), link,
+ kTaskManagerAboutMemoryLink);
+
+ // Setting the link widget to secondary positions the button on the left side
+ // of the action area (vice versa for RTL layout).
+ gtk_button_box_set_child_secondary(
+ GTK_BUTTON_BOX(GTK_DIALOG(dialog_)->action_area), link, TRUE);
+
+ ConnectAccelerators();
+
+ gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog_)->vbox),
+ gtk_util::kContentAreaSpacing);
+
+ destroy_handler_id_ = g_signal_connect(dialog_, "destroy",
+ G_CALLBACK(OnDestroyThunk), this);
+ g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this);
+ g_signal_connect(dialog_, "button-release-event",
+ G_CALLBACK(OnButtonReleaseEventThunk), this);
+ gtk_widget_add_events(dialog_,
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
+
+ // Wrap the treeview widget in a scrolled window in order to have a frame.
+ GtkWidget* scrolled = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled),
+ GTK_SHADOW_ETCHED_IN);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+ gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog_)->vbox), scrolled);
+
+ CreateTaskManagerTreeview();
+ gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(treeview_), TRUE);
+ g_signal_connect(treeview_, "button-press-event",
+ G_CALLBACK(OnButtonPressEventThunk), this);
+ gtk_widget_add_events(treeview_,
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
+
+ // |selection| is owned by |treeview_|.
+ GtkTreeSelection* selection = gtk_tree_view_get_selection(
+ GTK_TREE_VIEW(treeview_));
+ gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
+ g_signal_connect(selection, "changed",
+ G_CALLBACK(OnSelectionChangedThunk), this);
+
+ gtk_container_add(GTK_CONTAINER(scrolled), treeview_);
+
+ SetInitialDialogSize();
+ gtk_util::ShowDialog(dialog_);
+
+ model_->AddObserver(this);
+}
+
+void TaskManagerGtk::SetInitialDialogSize() {
+ // Hook up to the realize event so we can size the page column to the
+ // size of the leftover space after packing the other columns.
+ g_signal_connect(treeview_, "realize",
+ G_CALLBACK(OnTreeViewRealizeThunk), this);
+ // If we previously saved the dialog's bounds, use them.
+ if (g_browser_process->local_state()) {
+ const DictionaryValue* placement_pref =
+ g_browser_process->local_state()->GetDictionary(
+ prefs::kTaskManagerWindowPlacement);
+ int top = 0, left = 0, bottom = 1, right = 1;
+ if (placement_pref &&
+ placement_pref->GetInteger(L"top", &top) &&
+ placement_pref->GetInteger(L"left", &left) &&
+ placement_pref->GetInteger(L"bottom", &bottom) &&
+ placement_pref->GetInteger(L"right", &right)) {
+ gtk_window_resize(GTK_WINDOW(dialog_),
+ std::max(1, right - left),
+ std::max(1, bottom - top));
+ return;
+ }
+ }
+
+ // Otherwise, just set a default size (GTK will override this if it's not
+ // large enough to hold the window's contents).
+ gtk_window_set_default_size(
+ GTK_WINDOW(dialog_), kDefaultWidth, kDefaultHeight);
+}
+
+void TaskManagerGtk::ConnectAccelerators() {
+ accel_group_ = gtk_accel_group_new();
+ gtk_window_add_accel_group(GTK_WINDOW(dialog_), accel_group_);
+
+ gtk_accel_group_connect(accel_group_,
+ GDK_w, GDK_CONTROL_MASK, GtkAccelFlags(0),
+ g_cclosure_new(G_CALLBACK(OnGtkAcceleratorThunk),
+ this, NULL));
+}
+
+void TaskManagerGtk::CreateTaskManagerTreeview() {
+ process_list_ = gtk_list_store_new(kTaskManagerColumnCount,
+ GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+ G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+ G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
+
+ // Support sorting on all columns.
+ process_list_sort_ = gtk_tree_model_sort_new_with_model(
+ GTK_TREE_MODEL(process_list_));
+ gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
+ kTaskManagerPage,
+ ComparePage, this, NULL);
+ gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
+ kTaskManagerSharedMem,
+ CompareSharedMemory, this, NULL);
+ gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
+ kTaskManagerPrivateMem,
+ ComparePrivateMemory, this, NULL);
+ gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
+ kTaskManagerJavaScriptMemory,
+ CompareV8Memory, this, NULL);
+ gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
+ kTaskManagerCPU,
+ CompareCPU, this, NULL);
+ gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
+ kTaskManagerNetwork,
+ CompareNetwork, this, NULL);
+ gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
+ kTaskManagerProcessID,
+ CompareProcessID, this, NULL);
+ gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
+ kTaskManagerWebCoreImageCache,
+ CompareWebCoreImageCache, this, NULL);
+ gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
+ kTaskManagerWebCoreScriptsCache,
+ CompareWebCoreScriptsCache, this, NULL);
+ gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
+ kTaskManagerWebCoreCssCache,
+ CompareWebCoreCssCache, this, NULL);
+ gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
+ kTaskManagerGoatsTeleported,
+ CompareGoatsTeleported, this, NULL);
+ treeview_ = gtk_tree_view_new_with_model(process_list_sort_);
+
+ // Insert all the columns.
+ TreeViewInsertColumnWithPixbuf(treeview_, IDS_TASK_MANAGER_PAGE_COLUMN);
+ TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_SHARED_MEM_COLUMN);
+ TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN);
+ TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_CPU_COLUMN);
+ TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_NET_COLUMN);
+ TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_PROCESS_ID_COLUMN);
+ TreeViewInsertColumn(treeview_,
+ IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN);
+ TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN);
+ TreeViewInsertColumn(treeview_,
+ IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN);
+ TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN);
+ TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN);
+
+ // Hide some columns by default.
+ TreeViewColumnSetVisible(treeview_, kTaskManagerSharedMem, false);
+ TreeViewColumnSetVisible(treeview_, kTaskManagerProcessID, false);
+ TreeViewColumnSetVisible(treeview_, kTaskManagerJavaScriptMemory, false);
+ TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreImageCache, false);
+ TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreScriptsCache, false);
+ TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreCssCache, false);
+ TreeViewColumnSetVisible(treeview_, kTaskManagerGoatsTeleported, false);
+
+ g_object_unref(process_list_);
+ g_object_unref(process_list_sort_);
+}
+
+bool IsSharedByGroup(int col_id) {
+ switch (col_id) {
+ case IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN:
+ case IDS_TASK_MANAGER_SHARED_MEM_COLUMN:
+ case IDS_TASK_MANAGER_CPU_COLUMN:
+ case IDS_TASK_MANAGER_PROCESS_ID_COLUMN:
+ case IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN:
+ case IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN:
+ case IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN:
+ case IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN:
+ return true;
+ default:
+ return false;
+ }
+}
+
+std::string TaskManagerGtk::GetModelText(int row, int col_id) {
+ if (IsSharedByGroup(col_id) && !model_->IsResourceFirstInGroup(row))
+ return std::string();
+
+ switch (col_id) {
+ case IDS_TASK_MANAGER_PAGE_COLUMN: // Process
+ return WideToUTF8(model_->GetResourceTitle(row));
+
+ case IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN: // Memory
+ return WideToUTF8(model_->GetResourcePrivateMemory(row));
+
+ case IDS_TASK_MANAGER_SHARED_MEM_COLUMN: // Memory
+ return WideToUTF8(model_->GetResourceSharedMemory(row));
+
+ case IDS_TASK_MANAGER_CPU_COLUMN: // CPU
+ return WideToUTF8(model_->GetResourceCPUUsage(row));
+
+ case IDS_TASK_MANAGER_NET_COLUMN: // Net
+ return WideToUTF8(model_->GetResourceNetworkUsage(row));
+
+ case IDS_TASK_MANAGER_PROCESS_ID_COLUMN: // Process ID
+ return WideToUTF8(model_->GetResourceProcessId(row));
+
+ case IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN:
+ return WideToUTF8(model_->GetResourceV8MemoryAllocatedSize(row));
+
+ case IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN:
+ return WideToUTF8(model_->GetResourceWebCoreImageCacheSize(row));
+
+ case IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN:
+ return WideToUTF8(model_->GetResourceWebCoreScriptsCacheSize(row));
+
+ case IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN:
+ return WideToUTF8(model_->GetResourceWebCoreCSSCacheSize(row));
+
+ case IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN: // Goats Teleported!
+ return WideToUTF8(model_->GetResourceGoatsTeleported(row));
+
+ default:
+ NOTREACHED();
+ return std::string();
+ }
+}
+
+GdkPixbuf* TaskManagerGtk::GetModelIcon(int row) {
+ SkBitmap icon = model_->GetResourceIcon(row);
+ if (icon.pixelRef() ==
+ ResourceBundle::GetSharedInstance().GetBitmapNamed(
+ IDR_DEFAULT_FAVICON)->pixelRef()) {
+ return static_cast<GdkPixbuf*>(g_object_ref(
+ GtkThemeProvider::GetDefaultFavicon(true)));
+ }
+
+ return gfx::GdkPixbufFromSkBitmap(&icon);
+}
+
+void TaskManagerGtk::SetRowDataFromModel(int row, GtkTreeIter* iter) {
+ GdkPixbuf* icon = GetModelIcon(row);
+ std::string page = GetModelText(row, IDS_TASK_MANAGER_PAGE_COLUMN);
+ std::string shared_mem = GetModelText(
+ row, IDS_TASK_MANAGER_SHARED_MEM_COLUMN);
+ std::string priv_mem = GetModelText(row, IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN);
+ std::string cpu = GetModelText(row, IDS_TASK_MANAGER_CPU_COLUMN);
+ std::string net = GetModelText(row, IDS_TASK_MANAGER_NET_COLUMN);
+ std::string procid = GetModelText(row, IDS_TASK_MANAGER_PROCESS_ID_COLUMN);
+
+ // Querying the renderer metrics is slow as it has to do IPC, so only do it
+ // when the columns are visible.
+ std::string javascript_memory;
+ if (TreeViewColumnIsVisible(treeview_, kTaskManagerJavaScriptMemory))
+ javascript_memory = GetModelText(
+ row, IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN);
+ std::string wk_img_cache;
+ if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreImageCache))
+ wk_img_cache = GetModelText(
+ row, IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN);
+ std::string wk_scripts_cache;
+ if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreScriptsCache))
+ wk_scripts_cache = GetModelText(
+ row, IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN);
+ std::string wk_css_cache;
+ if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreCssCache))
+ wk_css_cache = GetModelText(
+ row, IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN);
+
+ std::string goats = GetModelText(
+ row, IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN);
+ gtk_list_store_set(process_list_, iter,
+ kTaskManagerIcon, icon,
+ kTaskManagerPage, page.c_str(),
+ kTaskManagerSharedMem, shared_mem.c_str(),
+ kTaskManagerPrivateMem, priv_mem.c_str(),
+ kTaskManagerCPU, cpu.c_str(),
+ kTaskManagerNetwork, net.c_str(),
+ kTaskManagerProcessID, procid.c_str(),
+ kTaskManagerJavaScriptMemory, javascript_memory.c_str(),
+ kTaskManagerWebCoreImageCache, wk_img_cache.c_str(),
+ kTaskManagerWebCoreScriptsCache, wk_scripts_cache.c_str(),
+ kTaskManagerWebCoreCssCache, wk_css_cache.c_str(),
+ kTaskManagerGoatsTeleported, goats.c_str(),
+ -1);
+ g_object_unref(icon);
+}
+
+void TaskManagerGtk::KillSelectedProcesses() {
+ GtkTreeSelection* selection = gtk_tree_view_get_selection(
+ GTK_TREE_VIEW(treeview_));
+
+ GtkTreeModel* model;
+ GList* paths = gtk_tree_selection_get_selected_rows(selection, &model);
+ for (GList* item = paths; item; item = item->next) {
+ GtkTreePath* path = gtk_tree_model_sort_convert_path_to_child_path(
+ GTK_TREE_MODEL_SORT(process_list_sort_),
+ reinterpret_cast<GtkTreePath*>(item->data));
+ int row = gtk_tree::GetRowNumForPath(path);
+ gtk_tree_path_free(path);
+ task_manager_->KillProcess(row);
+ }
+ g_list_foreach(paths, reinterpret_cast<GFunc>(gtk_tree_path_free), NULL);
+ g_list_free(paths);
+}
+
+void TaskManagerGtk::ShowContextMenu() {
+ if (!menu_controller_.get())
+ menu_controller_.reset(new ContextMenuController(this));
+
+ menu_controller_->RunMenu();
+}
+
+void TaskManagerGtk::ActivateFocusedTab() {
+ GtkTreeSelection* selection = gtk_tree_view_get_selection(
+ GTK_TREE_VIEW(treeview_));
+
+ // If the user has just double clicked, only one item is selected.
+ GtkTreeModel* model;
+ GList* selected = gtk_tree_selection_get_selected_rows(selection, &model);
+ if (selected) {
+ GtkTreePath* path = gtk_tree_model_sort_convert_path_to_child_path(
+ GTK_TREE_MODEL_SORT(process_list_sort_),
+ reinterpret_cast<GtkTreePath*>(selected->data));
+ int row = gtk_tree::GetRowNumForPath(path);
+ gtk_tree_path_free(path);
+ task_manager_->ActivateProcess(row);
+ }
+ g_list_foreach(selected, reinterpret_cast<GFunc>(gtk_tree_path_free), NULL);
+ g_list_free(selected);
+}
+
+void TaskManagerGtk::OnLinkActivated() {
+ task_manager_->OpenAboutMemory();
+}
+
+gint TaskManagerGtk::CompareImpl(GtkTreeModel* model, GtkTreeIter* a,
+ GtkTreeIter* b, int id) {
+ int row1 = gtk_tree::GetRowNumForIter(model, a);
+ int row2 = gtk_tree::GetRowNumForIter(model, b);
+
+ // When sorting by non-grouped attributes (e.g., Network), just do a normal
+ // sort.
+ if (!IsSharedByGroup(id))
+ return model_->CompareValues(row1, row2, id);
+
+ // Otherwise, make sure grouped resources are shown together.
+ std::pair<int, int> group_range1 = model_->GetGroupRangeForResource(row1);
+ std::pair<int, int> group_range2 = model_->GetGroupRangeForResource(row2);
+
+ if (group_range1 == group_range2) {
+ // Sort within groups.
+ // We want the first-in-group row at the top, whether we are sorting up or
+ // down.
+ GtkSortType sort_type;
+ gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(process_list_sort_),
+ NULL, &sort_type);
+ if (row1 == group_range1.first)
+ return sort_type == GTK_SORT_ASCENDING ? -1 : 1;
+ if (row2 == group_range2.first)
+ return sort_type == GTK_SORT_ASCENDING ? 1 : -1;
+
+ return model_->CompareValues(row1, row2, id);
+ } else {
+ // Sort between groups.
+ // Compare by the first-in-group rows so that the groups will stay together.
+ return model_->CompareValues(group_range1.first, group_range2.first, id);
+ }
+}
+
+void TaskManagerGtk::OnDestroy(GtkWidget* dialog) {
+ instance_ = NULL;
+ delete this;
+}
+
+void TaskManagerGtk::OnResponse(GtkWidget* dialog, gint response_id) {
+ if (response_id == GTK_RESPONSE_DELETE_EVENT) {
+ // Store the dialog's size so we can restore it the next time it's opened.
+ if (g_browser_process->local_state()) {
+ gfx::Rect dialog_bounds = gtk_util::GetDialogBounds(GTK_WIDGET(dialog));
+
+ DictionaryValue* placement_pref =
+ g_browser_process->local_state()->GetMutableDictionary(
+ prefs::kTaskManagerWindowPlacement);
+ // Note that we store left/top for consistency with Windows, but that we
+ // *don't* restore them.
+ placement_pref->SetInteger(L"left", dialog_bounds.x());
+ placement_pref->SetInteger(L"top", dialog_bounds.y());
+ placement_pref->SetInteger(L"right", dialog_bounds.right());
+ placement_pref->SetInteger(L"bottom", dialog_bounds.bottom());
+ placement_pref->SetBoolean(L"maximized", false);
+ }
+
+ instance_ = NULL;
+ delete this;
+ } else if (response_id == kTaskManagerResponseKill) {
+ KillSelectedProcesses();
+ } else if (response_id == kTaskManagerAboutMemoryLink) {
+ OnLinkActivated();
+ } else if (response_id == kTaskManagerPurgeMemory) {
+ MemoryPurger::PurgeAll();
+ }
+}
+
+void TaskManagerGtk::OnTreeViewRealize(GtkTreeView* treeview) {
+ // Four columns show by default: the page column, the memory column, the
+ // CPU column, and the network column. Initially we set the page column to
+ // take all the extra space, with the other columns being sized to fit the
+ // column names. Here we turn off the expand property of the first column
+ // (to make the table behave sanely when the user resizes columns) and set
+ // the effective sizes of all four default columns to the automatically
+ // chosen size before any rows are added. This causes them to stay at that
+ // size even if the data would overflow, preventing a horizontal scroll
+ // bar from appearing due to the row data.
+ const TaskManagerColumn dfl_columns[] = {kTaskManagerNetwork, kTaskManagerCPU,
+ kTaskManagerPrivateMem};
+ GtkTreeViewColumn* column = NULL;
+ gint width;
+ for (size_t i = 0; i < arraysize(dfl_columns); ++i) {
+ column = gtk_tree_view_get_column(treeview,
+ TreeViewColumnIndexFromID(dfl_columns[i]));
+ width = gtk_tree_view_column_get_width(column);
+ TreeViewColumnSetWidth(column, width);
+ }
+ // Do the page column separately since it's a little different.
+ column = gtk_tree_view_get_column(treeview,
+ TreeViewColumnIndexFromID(kTaskManagerPage));
+ width = gtk_tree_view_column_get_width(column);
+ // Turn expanding back off to make resizing columns behave sanely.
+ gtk_tree_view_column_set_expand(column, FALSE);
+ TreeViewColumnSetWidth(column, width);
+}
+
+void TaskManagerGtk::OnSelectionChanged(GtkTreeSelection* selection) {
+ if (ignore_selection_changed_)
+ return;
+ AutoReset<bool> autoreset(&ignore_selection_changed_, true);
+
+ // The set of groups that should be selected.
+ std::set<std::pair<int, int> > ranges;
+ bool selection_contains_browser_process = false;
+
+ GtkTreeModel* model;
+ GList* paths = gtk_tree_selection_get_selected_rows(selection, &model);
+ for (GList* item = paths; item; item = item->next) {
+ GtkTreePath* path = gtk_tree_model_sort_convert_path_to_child_path(
+ GTK_TREE_MODEL_SORT(process_list_sort_),
+ reinterpret_cast<GtkTreePath*>(item->data));
+ int row = gtk_tree::GetRowNumForPath(path);
+ gtk_tree_path_free(path);
+ if (task_manager_->IsBrowserProcess(row))
+ selection_contains_browser_process = true;
+ ranges.insert(model_->GetGroupRangeForResource(row));
+ }
+ g_list_foreach(paths, reinterpret_cast<GFunc>(gtk_tree_path_free), NULL);
+ g_list_free(paths);
+
+ for (std::set<std::pair<int, int> >::iterator iter = ranges.begin();
+ iter != ranges.end(); ++iter) {
+ for (int i = 0; i < iter->second; ++i) {
+ GtkTreePath* child_path = gtk_tree_path_new_from_indices(iter->first + i,
+ -1);
+ GtkTreePath* sort_path = gtk_tree_model_sort_convert_child_path_to_path(
+ GTK_TREE_MODEL_SORT(process_list_sort_), child_path);
+ gtk_tree_selection_select_path(selection, sort_path);
+ gtk_tree_path_free(child_path);
+ gtk_tree_path_free(sort_path);
+ }
+ }
+
+ bool sensitive = (paths != NULL) && !selection_contains_browser_process;
+ gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_),
+ kTaskManagerResponseKill, sensitive);
+}
+
+gboolean TaskManagerGtk::OnButtonPressEvent(GtkWidget* widget,
+ GdkEventButton* event) {
+ if (event->type == GDK_2BUTTON_PRESS)
+ ActivateFocusedTab();
+
+ return FALSE;
+}
+
+gboolean TaskManagerGtk::OnButtonReleaseEvent(GtkWidget* widget,
+ GdkEventButton* event) {
+ if (event->button == 3)
+ ShowContextMenu();
+
+ return FALSE;
+}
+
+gboolean TaskManagerGtk::OnGtkAccelerator(GtkAccelGroup* accel_group,
+ GObject* acceleratable,
+ guint keyval,
+ GdkModifierType modifier) {
+ if (keyval == GDK_w && modifier == GDK_CONTROL_MASK) {
+ // The GTK_RESPONSE_DELETE_EVENT response must be sent before the widget
+ // is destroyed. The deleted object will receive gtk signals otherwise.
+ gtk_dialog_response(GTK_DIALOG(dialog_), GTK_RESPONSE_DELETE_EVENT);
+ }
+
+ return TRUE;
+}