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 "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_FPS_COLUMN,
     58       arraysize("100") * kCharWidth, -1 },
     59   { IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN,
     60       arraysize("2000.0K") * kCharWidth, -1 },
     61   { IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN,
     62       arraysize("800 kB") * kCharWidth, -1 },
     63   { IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN,
     64       arraysize("2000.0K (2000.0 live)") * kCharWidth, -1 },
     65   { IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN,
     66       arraysize("15 ") * 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_FPS_COLUMN visible:YES];
    303   [self addColumnWithId:IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN visible:NO];
    304   [self addColumnWithId:IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN visible:NO];
    305   [self addColumnWithId:IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN
    306                 visible:NO];
    307   [self addColumnWithId:IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN visible:NO];
    308 }
    309 
    310 // Creates a context menu for the table header that allows the user to toggle
    311 // which columns should be shown and which should be hidden (like e.g.
    312 // Task Manager.app's table header context menu).
    313 - (void)setUpTableHeaderContextMenu {
    314   base::scoped_nsobject<NSMenu> contextMenu(
    315       [[NSMenu alloc] initWithTitle:@"Task Manager context menu"]);
    316   for (NSTableColumn* column in [tableView_ tableColumns]) {
    317     NSMenuItem* item = [contextMenu.get()
    318         addItemWithTitle:[[column headerCell] stringValue]
    319                   action:@selector(toggleColumn:)
    320            keyEquivalent:@""];
    321     [item setTarget:self];
    322     [item setRepresentedObject:column];
    323     [item setState:[column isHidden] ? NSOffState : NSOnState];
    324   }
    325   [[tableView_ headerView] setMenu:contextMenu.get()];
    326 }
    327 
    328 // Callback for the table header context menu. Toggles visibility of the table
    329 // column associated with the clicked menu item.
    330 - (void)toggleColumn:(id)item {
    331   DCHECK([item isKindOfClass:[NSMenuItem class]]);
    332   if (![item isKindOfClass:[NSMenuItem class]])
    333     return;
    334 
    335   NSTableColumn* column = [item representedObject];
    336   DCHECK(column);
    337   NSInteger oldState = [item state];
    338   NSInteger newState = oldState == NSOnState ? NSOffState : NSOnState;
    339   [column setHidden:newState == NSOffState];
    340   [item setState:newState];
    341   [tableView_ sizeToFit];
    342   [tableView_ setNeedsDisplay];
    343 }
    344 
    345 // This function appropriately sets the enabled states on the table's editing
    346 // buttons.
    347 - (void)adjustSelectionAndEndProcessButton {
    348   bool selectionContainsBrowserProcess = false;
    349 
    350   // If a row is selected, make sure that all rows belonging to the same process
    351   // are selected as well. Also, check if the selection contains the browser
    352   // process.
    353   NSIndexSet* selection = [tableView_ selectedRowIndexes];
    354   for (NSUInteger i = [selection lastIndex];
    355        i != NSNotFound;
    356        i = [selection indexLessThanIndex:i]) {
    357     int modelIndex = viewToModelMap_[i];
    358     if (taskManager_->IsBrowserProcess(modelIndex))
    359       selectionContainsBrowserProcess = true;
    360 
    361     TaskManagerModel::GroupRange rangePair =
    362         model_->GetGroupRangeForResource(modelIndex);
    363     NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet];
    364     for (int j = 0; j < rangePair.second; ++j)
    365       [indexSet addIndex:modelToViewMap_[rangePair.first + j]];
    366     [tableView_ selectRowIndexes:indexSet byExtendingSelection:YES];
    367   }
    368 
    369   bool enabled = [selection count] > 0 && !selectionContainsBrowserProcess;
    370   [endProcessButton_ setEnabled:enabled];
    371 }
    372 
    373 - (void)deselectRows {
    374   [tableView_ deselectAll:self];
    375 }
    376 
    377 // Table view delegate method.
    378 - (void)tableViewSelectionIsChanging:(NSNotification*)aNotification {
    379   [self adjustSelectionAndEndProcessButton];
    380 }
    381 
    382 - (void)windowWillClose:(NSNotification*)notification {
    383   if (taskManagerObserver_) {
    384     taskManagerObserver_->WindowWasClosed();
    385     taskManagerObserver_ = nil;
    386   }
    387   [self autorelease];
    388 }
    389 
    390 @end
    391 
    392 @implementation TaskManagerWindowController (NSTableDataSource)
    393 
    394 - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView {
    395   DCHECK(tableView == tableView_ || tableView_ == nil);
    396   return model_->ResourceCount();
    397 }
    398 
    399 - (NSString*)modelTextForRow:(int)row column:(int)columnId {
    400   DCHECK_LT(static_cast<size_t>(row), viewToModelMap_.size());
    401   return base::SysUTF16ToNSString(
    402       model_->GetResourceById(viewToModelMap_[row], columnId));
    403 }
    404 
    405 - (id)tableView:(NSTableView*)tableView
    406     objectValueForTableColumn:(NSTableColumn*)tableColumn
    407                           row:(NSInteger)rowIndex {
    408   // NSButtonCells expect an on/off state as objectValue. Their title is set
    409   // in |tableView:dataCellForTableColumn:row:| below.
    410   if ([[tableColumn identifier] intValue] == IDS_TASK_MANAGER_TASK_COLUMN) {
    411     return [NSNumber numberWithInt:NSOffState];
    412   }
    413 
    414   return [self modelTextForRow:rowIndex
    415                         column:[[tableColumn identifier] intValue]];
    416 }
    417 
    418 - (NSCell*)tableView:(NSTableView*)tableView
    419     dataCellForTableColumn:(NSTableColumn*)tableColumn
    420                        row:(NSInteger)rowIndex {
    421   NSCell* cell = [tableColumn dataCellForRow:rowIndex];
    422 
    423   // Set the favicon and title for the task in the name column.
    424   if ([[tableColumn identifier] intValue] == IDS_TASK_MANAGER_TASK_COLUMN) {
    425     DCHECK([cell isKindOfClass:[NSButtonCell class]]);
    426     NSButtonCell* buttonCell = static_cast<NSButtonCell*>(cell);
    427     NSString* title = [self modelTextForRow:rowIndex
    428                                     column:[[tableColumn identifier] intValue]];
    429     [buttonCell setTitle:title];
    430     [buttonCell setImage:
    431         taskManagerObserver_->GetImageForRow(viewToModelMap_[rowIndex])];
    432     [buttonCell setRefusesFirstResponder:YES];  // Don't push in like a button.
    433     [buttonCell setHighlightsBy:NSNoCellMask];
    434   }
    435 
    436   return cell;
    437 }
    438 
    439 - (void)           tableView:(NSTableView*)tableView
    440     sortDescriptorsDidChange:(NSArray*)oldDescriptors {
    441   NSArray* newDescriptors = [tableView sortDescriptors];
    442   if ([newDescriptors count] < 1)
    443     return;
    444 
    445   currentSortDescriptor_.reset([[newDescriptors objectAtIndex:0] retain]);
    446   [self reloadData];  // Sorts.
    447 }
    448 
    449 @end
    450 
    451 ////////////////////////////////////////////////////////////////////////////////
    452 // TaskManagerMac implementation:
    453 
    454 TaskManagerMac::TaskManagerMac(TaskManager* task_manager)
    455   : task_manager_(task_manager),
    456     model_(task_manager->model()),
    457     icon_cache_(this) {
    458   window_controller_ =
    459       [[TaskManagerWindowController alloc] initWithTaskManagerObserver:this];
    460   model_->AddObserver(this);
    461 }
    462 
    463 // static
    464 TaskManagerMac* TaskManagerMac::instance_ = NULL;
    465 
    466 TaskManagerMac::~TaskManagerMac() {
    467   if (this == instance_) {
    468     // Do not do this when running in unit tests: |StartUpdating()| never got
    469     // called in that case.
    470     task_manager_->OnWindowClosed();
    471   }
    472   model_->RemoveObserver(this);
    473 }
    474 
    475 ////////////////////////////////////////////////////////////////////////////////
    476 // TaskManagerMac, TaskManagerModelObserver implementation:
    477 
    478 void TaskManagerMac::OnModelChanged() {
    479   icon_cache_.OnModelChanged();
    480   [window_controller_ deselectRows];
    481   [window_controller_ reloadData];
    482 }
    483 
    484 void TaskManagerMac::OnItemsChanged(int start, int length) {
    485   icon_cache_.OnItemsChanged(start, length);
    486   [window_controller_ reloadData];
    487 }
    488 
    489 void TaskManagerMac::OnItemsAdded(int start, int length) {
    490   icon_cache_.OnItemsAdded(start, length);
    491   [window_controller_ deselectRows];
    492   [window_controller_ reloadData];
    493 }
    494 
    495 void TaskManagerMac::OnItemsRemoved(int start, int length) {
    496   icon_cache_.OnItemsRemoved(start, length);
    497   [window_controller_ deselectRows];
    498   [window_controller_ reloadData];
    499 }
    500 
    501 NSImage* TaskManagerMac::GetImageForRow(int row) {
    502   return icon_cache_.GetImageForRow(row);
    503 }
    504 
    505 ////////////////////////////////////////////////////////////////////////////////
    506 // TaskManagerMac, public:
    507 
    508 void TaskManagerMac::WindowWasClosed() {
    509   delete this;
    510   instance_ = NULL;
    511 }
    512 
    513 int TaskManagerMac::RowCount() const {
    514   return model_->ResourceCount();
    515 }
    516 
    517 gfx::ImageSkia TaskManagerMac::GetIcon(int r) const {
    518   return model_->GetResourceIcon(r);
    519 }
    520 
    521 // static
    522 void TaskManagerMac::Show() {
    523   if (instance_) {
    524     [[instance_->window_controller_ window]
    525       makeKeyAndOrderFront:instance_->window_controller_];
    526     return;
    527   }
    528   // Create a new instance.
    529   instance_ = new TaskManagerMac(TaskManager::GetInstance());
    530   instance_->model_->StartUpdating();
    531 }
    532 
    533 namespace chrome {
    534 
    535 // Declared in browser_dialogs.h.
    536 void ShowTaskManager(Browser* browser) {
    537   TaskManagerMac::Show();
    538 }
    539 
    540 }  // namespace chrome
    541 
    542