Home | History | Annotate | Download | only in cocoa
      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 
     34 namespace {
     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.
     37 HungRendererController* 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 
    183 namespace browser {
    184 
    185 void 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 
    194 void HideHungRendererDialog(TabContents* contents) {
    195   if (!logging::DialogsAreSuppressed() && g_instance)
    196     [g_instance endForTabContents:contents];
    197 }
    198 
    199 }  // namespace browser
    200