1// Copyright (c) 2011 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 "app/mac/nsimage_cache.h" 10#include "base/mac/mac_util.h" 11#include "base/process_util.h" 12#include "base/sys_string_conversions.h" 13#include "chrome/browser/favicon_helper.h" 14#include "chrome/browser/ui/browser_dialogs.h" 15#include "chrome/browser/ui/browser_list.h" 16#import "chrome/browser/ui/cocoa/multi_key_equivalent_button.h" 17#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" 18#include "chrome/common/logging_chrome.h" 19#include "content/browser/renderer_host/render_process_host.h" 20#include "content/browser/renderer_host/render_view_host.h" 21#include "content/browser/tab_contents/tab_contents.h" 22#import "chrome/browser/ui/cocoa/tab_contents/favicon_util.h" 23#include "content/common/result_codes.h" 24#include "grit/app_resources.h" 25#include "grit/chromium_strings.h" 26#include "grit/generated_resources.h" 27#include "grit/theme_resources.h" 28#include "skia/ext/skia_utils_mac.h" 29#include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" 30#include "ui/base/l10n/l10n_util_mac.h" 31#include "ui/base/resource/resource_bundle.h" 32#include "ui/gfx/image.h" 33 34namespace { 35// We only support showing one of these at a time per app. The 36// controller owns itself and is released when its window is closed. 37HungRendererController* g_instance = NULL; 38} // end namespace 39 40@implementation HungRendererController 41 42- (id)initWithWindowNibName:(NSString*)nibName { 43 NSString* nibpath = [base::mac::MainAppBundle() pathForResource:nibName 44 ofType:@"nib"]; 45 self = [super initWithWindowNibPath:nibpath owner:self]; 46 if (self) { 47 [tableView_ setDataSource:self]; 48 } 49 return self; 50} 51 52- (void)dealloc { 53 DCHECK(!g_instance); 54 [tableView_ setDataSource:nil]; 55 [super dealloc]; 56} 57 58- (void)awakeFromNib { 59 // Load in the image 60 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 61 NSImage* backgroundImage = rb.GetNativeImageNamed(IDR_FROZEN_TAB_ICON); 62 DCHECK(backgroundImage); 63 [imageView_ setImage:backgroundImage]; 64 65 // Make the message fit. 66 CGFloat messageShift = 67 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:messageView_]; 68 69 // Move the graphic up to be top even with the message. 70 NSRect graphicFrame = [imageView_ frame]; 71 graphicFrame.origin.y += messageShift; 72 [imageView_ setFrame:graphicFrame]; 73 74 // Make the window taller to fit everything. 75 NSSize windowDelta = NSMakeSize(0, messageShift); 76 [GTMUILocalizerAndLayoutTweaker 77 resizeWindowWithoutAutoResizingSubViews:[self window] 78 delta:windowDelta]; 79 80 // Make the "wait" button respond to additional keys. By setting this to 81 // @"\e", it will respond to both Esc and Command-. (period). 82 KeyEquivalentAndModifierMask key; 83 key.charCode = @"\e"; 84 [waitButton_ addKeyEquivalent:key]; 85} 86 87- (IBAction)kill:(id)sender { 88 if (hungContents_) 89 base::KillProcess(hungContents_->GetRenderProcessHost()->GetHandle(), 90 ResultCodes::HUNG, false); 91 // Cannot call performClose:, because the close button is disabled. 92 [self close]; 93} 94 95- (IBAction)wait:(id)sender { 96 if (hungContents_ && hungContents_->render_view_host()) 97 hungContents_->render_view_host()->RestartHangMonitorTimeout(); 98 // Cannot call performClose:, because the close button is disabled. 99 [self close]; 100} 101 102- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView { 103 return [hungTitles_ count]; 104} 105 106- (id)tableView:(NSTableView*)aTableView 107 objectValueForTableColumn:(NSTableColumn*)column 108 row:(NSInteger)rowIndex { 109 return [NSNumber numberWithInt:NSOffState]; 110} 111 112- (NSCell*)tableView:(NSTableView*)tableView 113 dataCellForTableColumn:(NSTableColumn*)tableColumn 114 row:(NSInteger)rowIndex { 115 NSCell* cell = [tableColumn dataCellForRow:rowIndex]; 116 117 if ([[tableColumn identifier] isEqualToString:@"title"]) { 118 DCHECK([cell isKindOfClass:[NSButtonCell class]]); 119 NSButtonCell* buttonCell = static_cast<NSButtonCell*>(cell); 120 [buttonCell setTitle:[hungTitles_ objectAtIndex:rowIndex]]; 121 [buttonCell setImage:[hungFavicons_ objectAtIndex:rowIndex]]; 122 [buttonCell setRefusesFirstResponder:YES]; // Don't push in like a button. 123 [buttonCell setHighlightsBy:NSNoCellMask]; 124 } 125 return cell; 126} 127 128- (void)windowWillClose:(NSNotification*)notification { 129 // We have to reset g_instance before autoreleasing the window, 130 // because we want to avoid reusing the same dialog if someone calls 131 // browser::ShowHungRendererDialog() between the autorelease 132 // call and the actual dealloc. 133 g_instance = nil; 134 135 [self autorelease]; 136} 137 138- (void)showForTabContents:(TabContents*)contents { 139 DCHECK(contents); 140 hungContents_ = contents; 141 scoped_nsobject<NSMutableArray> titles([[NSMutableArray alloc] init]); 142 scoped_nsobject<NSMutableArray> favicons([[NSMutableArray alloc] init]); 143 for (TabContentsIterator it; !it.done(); ++it) { 144 if (it->tab_contents()->GetRenderProcessHost() == 145 hungContents_->GetRenderProcessHost()) { 146 string16 title = (*it)->tab_contents()->GetTitle(); 147 if (title.empty()) 148 title = TabContentsWrapper::GetDefaultTitle(); 149 [titles addObject:base::SysUTF16ToNSString(title)]; 150 [favicons addObject:mac::FaviconForTabContents(it->tab_contents())]; 151 } 152 } 153 hungTitles_.reset([titles copy]); 154 hungFavicons_.reset([favicons copy]); 155 [tableView_ reloadData]; 156 157 [[self window] center]; 158 [self showWindow:self]; 159} 160 161- (void)endForTabContents:(TabContents*)contents { 162 DCHECK(contents); 163 DCHECK(hungContents_); 164 if (hungContents_ && hungContents_->GetRenderProcessHost() == 165 contents->GetRenderProcessHost()) { 166 // Cannot call performClose:, because the close button is disabled. 167 [self close]; 168 } 169} 170 171@end 172 173@implementation HungRendererController (JustForTesting) 174- (NSButton*)killButton { 175 return killButton_; 176} 177 178- (MultiKeyEquivalentButton*)waitButton { 179 return waitButton_; 180} 181@end 182 183namespace browser { 184 185void ShowHungRendererDialog(TabContents* contents) { 186 if (!logging::DialogsAreSuppressed()) { 187 if (!g_instance) 188 g_instance = [[HungRendererController alloc] 189 initWithWindowNibName:@"HungRendererDialog"]; 190 [g_instance showForTabContents:contents]; 191 } 192} 193 194void HideHungRendererDialog(TabContents* contents) { 195 if (!logging::DialogsAreSuppressed() && g_instance) 196 [g_instance endForTabContents:contents]; 197} 198 199} // namespace browser 200