Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / hung_renderer_controller.mm
blob41aa619201765a8a1dabbe0c6c1556d94c782b2e
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 #import "chrome/browser/ui/cocoa/hung_renderer_controller.h"
7 #import <Cocoa/Cocoa.h>
9 #include "base/mac/bundle_locations.h"
10 #include "base/process/process.h"
11 #include "base/strings/sys_string_conversions.h"
12 #import "chrome/browser/ui/cocoa/multi_key_equivalent_button.h"
13 #import "chrome/browser/ui/cocoa/tab_contents/favicon_util_mac.h"
14 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
15 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
16 #include "chrome/common/logging_chrome.h"
17 #include "chrome/grit/generated_resources.h"
18 #include "content/public/browser/render_process_host.h"
19 #include "content/public/browser/render_view_host.h"
20 #include "content/public/browser/web_contents.h"
21 #include "content/public/common/result_codes.h"
22 #include "grit/theme_resources.h"
23 #include "skia/ext/skia_utils_mac.h"
24 #include "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
25 #include "ui/base/l10n/l10n_util_mac.h"
26 #include "ui/base/resource/resource_bundle.h"
27 #include "ui/gfx/image/image.h"
29 using content::WebContents;
31 @interface HungRendererController ()
33 // Lays out the interface for the specified number of items.
34 - (void)layoutForItemCount:(int)count;
36 // Modifies the dialog to show a warning for the given tab contents.
37 // The dialog will contain a list of all tabs that share a renderer
38 // process with |contents|.  The caller must not delete any tab
39 // contents without first calling endForWebContents.
40 - (void)showForWebContents:(content::WebContents*)contents;
42 // Notifies the dialog that |contents| is either responsive or closed.
43 // If |contents| shares the same render process as the tab contents
44 // this dialog was created for, this function will close the dialog.
45 // If |contents| has a different process, this function does nothing.
46 - (void)endForWebContents:(content::WebContents*)contents;
48 // Called by |hungContentsObserver_| to indicate that |hungContents_|
49 // has gone away.
50 - (void)renderProcessGone;
52 @end
54 namespace {
55 // We only support showing one of these at a time per app.  The
56 // controller owns itself and is released when its window is closed.
57 HungRendererController* g_instance = NULL;
58 }  // namespace
60 class HungRendererWebContentsObserverBridge
61     : public content::WebContentsObserver {
62  public:
63   HungRendererWebContentsObserverBridge(WebContents* web_contents,
64                                         HungRendererController* controller)
65     : content::WebContentsObserver(web_contents),
66       controller_(controller) {
67   }
69  protected:
70   // WebContentsObserver overrides:
71   void RenderProcessGone(base::TerminationStatus status) override {
72     [controller_ renderProcessGone];
73   }
74   void WebContentsDestroyed() override { [controller_ renderProcessGone]; }
76  private:
77   HungRendererController* controller_;  // weak
79   DISALLOW_COPY_AND_ASSIGN(HungRendererWebContentsObserverBridge);
82 @implementation HungRendererController
84 - (id)initWithWindowNibName:(NSString*)nibName {
85   NSString* nibpath = [base::mac::FrameworkBundle() pathForResource:nibName
86                                                              ofType:@"nib"];
87   self = [super initWithWindowNibPath:nibpath owner:self];
88   if (self) {
89     [tableView_ setDataSource:self];
90   }
91   return self;
94 - (void)dealloc {
95   DCHECK(!g_instance);
96   [tableView_ setDataSource:nil];
97   [tableView_ setDelegate:nil];
98   [killButton_ setTarget:nil];
99   [waitButton_ setTarget:nil];
100   [super dealloc];
103 - (void)awakeFromNib {
104   // Load in the image.
105   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
106   NSImage* backgroundImage =
107       rb.GetNativeImageNamed(IDR_FROZEN_TAB_ICON).ToNSImage();
108   [imageView_ setImage:backgroundImage];
110   // Make the "wait" button respond to additional keys.  By setting this to
111   // @"\e", it will respond to both Esc and Command-. (period).
112   KeyEquivalentAndModifierMask key;
113   key.charCode = @"\e";
114   [waitButton_ addKeyEquivalent:key];
117 - (void)layoutForItemCount:(int)count {
118   // Set the messages.
119   [[self window] setTitle:
120       l10n_util::GetPluralNSStringF(IDS_BROWSER_HANGMONITOR_RENDERER_TITLE,
121                                     count)];
122   [messageView_ setStringValue:
123       l10n_util::GetPluralNSStringF(IDS_BROWSER_HANGMONITOR_RENDERER,
124                                     count)];
126   // Make the message fit.
127   CGFloat messageShift =
128     [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:messageView_];
130   // Move the graphic up to be top even with the message.
131   NSRect graphicFrame = [imageView_ frame];
132   graphicFrame.origin.y += messageShift;
133   [imageView_ setFrame:graphicFrame];
135   // Make the window taller to fit everything.
136   NSSize windowDelta = NSMakeSize(0, messageShift);
137   [GTMUILocalizerAndLayoutTweaker
138       resizeWindowWithoutAutoResizingSubViews:[self window]
139                                         delta:windowDelta];
142 + (void)showForWebContents:(content::WebContents*)contents {
143   if (!logging::DialogsAreSuppressed()) {
144     if (!g_instance)
145       g_instance = [[HungRendererController alloc]
146           initWithWindowNibName:@"HungRendererDialog"];
147     [g_instance showForWebContents:contents];
148   }
151 + (void)endForWebContents:(content::WebContents*)contents {
152   if (!logging::DialogsAreSuppressed() && g_instance)
153     [g_instance endForWebContents:contents];
156 - (IBAction)kill:(id)sender {
157   if (hungContents_) {
158     base::Process process = base::Process::DeprecatedGetProcessFromHandle(
159         hungContents_->GetRenderProcessHost()->GetHandle());
160     process.Terminate(content::RESULT_CODE_HUNG, false);
161   }
162   // Cannot call performClose:, because the close button is disabled.
163   [self close];
166 - (IBAction)wait:(id)sender {
167   if (hungContents_ && hungContents_->GetRenderViewHost())
168     hungContents_->GetRenderViewHost()->RestartHangMonitorTimeout();
169   // Cannot call performClose:, because the close button is disabled.
170   [self close];
173 - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView {
174   return [hungTitles_ count];
177 - (id)tableView:(NSTableView*)aTableView
178       objectValueForTableColumn:(NSTableColumn*)column
179             row:(NSInteger)rowIndex {
180   return [NSNumber numberWithInt:NSOffState];
183 - (NSCell*)tableView:(NSTableView*)tableView
184     dataCellForTableColumn:(NSTableColumn*)tableColumn
185                        row:(NSInteger)rowIndex {
186   NSCell* cell = [tableColumn dataCellForRow:rowIndex];
188   if ([[tableColumn identifier] isEqualToString:@"title"]) {
189     DCHECK([cell isKindOfClass:[NSButtonCell class]]);
190     NSButtonCell* buttonCell = static_cast<NSButtonCell*>(cell);
191     [buttonCell setTitle:[hungTitles_ objectAtIndex:rowIndex]];
192     [buttonCell setImage:[hungFavicons_ objectAtIndex:rowIndex]];
193     [buttonCell setRefusesFirstResponder:YES];  // Don't push in like a button.
194     [buttonCell setHighlightsBy:NSNoCellMask];
195   }
196   return cell;
199 - (void)windowWillClose:(NSNotification*)notification {
200   // We have to reset g_instance before autoreleasing the window,
201   // because we want to avoid reusing the same dialog if someone calls
202   // chrome::ShowHungRendererDialog() between the autorelease call and the
203   // actual dealloc.
204   g_instance = nil;
206   // Prevent kills from happening after close if the user had the
207   // button depressed just when new activity was detected.
208   hungContents_ = NULL;
210   [self autorelease];
213 // TODO(shess): This could observe all of the tabs referenced in the
214 // loop, updating the dialog and keeping it up so long as any remain.
215 // Tabs closed by their renderer will close the dialog (that's
216 // activity!), so it would not add much value.  Also, the views
217 // implementation only monitors the initiating tab.
218 - (void)showForWebContents:(WebContents*)contents {
219   DCHECK(contents);
220   hungContents_ = contents;
221   hungContentsObserver_.reset(
222       new HungRendererWebContentsObserverBridge(contents, self));
223   base::scoped_nsobject<NSMutableArray> titles([[NSMutableArray alloc] init]);
224   base::scoped_nsobject<NSMutableArray> favicons([[NSMutableArray alloc] init]);
225   for (TabContentsIterator it; !it.done(); it.Next()) {
226     if (it->GetRenderProcessHost() == hungContents_->GetRenderProcessHost()) {
227       base::string16 title = it->GetTitle();
228       if (title.empty())
229         title = CoreTabHelper::GetDefaultTitle();
230       [titles addObject:base::SysUTF16ToNSString(title)];
231       [favicons addObject:mac::FaviconForWebContents(*it)];
232     }
233   }
234   hungTitles_.reset([titles copy]);
235   hungFavicons_.reset([favicons copy]);
236   [tableView_ reloadData];
238   [self layoutForItemCount:[titles count]];
239   [[self window] center];
240   [self showWindow:self];
243 - (void)endForWebContents:(WebContents*)contents {
244   DCHECK(contents);
245   DCHECK(hungContents_);
246   if (hungContents_ && hungContents_->GetRenderProcessHost() ==
247       contents->GetRenderProcessHost()) {
248     // Cannot call performClose:, because the close button is disabled.
249     [self close];
250   }
253 - (void)renderProcessGone {
254   // Cannot call performClose:, because the close button is disabled.
255   [self close];
258 @end
260 @implementation HungRendererController (JustForTesting)
261 - (NSButton*)killButton {
262   return killButton_;
265 - (MultiKeyEquivalentButton*)waitButton {
266   return waitButton_;
268 @end