Home | History | Annotate | Download | only in cocoa
      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/cocoa/task_manager_mac.h"
      6 
      7 #include <algorithm>
      8 #include <vector>
      9 
     10 #include "base/mac/bundle_locations.h"
     11 #include "base/mac/mac_util.h"
     12 #include "base/prefs/pref_service.h"
     13 #include "base/strings/sys_string_conversions.h"
     14 #include "chrome/browser/browser_process.h"
     15 #import "chrome/browser/ui/cocoa/window_size_autosaver.h"
     16 #include "chrome/browser/ui/host_desktop.h"
     17 #include "chrome/common/pref_names.h"
     18 #include "chrome/grit/generated_resources.h"
     19 #include "third_party/skia/include/core/SkBitmap.h"
     20 #include "ui/base/l10n/l10n_util_mac.h"
     21 #include "ui/gfx/image/image_skia.h"
     22 
     23 namespace {
     24 
     25 // Width of "a" and most other letters/digits in "small" table views.
     26 const int kCharWidth = 6;
     27 
     28 // Some of the strings below have spaces at the end or are missing letters, to
     29 // make the columns look nicer, and to take potentially longer localized strings
     30 // into account.
     31 const struct ColumnWidth {
     32   int columnId;
     33   int minWidth;
     34   int maxWidth;  // If this is -1, 1.5*minColumWidth is used as max width.
     35 } columnWidths[] = {
     36   // Note that arraysize includes the trailing \0. That's intended.
     37   { IDS_TASK_MANAGER_TASK_COLUMN, 120, 600 },
     38   { IDS_TASK_MANAGER_PROFILE_NAME_COLUMN, 60, 200 },
     39   { IDS_TASK_MANAGER_PHYSICAL_MEM_COLUMN,
     40       arraysize("800 MiB") * kCharWidth, -1 },
     41   { IDS_TASK_MANAGER_SHARED_MEM_COLUMN,
     42       arraysize("800 MiB") * kCharWidth, -1 },
     43   { IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN,
     44       arraysize("800 MiB") * kCharWidth, -1 },
     45   { IDS_TASK_MANAGER_CPU_COLUMN,
     46       arraysize("99.9") * kCharWidth, -1 },
     47   { IDS_TASK_MANAGER_NET_COLUMN,
     48       arraysize("150 kiB/s") * kCharWidth, -1 },
     49   { IDS_TASK_MANAGER_PROCESS_ID_COLUMN,
     50       arraysize("73099  ") * kCharWidth, -1 },
     51   { IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN,
     52       arraysize("2000.0K (2000.0 live)") * kCharWidth, -1 },
     53   { IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN,
     54       arraysize("2000.0K (2000.0 live)") * kCharWidth, -1 },
     55   { IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN,
     56       arraysize("2000.0K (2000.0 live)") * kCharWidth, -1 },
     57   { IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN,
     58       arraysize("2000.0K") * kCharWidth, -1 },
     59   { IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN,
     60       arraysize("800 kB") * kCharWidth, -1 },
     61   { IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN,
     62       arraysize("2000.0K (2000.0 live)") * kCharWidth, -1 },
     63   { IDS_TASK_MANAGER_NACL_DEBUG_STUB_PORT_COLUMN,
     64       arraysize("32767") * kCharWidth, -1 },
     65   { IDS_TASK_MANAGER_IDLE_WAKEUPS_COLUMN,
     66       arraysize("idlewakeups") * kCharWidth, -1 },
     67 };
     68 
     69 class SortHelper {
     70  public:
     71   SortHelper(TaskManagerModel* model, NSSortDescriptor* column)
     72       : sort_column_([[column key] intValue]),
     73         ascending_([column ascending]),
     74         model_(model) {}
     75 
     76   bool operator()(int a, int b) {
     77     TaskManagerModel::GroupRange group_range1 =
     78         model_->GetGroupRangeForResource(a);
     79     TaskManagerModel::GroupRange group_range2 =
     80         model_->GetGroupRangeForResource(b);
     81     if (group_range1 == group_range2) {
     82       // The two rows are in the same group, sort so that items in the same
     83       // group always appear in the same order. |ascending_| is intentionally
     84       // ignored.
     85       return a < b;
     86     }
     87     // Sort by the first entry of each of the groups.
     88     int cmp_result = model_->CompareValues(
     89         group_range1.first, group_range2.first, sort_column_);
     90     if (!ascending_)
     91       cmp_result = -cmp_result;
     92     return cmp_result < 0;
     93   }
     94  private:
     95   int sort_column_;
     96   bool ascending_;
     97   TaskManagerModel* model_;  // weak;
     98 };
     99 
    100 }  // namespace
    101 
    102 @interface TaskManagerWindowController (Private)
    103 - (NSTableColumn*)addColumnWithId:(int)columnId visible:(BOOL)isVisible;
    104 - (void)setUpTableColumns;
    105 - (void)setUpTableHeaderContextMenu;
    106 - (void)toggleColumn:(id)sender;
    107 - (void)adjustSelectionAndEndProcessButton;
    108 - (void)deselectRows;
    109 @end
    110 
    111 ////////////////////////////////////////////////////////////////////////////////
    112 // TaskManagerWindowController implementation:
    113 
    114 @implementation TaskManagerWindowController
    115 
    116 - (id)initWithTaskManagerObserver:(TaskManagerMac*)taskManagerObserver {
    117   NSString* nibpath = [base::mac::FrameworkBundle()
    118                         pathForResource:@"TaskManager"
    119                                  ofType:@"nib"];
    120   if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
    121     taskManagerObserver_ = taskManagerObserver;
    122     taskManager_ = taskManagerObserver_->task_manager();
    123     model_ = taskManager_->model();
    124 
    125     if (g_browser_process && g_browser_process->local_state()) {
    126       size_saver_.reset([[WindowSizeAutosaver alloc]
    127           initWithWindow:[self window]
    128              prefService:g_browser_process->local_state()
    129                     path:prefs::kTaskManagerWindowPlacement]);
    130     }
    131     [self showWindow:self];
    132   }
    133   return self;
    134 }
    135 
    136 - (void)sortShuffleArray {
    137   viewToModelMap_.resize(model_->ResourceCount());
    138   for (size_t i = 0; i < viewToModelMap_.size(); ++i)
    139     viewToModelMap_[i] = i;
    140 
    141   std::sort(viewToModelMap_.begin(), viewToModelMap_.end(),
    142             SortHelper(model_, currentSortDescriptor_.get()));
    143 
    144   modelToViewMap_.resize(viewToModelMap_.size());
    145   for (size_t i = 0; i < viewToModelMap_.size(); ++i)
    146     modelToViewMap_[viewToModelMap_[i]] = i;
    147 }
    148 
    149 - (void)reloadData {
    150   // Store old view indices, and the model indices they map to.
    151   NSIndexSet* viewSelection = [tableView_ selectedRowIndexes];
    152   std::vector<int> modelSelection;
    153   for (NSUInteger i = [viewSelection lastIndex];
    154        i != NSNotFound;
    155        i = [viewSelection indexLessThanIndex:i]) {
    156     modelSelection.push_back(viewToModelMap_[i]);
    157   }
    158 
    159   // Sort.
    160   [self sortShuffleArray];
    161 
    162   // Use the model indices to get the new view indices of the selection, and
    163   // set selection to that. This assumes that no rows were added or removed
    164   // (in that case, the selection is cleared before -reloadData is called).
    165   if (!modelSelection.empty())
    166     DCHECK_EQ([tableView_ numberOfRows], model_->ResourceCount());
    167   NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet];
    168   for (size_t i = 0; i < modelSelection.size(); ++i)
    169     [indexSet addIndex:modelToViewMap_[modelSelection[i]]];
    170   [tableView_ selectRowIndexes:indexSet byExtendingSelection:NO];
    171 
    172   [tableView_ reloadData];
    173   [self adjustSelectionAndEndProcessButton];
    174 }
    175 
    176 - (IBAction)statsLinkClicked:(id)sender {
    177   TaskManager::GetInstance()->OpenAboutMemory(chrome::HOST_DESKTOP_TYPE_NATIVE);
    178 }
    179 
    180 - (IBAction)killSelectedProcesses:(id)sender {
    181   NSIndexSet* selection = [tableView_ selectedRowIndexes];
    182   for (NSUInteger i = [selection lastIndex];
    183        i != NSNotFound;
    184        i = [selection indexLessThanIndex:i]) {
    185     taskManager_->KillProcess(viewToModelMap_[i]);
    186   }
    187 }
    188 
    189 - (void)selectDoubleClickedTab:(id)sender {
    190   NSInteger row = [tableView_ clickedRow];
    191   if (row < 0)
    192     return;  // Happens e.g. if the table header is double-clicked.
    193   taskManager_->ActivateProcess(viewToModelMap_[row]);
    194 }
    195 
    196 - (NSTableView*)tableView {
    197   return tableView_;
    198 }
    199 
    200 - (void)awakeFromNib {
    201   [self setUpTableColumns];
    202   [self setUpTableHeaderContextMenu];
    203   [self adjustSelectionAndEndProcessButton];
    204 
    205   [tableView_ setDoubleAction:@selector(selectDoubleClickedTab:)];
    206   [tableView_ setIntercellSpacing:NSMakeSize(0.0, 0.0)];
    207   [tableView_ sizeToFit];
    208 }
    209 
    210 - (void)dealloc {
    211   [tableView_ setDelegate:nil];
    212   [tableView_ setDataSource:nil];
    213   [super dealloc];
    214 }
    215 
    216 // Adds a column which has the given string id as title. |isVisible| specifies
    217 // if the column is initially visible.
    218 - (NSTableColumn*)addColumnWithId:(int)columnId visible:(BOOL)isVisible {
    219   base::scoped_nsobject<NSTableColumn> column([[NSTableColumn alloc]
    220       initWithIdentifier:[NSString stringWithFormat:@"%d", columnId]]);
    221 
    222   NSTextAlignment textAlignment =
    223       (columnId == IDS_TASK_MANAGER_TASK_COLUMN ||
    224        columnId == IDS_TASK_MANAGER_PROFILE_NAME_COLUMN) ?
    225           NSLeftTextAlignment : NSRightTextAlignment;
    226 
    227   [[column.get() headerCell]
    228       setStringValue:l10n_util::GetNSStringWithFixup(columnId)];
    229   [[column.get() headerCell] setAlignment:textAlignment];
    230   [[column.get() dataCell] setAlignment:textAlignment];
    231 
    232   NSFont* font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
    233   [[column.get() dataCell] setFont:font];
    234 
    235   [column.get() setHidden:!isVisible];
    236   [column.get() setEditable:NO];
    237 
    238   // The page column should by default be sorted ascending.
    239   BOOL ascending = columnId == IDS_TASK_MANAGER_TASK_COLUMN;
    240 
    241   base::scoped_nsobject<NSSortDescriptor> sortDescriptor(
    242       [[NSSortDescriptor alloc]
    243           initWithKey:[NSString stringWithFormat:@"%d", columnId]
    244             ascending:ascending]);
    245   [column.get() setSortDescriptorPrototype:sortDescriptor.get()];
    246 
    247   // Default values, only used in release builds if nobody notices the DCHECK
    248   // during development when adding new columns.
    249   int minWidth = 200, maxWidth = 400;
    250 
    251   size_t i;
    252   for (i = 0; i < arraysize(columnWidths); ++i) {
    253     if (columnWidths[i].columnId == columnId) {
    254       minWidth = columnWidths[i].minWidth;
    255       maxWidth = columnWidths[i].maxWidth;
    256       if (maxWidth < 0)
    257         maxWidth = 3 * minWidth / 2;  // *1.5 for ints.
    258       break;
    259     }
    260   }
    261   DCHECK(i < arraysize(columnWidths)) << "Could not find " << columnId;
    262   [column.get() setMinWidth:minWidth];
    263   [column.get() setMaxWidth:maxWidth];
    264   [column.get() setResizingMask:NSTableColumnAutoresizingMask |
    265                                 NSTableColumnUserResizingMask];
    266 
    267   [tableView_ addTableColumn:column.get()];
    268   return column.get();  // Now retained by |tableView_|.
    269 }
    270 
    271 // Adds all the task manager's columns to the table.
    272 - (void)setUpTableColumns {
    273   for (NSTableColumn* column in [tableView_ tableColumns])
    274     [tableView_ removeTableColumn:column];
    275   NSTableColumn* nameColumn = [self addColumnWithId:IDS_TASK_MANAGER_TASK_COLUMN
    276                                             visible:YES];
    277   // |nameColumn| displays an icon for every row -- this is done by an
    278   // NSButtonCell.
    279   base::scoped_nsobject<NSButtonCell> nameCell(
    280       [[NSButtonCell alloc] initTextCell:@""]);
    281   [nameCell.get() setImagePosition:NSImageLeft];
    282   [nameCell.get() setButtonType:NSSwitchButton];
    283   [nameCell.get() setAlignment:[[nameColumn dataCell] alignment]];
    284   [nameCell.get() setFont:[[nameColumn dataCell] font]];
    285   [nameColumn setDataCell:nameCell.get()];
    286 
    287   // Initially, sort on the tab name.
    288   [tableView_ setSortDescriptors:
    289       [NSArray arrayWithObject:[nameColumn sortDescriptorPrototype]]];
    290   [self addColumnWithId:IDS_TASK_MANAGER_PROFILE_NAME_COLUMN visible:NO];
    291   [self addColumnWithId:IDS_TASK_MANAGER_PHYSICAL_MEM_COLUMN visible:YES];
    292   [self addColumnWithId:IDS_TASK_MANAGER_SHARED_MEM_COLUMN visible:NO];
    293   [self addColumnWithId:IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN visible:NO];
    294   [self addColumnWithId:IDS_TASK_MANAGER_CPU_COLUMN visible:YES];
    295   [self addColumnWithId:IDS_TASK_MANAGER_NET_COLUMN visible:YES];
    296   [self addColumnWithId:IDS_TASK_MANAGER_PROCESS_ID_COLUMN visible:YES];
    297   [self addColumnWithId:IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN
    298                 visible:NO];
    299   [self addColumnWithId:IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN
    300                 visible:NO];
    301   [self addColumnWithId:IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN visible:NO];
    302   [self addColumnWithId:IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN visible:NO];
    303   [self addColumnWithId:IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN visible:NO];
    304   [self addColumnWithId:IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN
    305                 visible:NO];
    306   [self addColumnWithId:IDS_TASK_MANAGER_NACL_DEBUG_STUB_PORT_COLUMN
    307                 visible:NO];
    308   [self addColumnWithId:IDS_TASK_MANAGER_IDLE_WAKEUPS_COLUMN
    309                 visible:NO];
    310 }
    311 
    312 // Creates a context menu for the table header that allows the user to toggle
    313 // which columns should be shown and which should be hidden (like e.g.
    314 // Task Manager.app's table header context menu).
    315 - (void)setUpTableHeaderContextMenu {
    316   base::scoped_nsobject<NSMenu> contextMenu(
    317       [[NSMenu alloc] initWithTitle:@"Task Manager context menu"]);
    318   for (NSTableColumn* column in [tableView_ tableColumns]) {
    319     NSMenuItem* item = [contextMenu.get()
    320         addItemWithTitle:[[column headerCell] stringValue]
    321                   action:@selector(toggleColumn:)
    322            keyEquivalent:@""];
    323     [item setTarget:self];
    324     [item setRepresentedObject:column];
    325     [item setState:[column isHidden] ? NSOffState : NSOnState];
    326   }
    327   [[tableView_ headerView] setMenu:contextMenu.get()];
    328 }
    329 
    330 // Callback for the table header context menu. Toggles visibility of the table
    331 // column associated with the clicked menu item.
    332 - (void)toggleColumn:(id)item {
    333   DCHECK([item isKindOfClass:[NSMenuItem class]]);
    334   if (![item isKindOfClass:[NSMenuItem class]])
    335     return;
    336 
    337   NSTableColumn* column = [item representedObject];
    338   DCHECK(column);
    339   NSInteger oldState = [item state];
    340   NSInteger newState = oldState == NSOnState ? NSOffState : NSOnState;
    341   [column setHidden:newState == NSOffState];
    342   [item setState:newState];
    343   [tableView_ sizeToFit];
    344   [tableView_ setNeedsDisplay];
    345 }
    346 
    347 // This function appropriately sets the enabled states on the table's editing
    348 // buttons.
    349 - (void)adjustSelectionAndEndProcessButton {
    350   bool selectionContainsBrowserProcess = false;
    351 
    352   // If a row is selected, make sure that all rows belonging to the same process
    353   // are selected as well. Also, check if the selection contains the browser
    354   // process.
    355   NSIndexSet* selection = [tableView_ selectedRowIndexes];
    356   for (NSUInteger i = [selection lastIndex];
    357        i != NSNotFound;
    358        i = [selection indexLessThanIndex:i]) {
    359     int modelIndex = viewToModelMap_[i];
    360     if (taskManager_->IsBrowserProcess(modelIndex))
    361       selectionContainsBrowserProcess = true;
    362 
    363     TaskManagerModel::GroupRange rangePair =
    364         model_->GetGroupRangeForResource(modelIndex);
    365     NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet];
    366     for (int j = 0; j < rangePair.second; ++j)
    367       [indexSet addIndex:modelToViewMap_[rangePair.first + j]];
    368     [tableView_ selectRowIndexes:indexSet byExtendingSelection:YES];
    369   }
    370 
    371   bool enabled = [selection count] > 0 && !selectionContainsBrowserProcess;
    372   [endProcessButton_ setEnabled:enabled];
    373 }
    374 
    375 - (void)deselectRows {
    376   [tableView_ deselectAll:self];
    377 }
    378 
    379 // Table view delegate methods.
    380 
    381 // The selection is being changed by mouse (drag/click).
    382 - (void)tableViewSelectionIsChanging:(NSNotification*)aNotification {
    383   [self adjustSelectionAndEndProcessButton];
    384 }
    385 
    386 // The selection is being changed by keyboard (arrows).
    387 - (void)tableViewSelectionDidChange:(NSNotification*)aNotification {
    388   [self adjustSelectionAndEndProcessButton];
    389 }
    390 
    391 - (void)windowWillClose:(NSNotification*)notification {
    392   if (taskManagerObserver_) {
    393     taskManagerObserver_->WindowWasClosed();
    394     taskManagerObserver_ = nil;
    395   }
    396   [self autorelease];
    397 }
    398 
    399 @end
    400 
    401 @implementation TaskManagerWindowController (NSTableDataSource)
    402 
    403 - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView {
    404   DCHECK(tableView == tableView_ || tableView_ == nil);
    405   return model_->ResourceCount();
    406 }
    407 
    408 - (NSString*)modelTextForRow:(int)row column:(int)columnId {
    409   DCHECK_LT(static_cast<size_t>(row), viewToModelMap_.size());
    410   return base::SysUTF16ToNSString(
    411       model_->GetResourceById(viewToModelMap_[row], columnId));
    412 }
    413 
    414 - (id)tableView:(NSTableView*)tableView
    415     objectValueForTableColumn:(NSTableColumn*)tableColumn
    416                           row:(NSInteger)rowIndex {
    417   // NSButtonCells expect an on/off state as objectValue. Their title is set
    418   // in |tableView:dataCellForTableColumn:row:| below.
    419   if ([[tableColumn identifier] intValue] == IDS_TASK_MANAGER_TASK_COLUMN) {
    420     return [NSNumber numberWithInt:NSOffState];
    421   }
    422 
    423   return [self modelTextForRow:rowIndex
    424                         column:[[tableColumn identifier] intValue]];
    425 }
    426 
    427 - (NSCell*)tableView:(NSTableView*)tableView
    428     dataCellForTableColumn:(NSTableColumn*)tableColumn
    429                        row:(NSInteger)rowIndex {
    430   NSCell* cell = [tableColumn dataCellForRow:rowIndex];
    431 
    432   // Set the favicon and title for the task in the name column.
    433   if ([[tableColumn identifier] intValue] == IDS_TASK_MANAGER_TASK_COLUMN) {
    434     DCHECK([cell isKindOfClass:[NSButtonCell class]]);
    435     NSButtonCell* buttonCell = static_cast<NSButtonCell*>(cell);
    436     NSString* title = [self modelTextForRow:rowIndex
    437                                     column:[[tableColumn identifier] intValue]];
    438     [buttonCell setTitle:title];
    439     [buttonCell setImage:
    440         taskManagerObserver_->GetImageForRow(viewToModelMap_[rowIndex])];
    441     [buttonCell setRefusesFirstResponder:YES];  // Don't push in like a button.
    442     [buttonCell setHighlightsBy:NSNoCellMask];
    443   }
    444 
    445   return cell;
    446 }
    447 
    448 - (void)           tableView:(NSTableView*)tableView
    449     sortDescriptorsDidChange:(NSArray*)oldDescriptors {
    450   NSArray* newDescriptors = [tableView sortDescriptors];
    451   if ([newDescriptors count] < 1)
    452     return;
    453 
    454   currentSortDescriptor_.reset([[newDescriptors objectAtIndex:0] retain]);
    455   [self reloadData];  // Sorts.
    456 }
    457 
    458 @end
    459 
    460 ////////////////////////////////////////////////////////////////////////////////
    461 // TaskManagerMac implementation:
    462 
    463 TaskManagerMac::TaskManagerMac(TaskManager* task_manager)
    464   : task_manager_(task_manager),
    465     model_(task_manager->model()),
    466     icon_cache_(this) {
    467   window_controller_ =
    468       [[TaskManagerWindowController alloc] initWithTaskManagerObserver:this];
    469   model_->AddObserver(this);
    470 }
    471 
    472 // static
    473 TaskManagerMac* TaskManagerMac::instance_ = NULL;
    474 
    475 TaskManagerMac::~TaskManagerMac() {
    476   if (this == instance_) {
    477     // Do not do this when running in unit tests: |StartUpdating()| never got
    478     // called in that case.
    479     task_manager_->OnWindowClosed();
    480   }
    481   model_->RemoveObserver(this);
    482 }
    483 
    484 ////////////////////////////////////////////////////////////////////////////////
    485 // TaskManagerMac, TaskManagerModelObserver implementation:
    486 
    487 void TaskManagerMac::OnModelChanged() {
    488   icon_cache_.OnModelChanged();
    489   [window_controller_ deselectRows];
    490   [window_controller_ reloadData];
    491 }
    492 
    493 void TaskManagerMac::OnItemsChanged(int start, int length) {
    494   icon_cache_.OnItemsChanged(start, length);
    495   [window_controller_ reloadData];
    496 }
    497 
    498 void TaskManagerMac::OnItemsAdded(int start, int length) {
    499   icon_cache_.OnItemsAdded(start, length);
    500   [window_controller_ deselectRows];
    501   [window_controller_ reloadData];
    502 }
    503 
    504 void TaskManagerMac::OnItemsRemoved(int start, int length) {
    505   icon_cache_.OnItemsRemoved(start, length);
    506   [window_controller_ deselectRows];
    507   [window_controller_ reloadData];
    508 }
    509 
    510 NSImage* TaskManagerMac::GetImageForRow(int row) {
    511   return icon_cache_.GetImageForRow(row);
    512 }
    513 
    514 ////////////////////////////////////////////////////////////////////////////////
    515 // TaskManagerMac, public:
    516 
    517 void TaskManagerMac::WindowWasClosed() {
    518   instance_ = NULL;
    519   delete this;
    520 }
    521 
    522 int TaskManagerMac::RowCount() const {
    523   return model_->ResourceCount();
    524 }
    525 
    526 gfx::ImageSkia TaskManagerMac::GetIcon(int r) const {
    527   return model_->GetResourceIcon(r);
    528 }
    529 
    530 // static
    531 void TaskManagerMac::Show() {
    532   if (instance_) {
    533     [[instance_->window_controller_ window]
    534       makeKeyAndOrderFront:instance_->window_controller_];
    535     return;
    536   }
    537   // Create a new instance.
    538   instance_ = new TaskManagerMac(TaskManager::GetInstance());
    539   instance_->model_->StartUpdating();
    540 }
    541 
    542 namespace chrome {
    543 
    544 // Declared in browser_dialogs.h.
    545 void ShowTaskManager(Browser* browser) {
    546   TaskManagerMac::Show();
    547 }
    548 
    549 }  // namespace chrome
    550 
    551