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