• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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#import "chrome/browser/ui/cocoa/hung_renderer_controller.h"
6
7#import <Cocoa/Cocoa.h>
8
9#include "base/mac/bundle_locations.h"
10#include "base/mac/mac_util.h"
11#include "base/strings/sys_string_conversions.h"
12#include "chrome/browser/favicon/favicon_tab_helper.h"
13#include "chrome/browser/ui/browser_dialogs.h"
14#import "chrome/browser/ui/cocoa/multi_key_equivalent_button.h"
15#import "chrome/browser/ui/cocoa/tab_contents/favicon_util_mac.h"
16#include "chrome/browser/ui/tab_contents/core_tab_helper.h"
17#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
18#include "chrome/common/logging_chrome.h"
19#include "content/public/browser/render_process_host.h"
20#include "content/public/browser/render_view_host.h"
21#include "content/public/browser/web_contents.h"
22#include "content/public/common/result_codes.h"
23#include "grit/chromium_strings.h"
24#include "grit/generated_resources.h"
25#include "grit/theme_resources.h"
26#include "grit/ui_resources.h"
27#include "skia/ext/skia_utils_mac.h"
28#include "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
29#include "ui/base/l10n/l10n_util_mac.h"
30#include "ui/base/resource/resource_bundle.h"
31#include "ui/gfx/image/image.h"
32
33using content::WebContents;
34
35namespace {
36// We only support showing one of these at a time per app.  The
37// controller owns itself and is released when its window is closed.
38HungRendererController* g_instance = NULL;
39}  // namespace
40
41class HungRendererWebContentsObserverBridge
42    : public content::WebContentsObserver {
43 public:
44  HungRendererWebContentsObserverBridge(WebContents* web_contents,
45                                        HungRendererController* controller)
46    : content::WebContentsObserver(web_contents),
47      controller_(controller) {
48  }
49
50 protected:
51  // WebContentsObserver overrides:
52  virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE {
53    [controller_ renderProcessGone];
54  }
55  virtual void WebContentsDestroyed() OVERRIDE {
56    [controller_ renderProcessGone];
57  }
58
59 private:
60  HungRendererController* controller_;  // weak
61
62  DISALLOW_COPY_AND_ASSIGN(HungRendererWebContentsObserverBridge);
63};
64
65@implementation HungRendererController
66
67- (id)initWithWindowNibName:(NSString*)nibName {
68  NSString* nibpath = [base::mac::FrameworkBundle() pathForResource:nibName
69                                                             ofType:@"nib"];
70  self = [super initWithWindowNibPath:nibpath owner:self];
71  if (self) {
72    [tableView_ setDataSource:self];
73  }
74  return self;
75}
76
77- (void)dealloc {
78  DCHECK(!g_instance);
79  [tableView_ setDataSource:nil];
80  [tableView_ setDelegate:nil];
81  [killButton_ setTarget:nil];
82  [waitButton_ setTarget:nil];
83  [super dealloc];
84}
85
86- (void)awakeFromNib {
87  // Load in the image
88  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
89  NSImage* backgroundImage =
90      rb.GetNativeImageNamed(IDR_FROZEN_TAB_ICON).ToNSImage();
91  [imageView_ setImage:backgroundImage];
92
93  // Make the message fit.
94  CGFloat messageShift =
95    [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:messageView_];
96
97  // Move the graphic up to be top even with the message.
98  NSRect graphicFrame = [imageView_ frame];
99  graphicFrame.origin.y += messageShift;
100  [imageView_ setFrame:graphicFrame];
101
102  // Make the window taller to fit everything.
103  NSSize windowDelta = NSMakeSize(0, messageShift);
104  [GTMUILocalizerAndLayoutTweaker
105      resizeWindowWithoutAutoResizingSubViews:[self window]
106                                        delta:windowDelta];
107
108  // Make the "wait" button respond to additional keys.  By setting this to
109  // @"\e", it will respond to both Esc and Command-. (period).
110  KeyEquivalentAndModifierMask key;
111  key.charCode = @"\e";
112  [waitButton_ addKeyEquivalent:key];
113}
114
115- (IBAction)kill:(id)sender {
116  if (hungContents_)
117    base::KillProcess(hungContents_->GetRenderProcessHost()->GetHandle(),
118                      content::RESULT_CODE_HUNG, false);
119  // Cannot call performClose:, because the close button is disabled.
120  [self close];
121}
122
123- (IBAction)wait:(id)sender {
124  if (hungContents_ && hungContents_->GetRenderViewHost())
125    hungContents_->GetRenderViewHost()->RestartHangMonitorTimeout();
126  // Cannot call performClose:, because the close button is disabled.
127  [self close];
128}
129
130- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView {
131  return [hungTitles_ count];
132}
133
134- (id)tableView:(NSTableView*)aTableView
135      objectValueForTableColumn:(NSTableColumn*)column
136            row:(NSInteger)rowIndex {
137  return [NSNumber numberWithInt:NSOffState];
138}
139
140- (NSCell*)tableView:(NSTableView*)tableView
141    dataCellForTableColumn:(NSTableColumn*)tableColumn
142                       row:(NSInteger)rowIndex {
143  NSCell* cell = [tableColumn dataCellForRow:rowIndex];
144
145  if ([[tableColumn identifier] isEqualToString:@"title"]) {
146    DCHECK([cell isKindOfClass:[NSButtonCell class]]);
147    NSButtonCell* buttonCell = static_cast<NSButtonCell*>(cell);
148    [buttonCell setTitle:[hungTitles_ objectAtIndex:rowIndex]];
149    [buttonCell setImage:[hungFavicons_ objectAtIndex:rowIndex]];
150    [buttonCell setRefusesFirstResponder:YES];  // Don't push in like a button.
151    [buttonCell setHighlightsBy:NSNoCellMask];
152  }
153  return cell;
154}
155
156- (void)windowWillClose:(NSNotification*)notification {
157  // We have to reset g_instance before autoreleasing the window,
158  // because we want to avoid reusing the same dialog if someone calls
159  // chrome::ShowHungRendererDialog() between the autorelease call and the
160  // actual dealloc.
161  g_instance = nil;
162
163  // Prevent kills from happening after close if the user had the
164  // button depressed just when new activity was detected.
165  hungContents_ = NULL;
166
167  [self autorelease];
168}
169
170// TODO(shess): This could observe all of the tabs referenced in the
171// loop, updating the dialog and keeping it up so long as any remain.
172// Tabs closed by their renderer will close the dialog (that's
173// activity!), so it would not add much value.  Also, the views
174// implementation only monitors the initiating tab.
175- (void)showForWebContents:(WebContents*)contents {
176  DCHECK(contents);
177  hungContents_ = contents;
178  hungContentsObserver_.reset(
179      new HungRendererWebContentsObserverBridge(contents, self));
180  base::scoped_nsobject<NSMutableArray> titles([[NSMutableArray alloc] init]);
181  base::scoped_nsobject<NSMutableArray> favicons([[NSMutableArray alloc] init]);
182  for (TabContentsIterator it; !it.done(); it.Next()) {
183    if (it->GetRenderProcessHost() == hungContents_->GetRenderProcessHost()) {
184      base::string16 title = it->GetTitle();
185      if (title.empty())
186        title = CoreTabHelper::GetDefaultTitle();
187      [titles addObject:base::SysUTF16ToNSString(title)];
188      [favicons addObject:mac::FaviconForWebContents(*it)];
189    }
190  }
191  hungTitles_.reset([titles copy]);
192  hungFavicons_.reset([favicons copy]);
193  [tableView_ reloadData];
194
195  [[self window] center];
196  [self showWindow:self];
197}
198
199- (void)endForWebContents:(WebContents*)contents {
200  DCHECK(contents);
201  DCHECK(hungContents_);
202  if (hungContents_ && hungContents_->GetRenderProcessHost() ==
203      contents->GetRenderProcessHost()) {
204    // Cannot call performClose:, because the close button is disabled.
205    [self close];
206  }
207}
208
209- (void)renderProcessGone {
210  // Cannot call performClose:, because the close button is disabled.
211  [self close];
212}
213
214@end
215
216@implementation HungRendererController (JustForTesting)
217- (NSButton*)killButton {
218  return killButton_;
219}
220
221- (MultiKeyEquivalentButton*)waitButton {
222  return waitButton_;
223}
224@end
225
226namespace chrome {
227
228void ShowHungRendererDialog(WebContents* contents) {
229  if (!logging::DialogsAreSuppressed()) {
230    if (!g_instance)
231      g_instance = [[HungRendererController alloc]
232                     initWithWindowNibName:@"HungRendererDialog"];
233    [g_instance showForWebContents:contents];
234  }
235}
236
237void HideHungRendererDialog(WebContents* contents) {
238  if (!logging::DialogsAreSuppressed() && g_instance)
239    [g_instance endForWebContents:contents];
240}
241
242}  // namespace chrome
243