[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / task_manager_mac.mm
blob65d4b3f7b619d758284ee4748b7438d05ca77494
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.
5 #include "chrome/browser/ui/cocoa/task_manager_mac.h"
7 #include <algorithm>
8 #include <vector>
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"
23 namespace {
25 // Width of "a" and most other letters/digits in "small" table views.
26 const int kCharWidth = 6;
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_NACL_DEBUG_STUB_PORT_COLUMN,
66       arraysize("32767") * kCharWidth, -1 },
67   { IDS_TASK_MANAGER_IDLE_WAKEUPS_COLUMN,
68       arraysize("idlewakeups") * kCharWidth, -1 },
69   { IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN,
70       arraysize("15 ") * kCharWidth, -1 },
73 class SortHelper {
74  public:
75   SortHelper(TaskManagerModel* model, NSSortDescriptor* column)
76       : sort_column_([[column key] intValue]),
77         ascending_([column ascending]),
78         model_(model) {}
80   bool operator()(int a, int b) {
81     TaskManagerModel::GroupRange group_range1 =
82         model_->GetGroupRangeForResource(a);
83     TaskManagerModel::GroupRange group_range2 =
84         model_->GetGroupRangeForResource(b);
85     if (group_range1 == group_range2) {
86       // The two rows are in the same group, sort so that items in the same
87       // group always appear in the same order. |ascending_| is intentionally
88       // ignored.
89       return a < b;
90     }
91     // Sort by the first entry of each of the groups.
92     int cmp_result = model_->CompareValues(
93         group_range1.first, group_range2.first, sort_column_);
94     if (!ascending_)
95       cmp_result = -cmp_result;
96     return cmp_result < 0;
97   }
98  private:
99   int sort_column_;
100   bool ascending_;
101   TaskManagerModel* model_;  // weak;
104 }  // namespace
106 @interface TaskManagerWindowController (Private)
107 - (NSTableColumn*)addColumnWithId:(int)columnId visible:(BOOL)isVisible;
108 - (void)setUpTableColumns;
109 - (void)setUpTableHeaderContextMenu;
110 - (void)toggleColumn:(id)sender;
111 - (void)adjustSelectionAndEndProcessButton;
112 - (void)deselectRows;
113 @end
115 ////////////////////////////////////////////////////////////////////////////////
116 // TaskManagerWindowController implementation:
118 @implementation TaskManagerWindowController
120 - (id)initWithTaskManagerObserver:(TaskManagerMac*)taskManagerObserver {
121   NSString* nibpath = [base::mac::FrameworkBundle()
122                         pathForResource:@"TaskManager"
123                                  ofType:@"nib"];
124   if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
125     taskManagerObserver_ = taskManagerObserver;
126     taskManager_ = taskManagerObserver_->task_manager();
127     model_ = taskManager_->model();
129     if (g_browser_process && g_browser_process->local_state()) {
130       size_saver_.reset([[WindowSizeAutosaver alloc]
131           initWithWindow:[self window]
132              prefService:g_browser_process->local_state()
133                     path:prefs::kTaskManagerWindowPlacement]);
134     }
135     [self showWindow:self];
136   }
137   return self;
140 - (void)sortShuffleArray {
141   viewToModelMap_.resize(model_->ResourceCount());
142   for (size_t i = 0; i < viewToModelMap_.size(); ++i)
143     viewToModelMap_[i] = i;
145   std::sort(viewToModelMap_.begin(), viewToModelMap_.end(),
146             SortHelper(model_, currentSortDescriptor_.get()));
148   modelToViewMap_.resize(viewToModelMap_.size());
149   for (size_t i = 0; i < viewToModelMap_.size(); ++i)
150     modelToViewMap_[viewToModelMap_[i]] = i;
153 - (void)reloadData {
154   // Store old view indices, and the model indices they map to.
155   NSIndexSet* viewSelection = [tableView_ selectedRowIndexes];
156   std::vector<int> modelSelection;
157   for (NSUInteger i = [viewSelection lastIndex];
158        i != NSNotFound;
159        i = [viewSelection indexLessThanIndex:i]) {
160     modelSelection.push_back(viewToModelMap_[i]);
161   }
163   // Sort.
164   [self sortShuffleArray];
166   // Use the model indices to get the new view indices of the selection, and
167   // set selection to that. This assumes that no rows were added or removed
168   // (in that case, the selection is cleared before -reloadData is called).
169   if (!modelSelection.empty())
170     DCHECK_EQ([tableView_ numberOfRows], model_->ResourceCount());
171   NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet];
172   for (size_t i = 0; i < modelSelection.size(); ++i)
173     [indexSet addIndex:modelToViewMap_[modelSelection[i]]];
174   [tableView_ selectRowIndexes:indexSet byExtendingSelection:NO];
176   [tableView_ reloadData];
177   [self adjustSelectionAndEndProcessButton];
180 - (IBAction)statsLinkClicked:(id)sender {
181   TaskManager::GetInstance()->OpenAboutMemory(chrome::HOST_DESKTOP_TYPE_NATIVE);
184 - (IBAction)killSelectedProcesses:(id)sender {
185   NSIndexSet* selection = [tableView_ selectedRowIndexes];
186   for (NSUInteger i = [selection lastIndex];
187        i != NSNotFound;
188        i = [selection indexLessThanIndex:i]) {
189     taskManager_->KillProcess(viewToModelMap_[i]);
190   }
193 - (void)selectDoubleClickedTab:(id)sender {
194   NSInteger row = [tableView_ clickedRow];
195   if (row < 0)
196     return;  // Happens e.g. if the table header is double-clicked.
197   taskManager_->ActivateProcess(viewToModelMap_[row]);
200 - (NSTableView*)tableView {
201   return tableView_;
204 - (void)awakeFromNib {
205   [self setUpTableColumns];
206   [self setUpTableHeaderContextMenu];
207   [self adjustSelectionAndEndProcessButton];
209   [tableView_ setDoubleAction:@selector(selectDoubleClickedTab:)];
210   [tableView_ setIntercellSpacing:NSMakeSize(0.0, 0.0)];
211   [tableView_ sizeToFit];
214 - (void)dealloc {
215   [tableView_ setDelegate:nil];
216   [tableView_ setDataSource:nil];
217   [super dealloc];
220 // Adds a column which has the given string id as title. |isVisible| specifies
221 // if the column is initially visible.
222 - (NSTableColumn*)addColumnWithId:(int)columnId visible:(BOOL)isVisible {
223   base::scoped_nsobject<NSTableColumn> column([[NSTableColumn alloc]
224       initWithIdentifier:[NSString stringWithFormat:@"%d", columnId]]);
226   NSTextAlignment textAlignment =
227       (columnId == IDS_TASK_MANAGER_TASK_COLUMN ||
228        columnId == IDS_TASK_MANAGER_PROFILE_NAME_COLUMN) ?
229           NSLeftTextAlignment : NSRightTextAlignment;
231   [[column.get() headerCell]
232       setStringValue:l10n_util::GetNSStringWithFixup(columnId)];
233   [[column.get() headerCell] setAlignment:textAlignment];
234   [[column.get() dataCell] setAlignment:textAlignment];
236   NSFont* font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
237   [[column.get() dataCell] setFont:font];
239   [column.get() setHidden:!isVisible];
240   [column.get() setEditable:NO];
242   // The page column should by default be sorted ascending.
243   BOOL ascending = columnId == IDS_TASK_MANAGER_TASK_COLUMN;
245   base::scoped_nsobject<NSSortDescriptor> sortDescriptor(
246       [[NSSortDescriptor alloc]
247           initWithKey:[NSString stringWithFormat:@"%d", columnId]
248             ascending:ascending]);
249   [column.get() setSortDescriptorPrototype:sortDescriptor.get()];
251   // Default values, only used in release builds if nobody notices the DCHECK
252   // during development when adding new columns.
253   int minWidth = 200, maxWidth = 400;
255   size_t i;
256   for (i = 0; i < arraysize(columnWidths); ++i) {
257     if (columnWidths[i].columnId == columnId) {
258       minWidth = columnWidths[i].minWidth;
259       maxWidth = columnWidths[i].maxWidth;
260       if (maxWidth < 0)
261         maxWidth = 3 * minWidth / 2;  // *1.5 for ints.
262       break;
263     }
264   }
265   DCHECK(i < arraysize(columnWidths)) << "Could not find " << columnId;
266   [column.get() setMinWidth:minWidth];
267   [column.get() setMaxWidth:maxWidth];
268   [column.get() setResizingMask:NSTableColumnAutoresizingMask |
269                                 NSTableColumnUserResizingMask];
271   [tableView_ addTableColumn:column.get()];
272   return column.get();  // Now retained by |tableView_|.
275 // Adds all the task manager's columns to the table.
276 - (void)setUpTableColumns {
277   for (NSTableColumn* column in [tableView_ tableColumns])
278     [tableView_ removeTableColumn:column];
279   NSTableColumn* nameColumn = [self addColumnWithId:IDS_TASK_MANAGER_TASK_COLUMN
280                                             visible:YES];
281   // |nameColumn| displays an icon for every row -- this is done by an
282   // NSButtonCell.
283   base::scoped_nsobject<NSButtonCell> nameCell(
284       [[NSButtonCell alloc] initTextCell:@""]);
285   [nameCell.get() setImagePosition:NSImageLeft];
286   [nameCell.get() setButtonType:NSSwitchButton];
287   [nameCell.get() setAlignment:[[nameColumn dataCell] alignment]];
288   [nameCell.get() setFont:[[nameColumn dataCell] font]];
289   [nameColumn setDataCell:nameCell.get()];
291   // Initially, sort on the tab name.
292   [tableView_ setSortDescriptors:
293       [NSArray arrayWithObject:[nameColumn sortDescriptorPrototype]]];
294   [self addColumnWithId:IDS_TASK_MANAGER_PROFILE_NAME_COLUMN visible:NO];
295   [self addColumnWithId:IDS_TASK_MANAGER_PHYSICAL_MEM_COLUMN visible:YES];
296   [self addColumnWithId:IDS_TASK_MANAGER_SHARED_MEM_COLUMN visible:NO];
297   [self addColumnWithId:IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN visible:NO];
298   [self addColumnWithId:IDS_TASK_MANAGER_CPU_COLUMN visible:YES];
299   [self addColumnWithId:IDS_TASK_MANAGER_NET_COLUMN visible:YES];
300   [self addColumnWithId:IDS_TASK_MANAGER_PROCESS_ID_COLUMN visible:YES];
301   [self addColumnWithId:IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN
302                 visible:NO];
303   [self addColumnWithId:IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN
304                 visible:NO];
305   [self addColumnWithId:IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN visible:NO];
306   [self addColumnWithId:IDS_TASK_MANAGER_FPS_COLUMN visible:YES];
307   [self addColumnWithId:IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN visible:NO];
308   [self addColumnWithId:IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN visible:NO];
309   [self addColumnWithId:IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN
310                 visible:NO];
311   [self addColumnWithId:IDS_TASK_MANAGER_NACL_DEBUG_STUB_PORT_COLUMN
312                 visible:NO];
313   [self addColumnWithId:IDS_TASK_MANAGER_IDLE_WAKEUPS_COLUMN
314                 visible:NO];
315   [self addColumnWithId:IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN visible:NO];
318 // Creates a context menu for the table header that allows the user to toggle
319 // which columns should be shown and which should be hidden (like e.g.
320 // Task Manager.app's table header context menu).
321 - (void)setUpTableHeaderContextMenu {
322   base::scoped_nsobject<NSMenu> contextMenu(
323       [[NSMenu alloc] initWithTitle:@"Task Manager context menu"]);
324   for (NSTableColumn* column in [tableView_ tableColumns]) {
325     NSMenuItem* item = [contextMenu.get()
326         addItemWithTitle:[[column headerCell] stringValue]
327                   action:@selector(toggleColumn:)
328            keyEquivalent:@""];
329     [item setTarget:self];
330     [item setRepresentedObject:column];
331     [item setState:[column isHidden] ? NSOffState : NSOnState];
332   }
333   [[tableView_ headerView] setMenu:contextMenu.get()];
336 // Callback for the table header context menu. Toggles visibility of the table
337 // column associated with the clicked menu item.
338 - (void)toggleColumn:(id)item {
339   DCHECK([item isKindOfClass:[NSMenuItem class]]);
340   if (![item isKindOfClass:[NSMenuItem class]])
341     return;
343   NSTableColumn* column = [item representedObject];
344   DCHECK(column);
345   NSInteger oldState = [item state];
346   NSInteger newState = oldState == NSOnState ? NSOffState : NSOnState;
347   [column setHidden:newState == NSOffState];
348   [item setState:newState];
349   [tableView_ sizeToFit];
350   [tableView_ setNeedsDisplay];
353 // This function appropriately sets the enabled states on the table's editing
354 // buttons.
355 - (void)adjustSelectionAndEndProcessButton {
356   bool selectionContainsBrowserProcess = false;
358   // If a row is selected, make sure that all rows belonging to the same process
359   // are selected as well. Also, check if the selection contains the browser
360   // process.
361   NSIndexSet* selection = [tableView_ selectedRowIndexes];
362   for (NSUInteger i = [selection lastIndex];
363        i != NSNotFound;
364        i = [selection indexLessThanIndex:i]) {
365     int modelIndex = viewToModelMap_[i];
366     if (taskManager_->IsBrowserProcess(modelIndex))
367       selectionContainsBrowserProcess = true;
369     TaskManagerModel::GroupRange rangePair =
370         model_->GetGroupRangeForResource(modelIndex);
371     NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet];
372     for (int j = 0; j < rangePair.second; ++j)
373       [indexSet addIndex:modelToViewMap_[rangePair.first + j]];
374     [tableView_ selectRowIndexes:indexSet byExtendingSelection:YES];
375   }
377   bool enabled = [selection count] > 0 && !selectionContainsBrowserProcess;
378   [endProcessButton_ setEnabled:enabled];
381 - (void)deselectRows {
382   [tableView_ deselectAll:self];
385 // Table view delegate methods.
387 // The selection is being changed by mouse (drag/click).
388 - (void)tableViewSelectionIsChanging:(NSNotification*)aNotification {
389   [self adjustSelectionAndEndProcessButton];
392 // The selection is being changed by keyboard (arrows).
393 - (void)tableViewSelectionDidChange:(NSNotification*)aNotification {
394   [self adjustSelectionAndEndProcessButton];
397 - (void)windowWillClose:(NSNotification*)notification {
398   if (taskManagerObserver_) {
399     taskManagerObserver_->WindowWasClosed();
400     taskManagerObserver_ = nil;
401   }
402   [self autorelease];
405 @end
407 @implementation TaskManagerWindowController (NSTableDataSource)
409 - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView {
410   DCHECK(tableView == tableView_ || tableView_ == nil);
411   return model_->ResourceCount();
414 - (NSString*)modelTextForRow:(int)row column:(int)columnId {
415   DCHECK_LT(static_cast<size_t>(row), viewToModelMap_.size());
416   return base::SysUTF16ToNSString(
417       model_->GetResourceById(viewToModelMap_[row], columnId));
420 - (id)tableView:(NSTableView*)tableView
421     objectValueForTableColumn:(NSTableColumn*)tableColumn
422                           row:(NSInteger)rowIndex {
423   // NSButtonCells expect an on/off state as objectValue. Their title is set
424   // in |tableView:dataCellForTableColumn:row:| below.
425   if ([[tableColumn identifier] intValue] == IDS_TASK_MANAGER_TASK_COLUMN) {
426     return [NSNumber numberWithInt:NSOffState];
427   }
429   return [self modelTextForRow:rowIndex
430                         column:[[tableColumn identifier] intValue]];
433 - (NSCell*)tableView:(NSTableView*)tableView
434     dataCellForTableColumn:(NSTableColumn*)tableColumn
435                        row:(NSInteger)rowIndex {
436   NSCell* cell = [tableColumn dataCellForRow:rowIndex];
438   // Set the favicon and title for the task in the name column.
439   if ([[tableColumn identifier] intValue] == IDS_TASK_MANAGER_TASK_COLUMN) {
440     DCHECK([cell isKindOfClass:[NSButtonCell class]]);
441     NSButtonCell* buttonCell = static_cast<NSButtonCell*>(cell);
442     NSString* title = [self modelTextForRow:rowIndex
443                                     column:[[tableColumn identifier] intValue]];
444     [buttonCell setTitle:title];
445     [buttonCell setImage:
446         taskManagerObserver_->GetImageForRow(viewToModelMap_[rowIndex])];
447     [buttonCell setRefusesFirstResponder:YES];  // Don't push in like a button.
448     [buttonCell setHighlightsBy:NSNoCellMask];
449   }
451   return cell;
454 - (void)           tableView:(NSTableView*)tableView
455     sortDescriptorsDidChange:(NSArray*)oldDescriptors {
456   NSArray* newDescriptors = [tableView sortDescriptors];
457   if ([newDescriptors count] < 1)
458     return;
460   currentSortDescriptor_.reset([[newDescriptors objectAtIndex:0] retain]);
461   [self reloadData];  // Sorts.
464 @end
466 ////////////////////////////////////////////////////////////////////////////////
467 // TaskManagerMac implementation:
469 TaskManagerMac::TaskManagerMac(TaskManager* task_manager)
470   : task_manager_(task_manager),
471     model_(task_manager->model()),
472     icon_cache_(this) {
473   window_controller_ =
474       [[TaskManagerWindowController alloc] initWithTaskManagerObserver:this];
475   model_->AddObserver(this);
478 // static
479 TaskManagerMac* TaskManagerMac::instance_ = NULL;
481 TaskManagerMac::~TaskManagerMac() {
482   if (this == instance_) {
483     // Do not do this when running in unit tests: |StartUpdating()| never got
484     // called in that case.
485     task_manager_->OnWindowClosed();
486   }
487   model_->RemoveObserver(this);
490 ////////////////////////////////////////////////////////////////////////////////
491 // TaskManagerMac, TaskManagerModelObserver implementation:
493 void TaskManagerMac::OnModelChanged() {
494   icon_cache_.OnModelChanged();
495   [window_controller_ deselectRows];
496   [window_controller_ reloadData];
499 void TaskManagerMac::OnItemsChanged(int start, int length) {
500   icon_cache_.OnItemsChanged(start, length);
501   [window_controller_ reloadData];
504 void TaskManagerMac::OnItemsAdded(int start, int length) {
505   icon_cache_.OnItemsAdded(start, length);
506   [window_controller_ deselectRows];
507   [window_controller_ reloadData];
510 void TaskManagerMac::OnItemsRemoved(int start, int length) {
511   icon_cache_.OnItemsRemoved(start, length);
512   [window_controller_ deselectRows];
513   [window_controller_ reloadData];
516 NSImage* TaskManagerMac::GetImageForRow(int row) {
517   return icon_cache_.GetImageForRow(row);
520 ////////////////////////////////////////////////////////////////////////////////
521 // TaskManagerMac, public:
523 void TaskManagerMac::WindowWasClosed() {
524   instance_ = NULL;
525   delete this;
528 int TaskManagerMac::RowCount() const {
529   return model_->ResourceCount();
532 gfx::ImageSkia TaskManagerMac::GetIcon(int r) const {
533   return model_->GetResourceIcon(r);
536 // static
537 void TaskManagerMac::Show() {
538   if (instance_) {
539     [[instance_->window_controller_ window]
540       makeKeyAndOrderFront:instance_->window_controller_];
541     return;
542   }
543   // Create a new instance.
544   instance_ = new TaskManagerMac(TaskManager::GetInstance());
545   instance_->model_->StartUpdating();
548 namespace chrome {
550 // Declared in browser_dialogs.h.
551 void ShowTaskManager(Browser* browser) {
552   TaskManagerMac::Show();
555 }  // namespace chrome