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 30using content::WebContents; 31 32namespace { 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. 35HungRendererController* g_instance = NULL; 36} // namespace 37 38class 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 223namespace chrome { 224 225void 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 234void HideHungRendererDialog(WebContents* contents) { 235 if (!logging::DialogsAreSuppressed() && g_instance) 236 [g_instance endForWebContents:contents]; 237} 238 239} // namespace chrome 240