Home | History | Annotate | Download | only in gtk
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/gtk/task_manager_gtk.h"
      6 
      7 #include <gdk/gdkkeysyms.h>
      8 
      9 #include <algorithm>
     10 #include <set>
     11 #include <utility>
     12 #include <vector>
     13 
     14 #include "base/auto_reset.h"
     15 #include "base/command_line.h"
     16 #include "base/logging.h"
     17 #include "base/prefs/pref_service.h"
     18 #include "base/strings/utf_string_conversions.h"
     19 #include "chrome/browser/browser_process.h"
     20 #include "chrome/browser/defaults.h"
     21 #include "chrome/browser/memory_purger.h"
     22 #include "chrome/browser/prefs/scoped_user_pref_update.h"
     23 #include "chrome/browser/ui/gtk/gtk_chrome_link_button.h"
     24 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
     25 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
     26 #include "chrome/browser/ui/gtk/gtk_tree.h"
     27 #include "chrome/browser/ui/gtk/gtk_util.h"
     28 #include "chrome/browser/ui/gtk/menu_gtk.h"
     29 #include "chrome/browser/ui/host_desktop.h"
     30 #include "chrome/common/chrome_switches.h"
     31 #include "chrome/common/pref_names.h"
     32 #include "grit/chromium_strings.h"
     33 #include "grit/ui_resources.h"
     34 #include "third_party/skia/include/core/SkBitmap.h"
     35 #include "ui/base/gtk/gtk_hig_constants.h"
     36 #include "ui/base/l10n/l10n_util.h"
     37 #include "ui/base/models/simple_menu_model.h"
     38 #include "ui/base/resource/resource_bundle.h"
     39 #include "ui/gfx/gtk_util.h"
     40 #include "ui/gfx/image/image.h"
     41 
     42 namespace {
     43 
     44 // The task manager window default size.
     45 const int kDefaultWidth = 460;
     46 const int kDefaultHeight = 270;
     47 
     48 // The resource id for the 'End process' button.
     49 const gint kTaskManagerResponseKill = 1;
     50 
     51 // The resource id for the 'Stats for nerds' link button.
     52 const gint kTaskManagerAboutMemoryLink = 2;
     53 
     54 // The resource id for the 'Purge Memory' button
     55 const gint kTaskManagerPurgeMemory = 3;
     56 
     57 enum TaskManagerColumn {
     58   kTaskManagerIcon,
     59   kTaskManagerTask,
     60   kTaskManagerProfileName,
     61   kTaskManagerSharedMem,
     62   kTaskManagerPrivateMem,
     63   kTaskManagerCPU,
     64   kTaskManagerNetwork,
     65   kTaskManagerProcessID,
     66   kTaskManagerJavaScriptMemory,
     67   kTaskManagerWebCoreImageCache,
     68   kTaskManagerWebCoreScriptsCache,
     69   kTaskManagerWebCoreCssCache,
     70   kTaskManagerVideoMemory,
     71   kTaskManagerFPS,
     72   kTaskManagerSqliteMemoryUsed,
     73   kTaskManagerGoatsTeleported,
     74   kTaskManagerColumnCount,
     75 };
     76 
     77 const TaskManagerColumn kTaskManagerLastVisibleColumn =
     78     kTaskManagerGoatsTeleported;
     79 
     80 TaskManagerColumn TaskManagerResourceIDToColumnID(int id) {
     81   switch (id) {
     82     case IDS_TASK_MANAGER_TASK_COLUMN:
     83       return kTaskManagerTask;
     84     case IDS_TASK_MANAGER_PROFILE_NAME_COLUMN:
     85       return kTaskManagerProfileName;
     86     case IDS_TASK_MANAGER_SHARED_MEM_COLUMN:
     87       return kTaskManagerSharedMem;
     88     case IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN:
     89       return kTaskManagerPrivateMem;
     90     case IDS_TASK_MANAGER_CPU_COLUMN:
     91       return kTaskManagerCPU;
     92     case IDS_TASK_MANAGER_NET_COLUMN:
     93       return kTaskManagerNetwork;
     94     case IDS_TASK_MANAGER_PROCESS_ID_COLUMN:
     95       return kTaskManagerProcessID;
     96     case IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN:
     97       return kTaskManagerJavaScriptMemory;
     98     case IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN:
     99       return kTaskManagerWebCoreImageCache;
    100     case IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN:
    101       return kTaskManagerWebCoreScriptsCache;
    102     case IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN:
    103       return kTaskManagerWebCoreCssCache;
    104     case IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN:
    105       return kTaskManagerVideoMemory;
    106     case IDS_TASK_MANAGER_FPS_COLUMN:
    107       return kTaskManagerFPS;
    108     case IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN:
    109       return kTaskManagerSqliteMemoryUsed;
    110     case IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN:
    111       return kTaskManagerGoatsTeleported;
    112     default:
    113       NOTREACHED();
    114       return static_cast<TaskManagerColumn>(-1);
    115   }
    116 }
    117 
    118 int TaskManagerColumnIDToResourceID(int id) {
    119   switch (id) {
    120     case kTaskManagerTask:
    121       return IDS_TASK_MANAGER_TASK_COLUMN;
    122     case kTaskManagerProfileName:
    123       return IDS_TASK_MANAGER_PROFILE_NAME_COLUMN;
    124     case kTaskManagerSharedMem:
    125       return IDS_TASK_MANAGER_SHARED_MEM_COLUMN;
    126     case kTaskManagerPrivateMem:
    127       return IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN;
    128     case kTaskManagerCPU:
    129       return IDS_TASK_MANAGER_CPU_COLUMN;
    130     case kTaskManagerNetwork:
    131       return IDS_TASK_MANAGER_NET_COLUMN;
    132     case kTaskManagerProcessID:
    133       return IDS_TASK_MANAGER_PROCESS_ID_COLUMN;
    134     case kTaskManagerJavaScriptMemory:
    135       return IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN;
    136     case kTaskManagerWebCoreImageCache:
    137       return IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN;
    138     case kTaskManagerWebCoreScriptsCache:
    139       return IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN;
    140     case kTaskManagerWebCoreCssCache:
    141       return IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN;
    142     case kTaskManagerVideoMemory:
    143       return IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN;
    144     case kTaskManagerFPS:
    145       return IDS_TASK_MANAGER_FPS_COLUMN;
    146     case kTaskManagerSqliteMemoryUsed:
    147       return IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN;
    148     case kTaskManagerGoatsTeleported:
    149       return IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN;
    150     default:
    151       NOTREACHED();
    152       return -1;
    153   }
    154 }
    155 
    156 // Should be used for all gtk_tree_view functions that require a column index on
    157 // input.
    158 //
    159 // We need colid - 1 because the gtk_tree_view function is asking for the
    160 // column index, not the column id, and both kTaskManagerIcon and
    161 // kTaskManagerTask are in the same column index, so all column IDs are off by
    162 // one.
    163 int TreeViewColumnIndexFromID(TaskManagerColumn colid) {
    164   return colid - 1;
    165 }
    166 
    167 // Shows or hides a treeview column.
    168 void TreeViewColumnSetVisible(GtkWidget* treeview, TaskManagerColumn colid,
    169                               bool visible) {
    170   GtkTreeViewColumn* column = gtk_tree_view_get_column(
    171       GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid));
    172   gtk_tree_view_column_set_visible(column, visible);
    173 }
    174 
    175 bool TreeViewColumnIsVisible(GtkWidget* treeview, TaskManagerColumn colid) {
    176   GtkTreeViewColumn* column = gtk_tree_view_get_column(
    177       GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid));
    178   return gtk_tree_view_column_get_visible(column);
    179 }
    180 
    181 // The task column is special because it has an icon and it gets special
    182 // treatment with respect to resizing the columns.
    183 void TreeViewInsertTaskColumn(GtkWidget* treeview, int resid) {
    184   int colid = TaskManagerResourceIDToColumnID(resid);
    185   GtkTreeViewColumn* column = gtk_tree_view_column_new();
    186   gtk_tree_view_column_set_title(column,
    187                                  l10n_util::GetStringUTF8(resid).c_str());
    188   gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(treeview), colid);
    189   GtkCellRenderer* image_renderer = gtk_cell_renderer_pixbuf_new();
    190   gtk_tree_view_column_pack_start(column, image_renderer, FALSE);
    191   gtk_tree_view_column_add_attribute(column, image_renderer,
    192                                      "pixbuf", kTaskManagerIcon);
    193   GtkCellRenderer* text_renderer = gtk_cell_renderer_text_new();
    194   gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
    195   gtk_tree_view_column_add_attribute(column, text_renderer, "markup", colid);
    196   gtk_tree_view_column_set_resizable(column, TRUE);
    197   // This is temporary: we'll turn expanding off after getting the size.
    198   gtk_tree_view_column_set_expand(column, TRUE);
    199   gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
    200   gtk_tree_view_column_set_sort_column_id(column, colid);
    201 }
    202 
    203 // Inserts a column with a column id of |colid| and |name|.
    204 void TreeViewInsertColumnWithName(GtkWidget* treeview,
    205                                   TaskManagerColumn colid, const char* name) {
    206   GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
    207   gtk_tree_view_insert_column_with_attributes(
    208       GTK_TREE_VIEW(treeview), -1,
    209       name, renderer,
    210       "text", colid,
    211       NULL);
    212   GtkTreeViewColumn* column = gtk_tree_view_get_column(
    213       GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid));
    214   gtk_tree_view_column_set_resizable(column, TRUE);
    215   gtk_tree_view_column_set_sort_column_id(column, colid);
    216 }
    217 
    218 // Loads the column name from |resid| and uses the corresponding
    219 // TaskManagerColumn value as the column id to insert into the treeview.
    220 void TreeViewInsertColumn(GtkWidget* treeview, int resid) {
    221   TreeViewInsertColumnWithName(treeview, TaskManagerResourceIDToColumnID(resid),
    222                                l10n_util::GetStringUTF8(resid).c_str());
    223 }
    224 
    225 // Set the current width of the column without forcing a fixed or maximum
    226 // width as gtk_tree_view_column_set_[fixed|maximum]_width() would. This would
    227 // basically be gtk_tree_view_column_set_width() except that there is no such
    228 // function. It turns out that other applications have done similar hacks to do
    229 // the same thing - search the web for that nonexistent function name! :)
    230 void TreeViewColumnSetWidth(GtkTreeViewColumn* column, gint width) {
    231   column->width = width;
    232   column->resized_width = width;
    233   column->use_resized_width = TRUE;
    234   // Needed for use_resized_width to be effective.
    235   gtk_widget_queue_resize(column->tree_view);
    236 }
    237 
    238 }  // namespace
    239 
    240 class TaskManagerGtk::ContextMenuController
    241     : public ui::SimpleMenuModel::Delegate {
    242  public:
    243   explicit ContextMenuController(TaskManagerGtk* task_manager)
    244       : task_manager_(task_manager) {
    245     menu_model_.reset(new ui::SimpleMenuModel(this));
    246     for (int i = kTaskManagerTask; i <= kTaskManagerLastVisibleColumn; i++) {
    247       menu_model_->AddCheckItemWithStringId(
    248           i, TaskManagerColumnIDToResourceID(i));
    249     }
    250     menu_.reset(new MenuGtk(NULL, menu_model_.get()));
    251   }
    252 
    253   virtual ~ContextMenuController() {}
    254 
    255   void RunMenu(const gfx::Point& point, guint32 event_time) {
    256     menu_->PopupAsContext(point, event_time);
    257   }
    258 
    259   void Cancel() {
    260     task_manager_ = NULL;
    261     menu_->Cancel();
    262   }
    263 
    264  private:
    265   // ui::SimpleMenuModel::Delegate implementation:
    266   virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
    267     if (!task_manager_)
    268       return false;
    269 
    270     return true;
    271   }
    272 
    273   virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
    274     if (!task_manager_)
    275       return false;
    276 
    277     TaskManagerColumn colid = static_cast<TaskManagerColumn>(command_id);
    278     return TreeViewColumnIsVisible(task_manager_->treeview_, colid);
    279   }
    280 
    281   virtual bool GetAcceleratorForCommandId(
    282       int command_id,
    283       ui::Accelerator* accelerator) OVERRIDE {
    284     return false;
    285   }
    286 
    287   virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
    288     if (!task_manager_)
    289       return;
    290 
    291     TaskManagerColumn colid = static_cast<TaskManagerColumn>(command_id);
    292     bool visible = !TreeViewColumnIsVisible(task_manager_->treeview_, colid);
    293     TreeViewColumnSetVisible(task_manager_->treeview_, colid, visible);
    294   }
    295 
    296   // The model and view for the right click context menu.
    297   scoped_ptr<ui::SimpleMenuModel> menu_model_;
    298   scoped_ptr<MenuGtk> menu_;
    299 
    300   // The TaskManager the context menu was brought up for. Set to NULL when the
    301   // menu is canceled.
    302   TaskManagerGtk* task_manager_;
    303 
    304   DISALLOW_COPY_AND_ASSIGN(ContextMenuController);
    305 };
    306 
    307 TaskManagerGtk::TaskManagerGtk()
    308   : task_manager_(TaskManager::GetInstance()),
    309     model_(TaskManager::GetInstance()->model()),
    310     dialog_(NULL),
    311     treeview_(NULL),
    312     process_list_(NULL),
    313     process_count_(0),
    314     ignore_selection_changed_(false) {
    315   Init();
    316 }
    317 
    318 // static
    319 TaskManagerGtk* TaskManagerGtk::instance_ = NULL;
    320 
    321 TaskManagerGtk::~TaskManagerGtk() {
    322   model_->RemoveObserver(this);
    323   task_manager_->OnWindowClosed();
    324 
    325   gtk_accel_group_disconnect_key(accel_group_, GDK_w, GDK_CONTROL_MASK);
    326   gtk_window_remove_accel_group(GTK_WINDOW(dialog_), accel_group_);
    327   g_object_unref(accel_group_);
    328   accel_group_ = NULL;
    329 
    330   // Disconnect the destroy signal so it doesn't delete |this|.
    331   g_signal_handler_disconnect(G_OBJECT(dialog_), destroy_handler_id_);
    332   gtk_widget_destroy(dialog_);
    333 }
    334 
    335 ////////////////////////////////////////////////////////////////////////////////
    336 // TaskManagerGtk, TaskManagerModelObserver implementation:
    337 
    338 void TaskManagerGtk::OnModelChanged() {
    339   // Nothing to do.
    340 }
    341 
    342 void TaskManagerGtk::OnItemsChanged(int start, int length) {
    343   GtkTreeIter iter;
    344   if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &iter,
    345                                      NULL, start)) {
    346     NOTREACHED() << "Can't get child " << start <<
    347         " from GTK_TREE_MODEL(process_list_)";
    348   }
    349 
    350   for (int i = start; i < start + length; i++) {
    351     SetRowDataFromModel(i, &iter);
    352     if (i != start + length - 1) {
    353       if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(process_list_), &iter)) {
    354         NOTREACHED() << "Can't get next GtkTreeIter object from process_list_ "
    355                         "iterator at position " << i;
    356       }
    357     }
    358   }
    359 }
    360 
    361 void TaskManagerGtk::OnItemsAdded(int start, int length) {
    362   base::AutoReset<bool> autoreset(&ignore_selection_changed_, true);
    363 
    364   GtkTreeIter iter;
    365   if (start == 0) {
    366     gtk_list_store_prepend(process_list_, &iter);
    367   } else if (start >= process_count_) {
    368     gtk_list_store_append(process_list_, &iter);
    369   } else {
    370     GtkTreeIter sibling;
    371     gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &sibling,
    372                                   NULL, start);
    373     gtk_list_store_insert_before(process_list_, &iter, &sibling);
    374   }
    375 
    376   SetRowDataFromModel(start, &iter);
    377 
    378   for (int i = start + 1; i < start + length; i++) {
    379     gtk_list_store_insert_after(process_list_, &iter, &iter);
    380     SetRowDataFromModel(i, &iter);
    381   }
    382 
    383   process_count_ += length;
    384 }
    385 
    386 void TaskManagerGtk::OnItemsRemoved(int start, int length) {
    387   {
    388     base::AutoReset<bool> autoreset(&ignore_selection_changed_, true);
    389 
    390     GtkTreeIter iter;
    391     gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &iter,
    392                                   NULL, start);
    393 
    394     for (int i = 0; i < length; i++) {
    395       // |iter| is moved to the next valid node when the current node is
    396       // removed.
    397       gtk_list_store_remove(process_list_, &iter);
    398     }
    399 
    400     process_count_ -= length;
    401   }
    402 
    403   // It is possible that we have removed the current selection; run selection
    404   // changed to detect that case.
    405   OnSelectionChanged(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview_)));
    406 }
    407 
    408 ////////////////////////////////////////////////////////////////////////////////
    409 // TaskManagerGtk, public:
    410 
    411 void TaskManagerGtk::Close() {
    412   // Blow away our dialog - this will cause TaskManagerGtk to free itself.
    413   gtk_widget_destroy(dialog_);
    414   DCHECK(!instance_);
    415 }
    416 
    417 // static
    418 void TaskManagerGtk::Show() {
    419   if (instance_) {
    420     // If there's a Task manager window open already, just activate it.
    421     gtk_util::PresentWindow(instance_->dialog_, 0);
    422   } else {
    423     instance_ = new TaskManagerGtk();
    424     instance_->model_->StartUpdating();
    425   }
    426 }
    427 
    428 ////////////////////////////////////////////////////////////////////////////////
    429 // TaskManagerGtk, private:
    430 
    431 void TaskManagerGtk::Init() {
    432   dialog_ = gtk_dialog_new_with_buttons(
    433       l10n_util::GetStringUTF8(IDS_TASK_MANAGER_TITLE).c_str(),
    434       // Task Manager window is shared between all browsers.
    435       NULL,
    436       GTK_DIALOG_NO_SEPARATOR,
    437       NULL);
    438 
    439   // Allow browser windows to go in front of the task manager dialog in
    440   // metacity.
    441   gtk_window_set_type_hint(GTK_WINDOW(dialog_), GDK_WINDOW_TYPE_HINT_NORMAL);
    442 
    443   if (CommandLine::ForCurrentProcess()->HasSwitch(
    444       switches::kPurgeMemoryButton)) {
    445     gtk_dialog_add_button(GTK_DIALOG(dialog_),
    446         l10n_util::GetStringUTF8(IDS_TASK_MANAGER_PURGE_MEMORY).c_str(),
    447         kTaskManagerPurgeMemory);
    448   }
    449 
    450   if (browser_defaults::kShowCancelButtonInTaskManager) {
    451     gtk_dialog_add_button(GTK_DIALOG(dialog_),
    452         l10n_util::GetStringUTF8(IDS_CLOSE).c_str(),
    453         GTK_RESPONSE_DELETE_EVENT);
    454   }
    455 
    456   gtk_dialog_add_button(GTK_DIALOG(dialog_),
    457       l10n_util::GetStringUTF8(IDS_TASK_MANAGER_KILL).c_str(),
    458       kTaskManagerResponseKill);
    459 
    460   // The response button should not be sensitive when the dialog is first opened
    461   // because the selection is initially empty.
    462   gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_),
    463                                     kTaskManagerResponseKill, FALSE);
    464 
    465   GtkWidget* link = gtk_chrome_link_button_new(
    466       l10n_util::GetStringUTF8(IDS_TASK_MANAGER_ABOUT_MEMORY_LINK).c_str());
    467   gtk_dialog_add_action_widget(GTK_DIALOG(dialog_), link,
    468                                kTaskManagerAboutMemoryLink);
    469 
    470   // Setting the link widget to secondary positions the button on the left side
    471   // of the action area (vice versa for RTL layout).
    472   GtkWidget* action_area = gtk_dialog_get_action_area(GTK_DIALOG(dialog_));
    473   gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(action_area), link, TRUE);
    474 
    475   ConnectAccelerators();
    476 
    477   GtkWidget* content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog_));
    478   gtk_box_set_spacing(GTK_BOX(content_area), ui::kContentAreaSpacing);
    479 
    480   destroy_handler_id_ = g_signal_connect(dialog_, "destroy",
    481                                          G_CALLBACK(OnDestroyThunk), this);
    482   g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this);
    483   g_signal_connect(dialog_, "button-press-event",
    484                    G_CALLBACK(OnButtonEventThunk), this);
    485   gtk_widget_add_events(dialog_,
    486                         GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
    487 
    488   // Wrap the treeview widget in a scrolled window in order to have a frame.
    489   GtkWidget* scrolled = gtk_scrolled_window_new(NULL, NULL);
    490   gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled),
    491                                       GTK_SHADOW_ETCHED_IN);
    492   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
    493                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    494 
    495   gtk_container_add(GTK_CONTAINER(content_area), scrolled);
    496 
    497   CreateTaskManagerTreeview();
    498   gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(treeview_), TRUE);
    499   g_signal_connect(treeview_, "row-activated",
    500                    G_CALLBACK(OnRowActivatedThunk), this);
    501   g_signal_connect(treeview_, "button-press-event",
    502                    G_CALLBACK(OnButtonEventThunk), this);
    503 
    504   // |selection| is owned by |treeview_|.
    505   GtkTreeSelection* selection = gtk_tree_view_get_selection(
    506       GTK_TREE_VIEW(treeview_));
    507   gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
    508   g_signal_connect(selection, "changed",
    509                    G_CALLBACK(OnSelectionChangedThunk), this);
    510 
    511   gtk_container_add(GTK_CONTAINER(scrolled), treeview_);
    512 
    513   SetInitialDialogSize();
    514   gtk_util::ShowDialog(dialog_);
    515 
    516   // If the model already has resources, we need to add them before we start
    517   // observing events.
    518   if (model_->ResourceCount() > 0)
    519     OnItemsAdded(0, model_->ResourceCount());
    520 
    521   model_->AddObserver(this);
    522 }
    523 
    524 void TaskManagerGtk::SetInitialDialogSize() {
    525   // Hook up to the realize event so we can size the task column to the
    526   // size of the leftover space after packing the other columns.
    527   g_signal_connect(treeview_, "realize",
    528                    G_CALLBACK(OnTreeViewRealizeThunk), this);
    529   // If we previously saved the dialog's bounds, use them.
    530   if (g_browser_process->local_state()) {
    531     const DictionaryValue* placement_pref =
    532         g_browser_process->local_state()->GetDictionary(
    533             prefs::kTaskManagerWindowPlacement);
    534     int top = 0, left = 0, bottom = 1, right = 1;
    535     if (placement_pref &&
    536         placement_pref->GetInteger("top", &top) &&
    537         placement_pref->GetInteger("left", &left) &&
    538         placement_pref->GetInteger("bottom", &bottom) &&
    539         placement_pref->GetInteger("right", &right)) {
    540       gtk_window_resize(GTK_WINDOW(dialog_),
    541                         std::max(1, right - left),
    542                         std::max(1, bottom - top));
    543       return;
    544     }
    545   }
    546 
    547   // Otherwise, just set a default size (GTK will override this if it's not
    548   // large enough to hold the window's contents).
    549   gtk_window_set_default_size(
    550       GTK_WINDOW(dialog_), kDefaultWidth, kDefaultHeight);
    551 }
    552 
    553 void TaskManagerGtk::ConnectAccelerators() {
    554   accel_group_ = gtk_accel_group_new();
    555   gtk_window_add_accel_group(GTK_WINDOW(dialog_), accel_group_);
    556 
    557   gtk_accel_group_connect(accel_group_,
    558                           GDK_w, GDK_CONTROL_MASK, GtkAccelFlags(0),
    559                           g_cclosure_new(G_CALLBACK(OnGtkAcceleratorThunk),
    560                                          this, NULL));
    561 }
    562 
    563 void TaskManagerGtk::CreateTaskManagerTreeview() {
    564   process_list_ = gtk_list_store_new(kTaskManagerColumnCount,
    565       GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
    566       G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
    567       G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
    568       G_TYPE_STRING, G_TYPE_STRING);
    569 
    570   // Support sorting on all columns.
    571   process_list_sort_ = gtk_tree_model_sort_new_with_model(
    572       GTK_TREE_MODEL(process_list_));
    573   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
    574                                   kTaskManagerTask,
    575                                   ComparePage, this, NULL);
    576   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
    577                                   kTaskManagerTask,
    578                                   CompareProfileName, this, NULL);
    579   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
    580                                   kTaskManagerSharedMem,
    581                                   CompareSharedMemory, this, NULL);
    582   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
    583                                   kTaskManagerPrivateMem,
    584                                   ComparePrivateMemory, this, NULL);
    585   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
    586                                   kTaskManagerJavaScriptMemory,
    587                                   CompareV8Memory, this, NULL);
    588   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
    589                                   kTaskManagerCPU,
    590                                   CompareCPU, this, NULL);
    591   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
    592                                   kTaskManagerNetwork,
    593                                   CompareNetwork, this, NULL);
    594   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
    595                                   kTaskManagerProcessID,
    596                                   CompareProcessID, this, NULL);
    597   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
    598                                   kTaskManagerWebCoreImageCache,
    599                                   CompareWebCoreImageCache, this, NULL);
    600   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
    601                                   kTaskManagerWebCoreScriptsCache,
    602                                   CompareWebCoreScriptsCache, this, NULL);
    603   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
    604                                   kTaskManagerWebCoreCssCache,
    605                                   CompareWebCoreCssCache, this, NULL);
    606   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
    607                                   kTaskManagerVideoMemory,
    608                                   CompareVideoMemory, this, NULL);
    609   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
    610                                   kTaskManagerFPS,
    611                                   CompareFPS, this, NULL);
    612   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
    613                                   kTaskManagerSqliteMemoryUsed,
    614                                   CompareSqliteMemoryUsed, this, NULL);
    615   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
    616                                   kTaskManagerGoatsTeleported,
    617                                   CompareGoatsTeleported, this, NULL);
    618   treeview_ = gtk_tree_view_new_with_model(process_list_sort_);
    619 
    620   // Insert all the columns.
    621   TreeViewInsertTaskColumn(treeview_, IDS_TASK_MANAGER_TASK_COLUMN);
    622   TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_PROFILE_NAME_COLUMN);
    623   TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_SHARED_MEM_COLUMN);
    624   TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN);
    625   TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_CPU_COLUMN);
    626   TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_NET_COLUMN);
    627   TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_PROCESS_ID_COLUMN);
    628   TreeViewInsertColumn(treeview_,
    629                        IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN);
    630   TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN);
    631   TreeViewInsertColumn(treeview_,
    632                        IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN);
    633   TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN);
    634   TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN);
    635   TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_FPS_COLUMN);
    636   TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN);
    637   TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN);
    638 
    639   // Hide some columns by default.
    640   TreeViewColumnSetVisible(treeview_, kTaskManagerProfileName, false);
    641   TreeViewColumnSetVisible(treeview_, kTaskManagerSharedMem, false);
    642   TreeViewColumnSetVisible(treeview_, kTaskManagerJavaScriptMemory, false);
    643   TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreImageCache, false);
    644   TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreScriptsCache, false);
    645   TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreCssCache, false);
    646   TreeViewColumnSetVisible(treeview_, kTaskManagerVideoMemory, false);
    647   TreeViewColumnSetVisible(treeview_, kTaskManagerSqliteMemoryUsed, false);
    648   TreeViewColumnSetVisible(treeview_, kTaskManagerGoatsTeleported, false);
    649 
    650   g_object_unref(process_list_);
    651   g_object_unref(process_list_sort_);
    652 }
    653 
    654 std::string TaskManagerGtk::GetModelText(int row, int col_id) {
    655   return UTF16ToUTF8(model_->GetResourceById(row, col_id));
    656 }
    657 
    658 GdkPixbuf* TaskManagerGtk::GetModelIcon(int row) {
    659   SkBitmap icon = *model_->GetResourceIcon(row).bitmap();
    660   if (icon.pixelRef() ==
    661       ui::ResourceBundle::GetSharedInstance().GetImageNamed(
    662           IDR_DEFAULT_FAVICON).AsBitmap().pixelRef()) {
    663     return static_cast<GdkPixbuf*>(g_object_ref(
    664         GtkThemeService::GetDefaultFavicon(true).ToGdkPixbuf()));
    665   }
    666 
    667   return gfx::GdkPixbufFromSkBitmap(icon);
    668 }
    669 
    670 void TaskManagerGtk::SetRowDataFromModel(int row, GtkTreeIter* iter) {
    671   GdkPixbuf* icon = GetModelIcon(row);
    672   std::string task = GetModelText(row, IDS_TASK_MANAGER_TASK_COLUMN);
    673   std::string profile_name =
    674       GetModelText(row, IDS_TASK_MANAGER_PROFILE_NAME_COLUMN);
    675   gchar* task_markup = g_markup_escape_text(task.c_str(), task.length());
    676   std::string shared_mem =
    677       GetModelText(row, IDS_TASK_MANAGER_SHARED_MEM_COLUMN);
    678   std::string priv_mem = GetModelText(row, IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN);
    679   std::string cpu = GetModelText(row, IDS_TASK_MANAGER_CPU_COLUMN);
    680   std::string net = GetModelText(row, IDS_TASK_MANAGER_NET_COLUMN);
    681   std::string procid = GetModelText(row, IDS_TASK_MANAGER_PROCESS_ID_COLUMN);
    682 
    683   // Querying the renderer metrics is slow as it has to do IPC, so only do it
    684   // when the columns are visible.
    685   std::string javascript_memory;
    686   if (TreeViewColumnIsVisible(treeview_, kTaskManagerJavaScriptMemory)) {
    687     javascript_memory =
    688         GetModelText(row, IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN);
    689   }
    690   std::string wk_img_cache;
    691   if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreImageCache)) {
    692     wk_img_cache =
    693         GetModelText(row, IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN);
    694   }
    695   std::string wk_scripts_cache;
    696   if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreScriptsCache)) {
    697     wk_scripts_cache =
    698         GetModelText(row, IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN);
    699   }
    700   std::string wk_css_cache;
    701   if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreCssCache)) {
    702     wk_css_cache =
    703         GetModelText(row, IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN);
    704   }
    705   std::string video_memory;
    706   if (TreeViewColumnIsVisible(treeview_, kTaskManagerVideoMemory))
    707     video_memory = GetModelText(row, IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN);
    708   std::string fps;
    709   if (TreeViewColumnIsVisible(treeview_, kTaskManagerFPS))
    710     fps = GetModelText(row, IDS_TASK_MANAGER_FPS_COLUMN);
    711   std::string sqlite_memory;
    712   if (TreeViewColumnIsVisible(treeview_, kTaskManagerSqliteMemoryUsed)) {
    713     sqlite_memory =
    714         GetModelText(row, IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN);
    715   }
    716 
    717   std::string goats =
    718       GetModelText(row, IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN);
    719 
    720   gtk_list_store_set(process_list_, iter,
    721                      kTaskManagerIcon, icon,
    722                      kTaskManagerTask, task_markup,
    723                      kTaskManagerProfileName, profile_name.c_str(),
    724                      kTaskManagerSharedMem, shared_mem.c_str(),
    725                      kTaskManagerPrivateMem, priv_mem.c_str(),
    726                      kTaskManagerCPU, cpu.c_str(),
    727                      kTaskManagerNetwork, net.c_str(),
    728                      kTaskManagerProcessID, procid.c_str(),
    729                      kTaskManagerJavaScriptMemory, javascript_memory.c_str(),
    730                      kTaskManagerWebCoreImageCache, wk_img_cache.c_str(),
    731                      kTaskManagerWebCoreScriptsCache, wk_scripts_cache.c_str(),
    732                      kTaskManagerWebCoreCssCache, wk_css_cache.c_str(),
    733                      kTaskManagerVideoMemory, video_memory.c_str(),
    734                      kTaskManagerFPS, fps.c_str(),
    735                      kTaskManagerSqliteMemoryUsed, sqlite_memory.c_str(),
    736                      kTaskManagerGoatsTeleported, goats.c_str(),
    737                      -1);
    738   g_object_unref(icon);
    739   g_free(task_markup);
    740 }
    741 
    742 void TaskManagerGtk::KillSelectedProcesses() {
    743   GtkTreeSelection* selection = gtk_tree_view_get_selection(
    744       GTK_TREE_VIEW(treeview_));
    745 
    746   GtkTreeModel* model;
    747   GList* paths = gtk_tree_selection_get_selected_rows(selection, &model);
    748   for (GList* item = paths; item; item = item->next) {
    749     GtkTreePath* path = gtk_tree_model_sort_convert_path_to_child_path(
    750         GTK_TREE_MODEL_SORT(process_list_sort_),
    751         reinterpret_cast<GtkTreePath*>(item->data));
    752     int row = gtk_tree::GetRowNumForPath(path);
    753     gtk_tree_path_free(path);
    754     task_manager_->KillProcess(row);
    755   }
    756   g_list_foreach(paths, reinterpret_cast<GFunc>(gtk_tree_path_free), NULL);
    757   g_list_free(paths);
    758 }
    759 
    760 void TaskManagerGtk::ShowContextMenu(const gfx::Point& point,
    761                                      guint32 event_time) {
    762   if (!menu_controller_.get())
    763     menu_controller_.reset(new ContextMenuController(this));
    764 
    765   menu_controller_->RunMenu(point, event_time);
    766 }
    767 
    768 void TaskManagerGtk::OnLinkActivated() {
    769   task_manager_->OpenAboutMemory(chrome::HOST_DESKTOP_TYPE_NATIVE);
    770 }
    771 
    772 gint TaskManagerGtk::CompareImpl(GtkTreeModel* model, GtkTreeIter* a,
    773                                  GtkTreeIter* b, int id) {
    774   int row1 = gtk_tree::GetRowNumForIter(model, b);
    775   int row2 = gtk_tree::GetRowNumForIter(model, a);
    776 
    777   // Otherwise, make sure grouped resources are shown together.
    778   TaskManagerModel::GroupRange group_range1 =
    779       model_->GetGroupRangeForResource(row1);
    780   TaskManagerModel::GroupRange group_range2 =
    781       model_->GetGroupRangeForResource(row2);
    782 
    783   if (group_range1 == group_range2) {
    784     // Sort within groups.
    785     // We want the first-in-group row at the top, whether we are sorting up or
    786     // down.
    787     GtkSortType sort_type;
    788     gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(process_list_sort_),
    789                                          NULL, &sort_type);
    790     if (row1 == group_range1.first)
    791       return sort_type == GTK_SORT_ASCENDING ? -1 : 1;
    792     if (row2 == group_range2.first)
    793       return sort_type == GTK_SORT_ASCENDING ? 1 : -1;
    794 
    795     return model_->CompareValues(row1, row2, id);
    796   } else {
    797     // Sort between groups.
    798     // Compare by the first-in-group rows so that the groups will stay together.
    799     return model_->CompareValues(group_range1.first, group_range2.first, id);
    800   }
    801 }
    802 
    803 void TaskManagerGtk::OnDestroy(GtkWidget* dialog) {
    804   instance_ = NULL;
    805   delete this;
    806 }
    807 
    808 void TaskManagerGtk::OnResponse(GtkWidget* dialog, int response_id) {
    809   if (response_id == GTK_RESPONSE_DELETE_EVENT) {
    810     // Store the dialog's size so we can restore it the next time it's opened.
    811     if (g_browser_process->local_state()) {
    812       gfx::Rect dialog_bounds = gtk_util::GetDialogBounds(GTK_WIDGET(dialog));
    813 
    814       DictionaryPrefUpdate update(g_browser_process->local_state(),
    815                                   prefs::kTaskManagerWindowPlacement);
    816       DictionaryValue* placement_pref = update.Get();
    817       // Note that we store left/top for consistency with Windows, but that we
    818       // *don't* restore them.
    819       placement_pref->SetInteger("left", dialog_bounds.x());
    820       placement_pref->SetInteger("top", dialog_bounds.y());
    821       placement_pref->SetInteger("right", dialog_bounds.right());
    822       placement_pref->SetInteger("bottom", dialog_bounds.bottom());
    823       placement_pref->SetBoolean("maximized", false);
    824     }
    825 
    826     instance_ = NULL;
    827     delete this;
    828   } else if (response_id == kTaskManagerResponseKill) {
    829     KillSelectedProcesses();
    830   } else if (response_id == kTaskManagerAboutMemoryLink) {
    831     OnLinkActivated();
    832   } else if (response_id == kTaskManagerPurgeMemory) {
    833     MemoryPurger::PurgeAll();
    834   }
    835 }
    836 
    837 void TaskManagerGtk::OnTreeViewRealize(GtkTreeView* treeview) {
    838   // Five columns show by default: the task column, the memory column, the CPU
    839   // column, the network column, and the FPS column. Initially we set the task
    840   // tolumn to take all the extra space, with the other columns being sized to
    841   // fit the column names. Here we turn off the expand property of the first
    842   // column (to make the table behave sanely when the user resizes it), and set
    843   // the effective sizes of all five default columns to the automatically chosen
    844   // sizes before any rows are added. This causes them to stay at those sizes
    845   // even if the data would overflow, preventing a horizontal scroll bar from
    846   // appearing due to the row data.
    847   static const TaskManagerColumn dfl_columns[] = {kTaskManagerPrivateMem,
    848                                                   kTaskManagerCPU,
    849                                                   kTaskManagerNetwork,
    850                                                   kTaskManagerFPS};
    851   GtkTreeViewColumn* column = NULL;
    852   gint width;
    853   for (size_t i = 0; i < arraysize(dfl_columns); ++i) {
    854     column = gtk_tree_view_get_column(treeview,
    855         TreeViewColumnIndexFromID(dfl_columns[i]));
    856     width = gtk_tree_view_column_get_width(column);
    857     TreeViewColumnSetWidth(column, width);
    858   }
    859   // Do the task column separately since it's a little different.
    860   column = gtk_tree_view_get_column(treeview,
    861       TreeViewColumnIndexFromID(kTaskManagerTask));
    862   width = gtk_tree_view_column_get_width(column);
    863   // Turn expanding back off to make resizing columns behave sanely.
    864   gtk_tree_view_column_set_expand(column, FALSE);
    865   TreeViewColumnSetWidth(column, width);
    866 }
    867 
    868 void TaskManagerGtk::OnSelectionChanged(GtkTreeSelection* selection) {
    869   if (ignore_selection_changed_)
    870     return;
    871   base::AutoReset<bool> autoreset(&ignore_selection_changed_, true);
    872 
    873   // The set of groups that should be selected.
    874   std::set<TaskManagerModel::GroupRange> ranges;
    875   bool selection_contains_browser_process = false;
    876 
    877   GtkTreeModel* model;
    878   GList* paths = gtk_tree_selection_get_selected_rows(selection, &model);
    879   for (GList* item = paths; item; item = item->next) {
    880     GtkTreePath* path = gtk_tree_model_sort_convert_path_to_child_path(
    881         GTK_TREE_MODEL_SORT(process_list_sort_),
    882         reinterpret_cast<GtkTreePath*>(item->data));
    883     int row = gtk_tree::GetRowNumForPath(path);
    884     gtk_tree_path_free(path);
    885     if (task_manager_->IsBrowserProcess(row))
    886       selection_contains_browser_process = true;
    887     ranges.insert(model_->GetGroupRangeForResource(row));
    888   }
    889   g_list_foreach(paths, reinterpret_cast<GFunc>(gtk_tree_path_free), NULL);
    890   g_list_free(paths);
    891 
    892   for (std::set<TaskManagerModel::GroupRange>::iterator iter = ranges.begin();
    893        iter != ranges.end(); ++iter) {
    894     for (int i = 0; i < iter->second; ++i) {
    895       GtkTreePath* child_path = gtk_tree_path_new_from_indices(iter->first + i,
    896                                                                -1);
    897       GtkTreePath* sort_path = gtk_tree_model_sort_convert_child_path_to_path(
    898         GTK_TREE_MODEL_SORT(process_list_sort_), child_path);
    899       gtk_tree_selection_select_path(selection, sort_path);
    900       gtk_tree_path_free(child_path);
    901       gtk_tree_path_free(sort_path);
    902     }
    903   }
    904 
    905   bool sensitive = (paths != NULL) && !selection_contains_browser_process;
    906   gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_),
    907                                     kTaskManagerResponseKill, sensitive);
    908 }
    909 
    910 void TaskManagerGtk::OnRowActivated(GtkWidget* widget,
    911                                     GtkTreePath* path,
    912                                     GtkTreeViewColumn* column) {
    913   GtkTreePath* child_path = gtk_tree_model_sort_convert_path_to_child_path(
    914       GTK_TREE_MODEL_SORT(process_list_sort_), path);
    915   int row = gtk_tree::GetRowNumForPath(child_path);
    916   gtk_tree_path_free(child_path);
    917   task_manager_->ActivateProcess(row);
    918 }
    919 
    920 gboolean TaskManagerGtk::OnButtonEvent(GtkWidget* widget,
    921                                        GdkEventButton* event) {
    922   // GTK does menu on mouse-up while views does menu on mouse-down,
    923   // so this function can be called from either signal.
    924   if (event->button == 3) {
    925     ShowContextMenu(gfx::Point(event->x_root, event->y_root),
    926                     event->time);
    927     return TRUE;
    928   }
    929 
    930   return FALSE;
    931 }
    932 
    933 gboolean TaskManagerGtk::OnGtkAccelerator(GtkAccelGroup* accel_group,
    934                                           GObject* acceleratable,
    935                                           guint keyval,
    936                                           GdkModifierType modifier) {
    937   if (keyval == GDK_w && modifier == GDK_CONTROL_MASK) {
    938     // The GTK_RESPONSE_DELETE_EVENT response must be sent before the widget
    939     // is destroyed.  The deleted object will receive gtk signals otherwise.
    940     gtk_dialog_response(GTK_DIALOG(dialog_), GTK_RESPONSE_DELETE_EVENT);
    941   }
    942 
    943   return TRUE;
    944 }
    945 
    946 namespace chrome {
    947 
    948 // Declared in browser_dialogs.h.
    949 void ShowTaskManager(Browser* browser) {
    950   TaskManagerGtk::Show();
    951 }
    952 
    953 }  // namespace chrome
    954