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