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