1// Copyright (c) 2011 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#import "chrome/browser/ui/cocoa/hung_renderer_controller.h"
6
7#import <Cocoa/Cocoa.h>
8
9#include "app/mac/nsimage_cache.h"
10#include "base/mac/mac_util.h"
11#include "base/process_util.h"
12#include "base/sys_string_conversions.h"
13#include "chrome/browser/favicon_helper.h"
14#include "chrome/browser/ui/browser_dialogs.h"
15#include "chrome/browser/ui/browser_list.h"
16#import "chrome/browser/ui/cocoa/multi_key_equivalent_button.h"
17#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
18#include "chrome/common/logging_chrome.h"
19#include "content/browser/renderer_host/render_process_host.h"
20#include "content/browser/renderer_host/render_view_host.h"
21#include "content/browser/tab_contents/tab_contents.h"
22#import "chrome/browser/ui/cocoa/tab_contents/favicon_util.h"
23#include "content/common/result_codes.h"
24#include "grit/app_resources.h"
25#include "grit/chromium_strings.h"
26#include "grit/generated_resources.h"
27#include "grit/theme_resources.h"
28#include "skia/ext/skia_utils_mac.h"
29#include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
30#include "ui/base/l10n/l10n_util_mac.h"
31#include "ui/base/resource/resource_bundle.h"
32#include "ui/gfx/image.h"
33
34namespace {
35// We only support showing one of these at a time per app.  The
36// controller owns itself and is released when its window is closed.
37HungRendererController* g_instance = NULL;
38}  // end namespace
39
40@implementation HungRendererController
41
42- (id)initWithWindowNibName:(NSString*)nibName {
43  NSString* nibpath = [base::mac::MainAppBundle() pathForResource:nibName
44                                                          ofType:@"nib"];
45  self = [super initWithWindowNibPath:nibpath owner:self];
46  if (self) {
47    [tableView_ setDataSource:self];
48  }
49  return self;
50}
51
52- (void)dealloc {
53  DCHECK(!g_instance);
54  [tableView_ setDataSource:nil];
55  [super dealloc];
56}
57
58- (void)awakeFromNib {
59  // Load in the image
60  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
61  NSImage* backgroundImage = rb.GetNativeImageNamed(IDR_FROZEN_TAB_ICON);
62  DCHECK(backgroundImage);
63  [imageView_ setImage:backgroundImage];
64
65  // Make the message fit.
66  CGFloat messageShift =
67    [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:messageView_];
68
69  // Move the graphic up to be top even with the message.
70  NSRect graphicFrame = [imageView_ frame];
71  graphicFrame.origin.y += messageShift;
72  [imageView_ setFrame:graphicFrame];
73
74  // Make the window taller to fit everything.
75  NSSize windowDelta = NSMakeSize(0, messageShift);
76  [GTMUILocalizerAndLayoutTweaker
77      resizeWindowWithoutAutoResizingSubViews:[self window]
78                                        delta:windowDelta];
79
80  // Make the "wait" button respond to additional keys.  By setting this to
81  // @"\e", it will respond to both Esc and Command-. (period).
82  KeyEquivalentAndModifierMask key;
83  key.charCode = @"\e";
84  [waitButton_ addKeyEquivalent:key];
85}
86
87- (IBAction)kill:(id)sender {
88  if (hungContents_)
89    base::KillProcess(hungContents_->GetRenderProcessHost()->GetHandle(),
90                      ResultCodes::HUNG, false);
91  // Cannot call performClose:, because the close button is disabled.
92  [self close];
93}
94
95- (IBAction)wait:(id)sender {
96  if (hungContents_ && hungContents_->render_view_host())
97    hungContents_->render_view_host()->RestartHangMonitorTimeout();
98  // Cannot call performClose:, because the close button is disabled.
99  [self close];
100}
101
102- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView {
103  return [hungTitles_ count];
104}
105
106- (id)tableView:(NSTableView*)aTableView
107      objectValueForTableColumn:(NSTableColumn*)column
108            row:(NSInteger)rowIndex {
109  return [NSNumber numberWithInt:NSOffState];
110}
111
112- (NSCell*)tableView:(NSTableView*)tableView
113    dataCellForTableColumn:(NSTableColumn*)tableColumn
114                       row:(NSInteger)rowIndex {
115  NSCell* cell = [tableColumn dataCellForRow:rowIndex];
116
117  if ([[tableColumn identifier] isEqualToString:@"title"]) {
118    DCHECK([cell isKindOfClass:[NSButtonCell class]]);
119    NSButtonCell* buttonCell = static_cast<NSButtonCell*>(cell);
120    [buttonCell setTitle:[hungTitles_ objectAtIndex:rowIndex]];
121    [buttonCell setImage:[hungFavicons_ objectAtIndex:rowIndex]];
122    [buttonCell setRefusesFirstResponder:YES];  // Don't push in like a button.
123    [buttonCell setHighlightsBy:NSNoCellMask];
124  }
125  return cell;
126}
127
128- (void)windowWillClose:(NSNotification*)notification {
129  // We have to reset g_instance before autoreleasing the window,
130  // because we want to avoid reusing the same dialog if someone calls
131  // browser::ShowHungRendererDialog() between the autorelease
132  // call and the actual dealloc.
133  g_instance = nil;
134
135  [self autorelease];
136}
137
138- (void)showForTabContents:(TabContents*)contents {
139  DCHECK(contents);
140  hungContents_ = contents;
141  scoped_nsobject<NSMutableArray> titles([[NSMutableArray alloc] init]);
142  scoped_nsobject<NSMutableArray> favicons([[NSMutableArray alloc] init]);
143  for (TabContentsIterator it; !it.done(); ++it) {
144    if (it->tab_contents()->GetRenderProcessHost() ==
145        hungContents_->GetRenderProcessHost()) {
146      string16 title = (*it)->tab_contents()->GetTitle();
147      if (title.empty())
148        title = TabContentsWrapper::GetDefaultTitle();
149      [titles addObject:base::SysUTF16ToNSString(title)];
150      [favicons addObject:mac::FaviconForTabContents(it->tab_contents())];
151    }
152  }
153  hungTitles_.reset([titles copy]);
154  hungFavicons_.reset([favicons copy]);
155  [tableView_ reloadData];
156
157  [[self window] center];
158  [self showWindow:self];
159}
160
161- (void)endForTabContents:(TabContents*)contents {
162  DCHECK(contents);
163  DCHECK(hungContents_);
164  if (hungContents_ && hungContents_->GetRenderProcessHost() ==
165      contents->GetRenderProcessHost()) {
166    // Cannot call performClose:, because the close button is disabled.
167    [self close];
168  }
169}
170
171@end
172
173@implementation HungRendererController (JustForTesting)
174- (NSButton*)killButton {
175  return killButton_;
176}
177
178- (MultiKeyEquivalentButton*)waitButton {
179  return waitButton_;
180}
181@end
182
183namespace browser {
184
185void ShowHungRendererDialog(TabContents* contents) {
186  if (!logging::DialogsAreSuppressed()) {
187    if (!g_instance)
188      g_instance = [[HungRendererController alloc]
189                     initWithWindowNibName:@"HungRendererDialog"];
190    [g_instance showForTabContents:contents];
191  }
192}
193
194void HideHungRendererDialog(TabContents* contents) {
195  if (!logging::DialogsAreSuppressed() && g_instance)
196    [g_instance endForTabContents:contents];
197}
198
199}  // namespace browser
200