hung_renderer_view.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
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#include "chrome/browser/hung_renderer_dialog.h" 6 7#include "base/i18n/rtl.h" 8#include "base/utf_string_conversions.h" 9#include "chrome/browser/browser_list.h" 10#include "chrome/browser/renderer_host/render_process_host.h" 11#include "chrome/browser/renderer_host/render_view_host.h" 12#include "chrome/browser/tab_contents/tab_contents.h" 13#include "chrome/common/chrome_constants.h" 14#include "chrome/common/logging_chrome.h" 15#include "chrome/common/result_codes.h" 16#include "grit/chromium_strings.h" 17#include "grit/generated_resources.h" 18#include "grit/theme_resources.h" 19#include "ui/base/l10n/l10n_util.h" 20#include "ui/base/resource/resource_bundle.h" 21#include "ui/gfx/canvas.h" 22#include "views/controls/button/native_button.h" 23#include "views/controls/image_view.h" 24#include "views/controls/label.h" 25#include "views/controls/table/group_table_view.h" 26#include "views/layout/grid_layout.h" 27#include "views/layout/layout_constants.h" 28#include "views/window/client_view.h" 29#include "views/window/dialog_delegate.h" 30#include "views/window/window.h" 31 32class HungRendererDialogView; 33 34namespace { 35// We only support showing one of these at a time per app. 36HungRendererDialogView* g_instance = NULL; 37} 38 39/////////////////////////////////////////////////////////////////////////////// 40// HungPagesTableModel 41 42class HungPagesTableModel : public views::GroupTableModel { 43 public: 44 HungPagesTableModel(); 45 virtual ~HungPagesTableModel(); 46 47 void InitForTabContents(TabContents* hung_contents); 48 49 // Overridden from views::GroupTableModel: 50 virtual int RowCount(); 51 virtual string16 GetText(int row, int column_id); 52 virtual SkBitmap GetIcon(int row); 53 virtual void SetObserver(ui::TableModelObserver* observer); 54 virtual void GetGroupRangeForItem(int item, views::GroupRange* range); 55 56 private: 57 typedef std::vector<TabContents*> TabContentsVector; 58 TabContentsVector tab_contentses_; 59 60 ui::TableModelObserver* observer_; 61 62 DISALLOW_COPY_AND_ASSIGN(HungPagesTableModel); 63}; 64 65/////////////////////////////////////////////////////////////////////////////// 66// HungPagesTableModel, public: 67 68HungPagesTableModel::HungPagesTableModel() : observer_(NULL) { 69} 70 71HungPagesTableModel::~HungPagesTableModel() { 72} 73 74void HungPagesTableModel::InitForTabContents(TabContents* hung_contents) { 75 tab_contentses_.clear(); 76 for (TabContentsIterator it; !it.done(); ++it) { 77 if (it->GetRenderProcessHost() == hung_contents->GetRenderProcessHost()) 78 tab_contentses_.push_back(*it); 79 } 80 // The world is different. 81 if (observer_) 82 observer_->OnModelChanged(); 83} 84 85/////////////////////////////////////////////////////////////////////////////// 86// HungPagesTableModel, views::GroupTableModel implementation: 87 88int HungPagesTableModel::RowCount() { 89 return static_cast<int>(tab_contentses_.size()); 90} 91 92string16 HungPagesTableModel::GetText(int row, int column_id) { 93 DCHECK(row >= 0 && row < RowCount()); 94 string16 title = tab_contentses_[row]->GetTitle(); 95 if (title.empty()) 96 title = TabContents::GetDefaultTitle(); 97 // TODO(xji): Consider adding a special case if the title text is a URL, 98 // since those should always have LTR directionality. Please refer to 99 // http://crbug.com/6726 for more information. 100 base::i18n::AdjustStringForLocaleDirection(&title); 101 return title; 102} 103 104SkBitmap HungPagesTableModel::GetIcon(int row) { 105 DCHECK(row >= 0 && row < RowCount()); 106 return tab_contentses_.at(row)->GetFavIcon(); 107} 108 109void HungPagesTableModel::SetObserver(ui::TableModelObserver* observer) { 110 observer_ = observer; 111} 112 113void HungPagesTableModel::GetGroupRangeForItem(int item, 114 views::GroupRange* range) { 115 DCHECK(range); 116 range->start = 0; 117 range->length = RowCount(); 118} 119 120/////////////////////////////////////////////////////////////////////////////// 121// HungRendererDialogView 122 123class HungRendererDialogView : public views::View, 124 public views::DialogDelegate, 125 public views::ButtonListener { 126 public: 127 HungRendererDialogView(); 128 ~HungRendererDialogView(); 129 130 void ShowForTabContents(TabContents* contents); 131 void EndForTabContents(TabContents* contents); 132 133 // views::WindowDelegate overrides: 134 virtual std::wstring GetWindowTitle() const; 135 virtual void WindowClosing(); 136 virtual int GetDialogButtons() const; 137 virtual std::wstring GetDialogButtonLabel( 138 MessageBoxFlags::DialogButton button) const; 139 virtual views::View* GetExtraView(); 140 virtual bool Accept(bool window_closing); 141 virtual views::View* GetContentsView(); 142 143 // views::ButtonListener overrides: 144 virtual void ButtonPressed(views::Button* sender, const views::Event& event); 145 146 protected: 147 // views::View overrides: 148 virtual void ViewHierarchyChanged(bool is_add, 149 views::View* parent, 150 views::View* child); 151 152 private: 153 // Initialize the controls in this dialog. 154 void Init(); 155 void CreateKillButtonView(); 156 157 // Returns the bounds the dialog should be displayed at to be meaningfully 158 // associated with the specified TabContents. 159 gfx::Rect GetDisplayBounds(TabContents* contents); 160 161 static void InitClass(); 162 163 // Controls within the dialog box. 164 views::ImageView* frozen_icon_view_; 165 views::Label* info_label_; 166 views::GroupTableView* hung_pages_table_; 167 168 // The button we insert into the ClientView to kill the errant process. This 169 // is parented to a container view that uses a grid layout to align it 170 // properly. 171 views::NativeButton* kill_button_; 172 class ButtonContainer : public views::View { 173 public: 174 ButtonContainer() {} 175 virtual ~ButtonContainer() {} 176 private: 177 DISALLOW_COPY_AND_ASSIGN(ButtonContainer); 178 }; 179 ButtonContainer* kill_button_container_; 180 181 // The model that provides the contents of the table that shows a list of 182 // pages affected by the hang. 183 scoped_ptr<HungPagesTableModel> hung_pages_table_model_; 184 185 // The TabContents that we detected had hung in the first place resulting in 186 // the display of this view. 187 TabContents* contents_; 188 189 // Whether or not we've created controls for ourself. 190 bool initialized_; 191 192 // An amusing icon image. 193 static SkBitmap* frozen_icon_; 194 195 DISALLOW_COPY_AND_ASSIGN(HungRendererDialogView); 196}; 197 198// static 199SkBitmap* HungRendererDialogView::frozen_icon_ = NULL; 200 201// The distance in pixels from the top of the relevant contents to place the 202// warning window. 203static const int kOverlayContentsOffsetY = 50; 204 205// The dimensions of the hung pages list table view, in pixels. 206static const int kTableViewWidth = 300; 207static const int kTableViewHeight = 100; 208 209/////////////////////////////////////////////////////////////////////////////// 210// HungRendererDialogView, public: 211 212HungRendererDialogView::HungRendererDialogView() 213 : frozen_icon_view_(NULL), 214 info_label_(NULL), 215 hung_pages_table_(NULL), 216 kill_button_(NULL), 217 kill_button_container_(NULL), 218 contents_(NULL), 219 initialized_(false) { 220 InitClass(); 221} 222 223HungRendererDialogView::~HungRendererDialogView() { 224 hung_pages_table_->SetModel(NULL); 225} 226 227void HungRendererDialogView::ShowForTabContents(TabContents* contents) { 228 DCHECK(contents && window()); 229 contents_ = contents; 230 231 // Don't show the warning unless the foreground window is the frame, or this 232 // window (but still invisible). If the user has another window or 233 // application selected, activating ourselves is rude. 234 HWND frame_hwnd = GetAncestor(contents->GetNativeView(), GA_ROOT); 235 HWND foreground_window = GetForegroundWindow(); 236 if (foreground_window != frame_hwnd && 237 foreground_window != window()->GetNativeWindow()) { 238 return; 239 } 240 241 if (!window()->IsActive()) { 242 volatile TabContents* passed_c = contents; 243 volatile TabContents* this_contents = contents_; 244 245 gfx::Rect bounds = GetDisplayBounds(contents); 246 window()->SetBounds(bounds, frame_hwnd); 247 248 // We only do this if the window isn't active (i.e. hasn't been shown yet, 249 // or is currently shown but deactivated for another TabContents). This is 250 // because this window is a singleton, and it's possible another active 251 // renderer may hang while this one is showing, and we don't want to reset 252 // the list of hung pages for a potentially unrelated renderer while this 253 // one is showing. 254 hung_pages_table_model_->InitForTabContents(contents); 255 window()->Show(); 256 } 257} 258 259void HungRendererDialogView::EndForTabContents(TabContents* contents) { 260 DCHECK(contents); 261 if (contents_ && contents_->GetRenderProcessHost() == 262 contents->GetRenderProcessHost()) { 263 window()->Close(); 264 // Since we're closing, we no longer need this TabContents. 265 contents_ = NULL; 266 } 267} 268 269/////////////////////////////////////////////////////////////////////////////// 270// HungRendererDialogView, views::DialogDelegate implementation: 271 272std::wstring HungRendererDialogView::GetWindowTitle() const { 273 return UTF16ToWide( 274 l10n_util::GetStringUTF16(IDS_BROWSER_HANGMONITOR_RENDERER_TITLE)); 275} 276 277void HungRendererDialogView::WindowClosing() { 278 // We are going to be deleted soon, so make sure our instance is destroyed. 279 g_instance = NULL; 280} 281 282int HungRendererDialogView::GetDialogButtons() const { 283 // We specifically don't want a CANCEL button here because that code path is 284 // also called when the window is closed by the user clicking the X button in 285 // the window's titlebar, and also if we call Window::Close. Rather, we want 286 // the OK button to wait for responsiveness (and close the dialog) and our 287 // additional button (which we create) to kill the process (which will result 288 // in the dialog being destroyed). 289 return MessageBoxFlags::DIALOGBUTTON_OK; 290} 291 292std::wstring HungRendererDialogView::GetDialogButtonLabel( 293 MessageBoxFlags::DialogButton button) const { 294 if (button == MessageBoxFlags::DIALOGBUTTON_OK) 295 return UTF16ToWide( 296 l10n_util::GetStringUTF16(IDS_BROWSER_HANGMONITOR_RENDERER_WAIT)); 297 return std::wstring(); 298} 299 300views::View* HungRendererDialogView::GetExtraView() { 301 return kill_button_container_; 302} 303 304bool HungRendererDialogView::Accept(bool window_closing) { 305 // Don't do anything if we're being called only because the dialog is being 306 // destroyed and we don't supply a Cancel function... 307 if (window_closing) 308 return true; 309 310 // Start waiting again for responsiveness. 311 if (contents_ && contents_->render_view_host()) 312 contents_->render_view_host()->RestartHangMonitorTimeout(); 313 return true; 314} 315 316views::View* HungRendererDialogView::GetContentsView() { 317 return this; 318} 319 320/////////////////////////////////////////////////////////////////////////////// 321// HungRendererDialogView, views::ButtonListener implementation: 322 323void HungRendererDialogView::ButtonPressed( 324 views::Button* sender, const views::Event& event) { 325 if (sender == kill_button_) { 326 if (contents_ && contents_->GetRenderProcessHost()) { 327 // Kill the process. 328 TerminateProcess(contents_->GetRenderProcessHost()->GetHandle(), 329 ResultCodes::HUNG); 330 } 331 } 332} 333 334/////////////////////////////////////////////////////////////////////////////// 335// HungRendererDialogView, views::View overrides: 336 337void HungRendererDialogView::ViewHierarchyChanged(bool is_add, 338 views::View* parent, 339 views::View* child) { 340 if (!initialized_ && is_add && child == this && GetWidget()) 341 Init(); 342} 343 344/////////////////////////////////////////////////////////////////////////////// 345// HungRendererDialogView, private: 346 347void HungRendererDialogView::Init() { 348 frozen_icon_view_ = new views::ImageView; 349 frozen_icon_view_->SetImage(frozen_icon_); 350 351 info_label_ = new views::Label( 352 UTF16ToWide(l10n_util::GetStringUTF16(IDS_BROWSER_HANGMONITOR_RENDERER))); 353 info_label_->SetMultiLine(true); 354 info_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); 355 356 hung_pages_table_model_.reset(new HungPagesTableModel); 357 std::vector<TableColumn> columns; 358 columns.push_back(TableColumn()); 359 hung_pages_table_ = new views::GroupTableView( 360 hung_pages_table_model_.get(), columns, views::ICON_AND_TEXT, true, 361 false, true, false); 362 hung_pages_table_->SetPreferredSize( 363 gfx::Size(kTableViewWidth, kTableViewHeight)); 364 365 CreateKillButtonView(); 366 367 using views::GridLayout; 368 using views::ColumnSet; 369 370 GridLayout* layout = GridLayout::CreatePanel(this); 371 SetLayoutManager(layout); 372 373 const int double_column_set_id = 0; 374 ColumnSet* column_set = layout->AddColumnSet(double_column_set_id); 375 column_set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0, 376 GridLayout::FIXED, frozen_icon_->width(), 0); 377 column_set->AddPaddingColumn( 378 0, views::kUnrelatedControlLargeHorizontalSpacing); 379 column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, 380 GridLayout::USE_PREF, 0, 0); 381 382 layout->StartRow(0, double_column_set_id); 383 layout->AddView(frozen_icon_view_, 1, 3); 384 layout->AddView(info_label_); 385 386 layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing); 387 388 layout->StartRow(0, double_column_set_id); 389 layout->SkipColumns(1); 390 layout->AddView(hung_pages_table_); 391 392 initialized_ = true; 393} 394 395void HungRendererDialogView::CreateKillButtonView() { 396 kill_button_ = new views::NativeButton(this, UTF16ToWide( 397 l10n_util::GetStringUTF16(IDS_BROWSER_HANGMONITOR_RENDERER_END))); 398 399 kill_button_container_ = new ButtonContainer; 400 401 using views::GridLayout; 402 using views::ColumnSet; 403 404 GridLayout* layout = new GridLayout(kill_button_container_); 405 kill_button_container_->SetLayoutManager(layout); 406 407 const int single_column_set_id = 0; 408 ColumnSet* column_set = layout->AddColumnSet(single_column_set_id); 409 column_set->AddPaddingColumn(0, frozen_icon_->width() + 410 kPanelHorizMargin + views::kUnrelatedControlHorizontalSpacing); 411 column_set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0, 412 GridLayout::USE_PREF, 0, 0); 413 414 layout->StartRow(0, single_column_set_id); 415 layout->AddView(kill_button_); 416} 417 418gfx::Rect HungRendererDialogView::GetDisplayBounds( 419 TabContents* contents) { 420 HWND contents_hwnd = contents->GetNativeView(); 421 RECT contents_bounds_rect; 422 GetWindowRect(contents_hwnd, &contents_bounds_rect); 423 gfx::Rect contents_bounds(contents_bounds_rect); 424 gfx::Rect window_bounds = window()->GetBounds(); 425 426 int window_x = contents_bounds.x() + 427 (contents_bounds.width() - window_bounds.width()) / 2; 428 int window_y = contents_bounds.y() + kOverlayContentsOffsetY; 429 return gfx::Rect(window_x, window_y, window_bounds.width(), 430 window_bounds.height()); 431} 432 433// static 434void HungRendererDialogView::InitClass() { 435 static bool initialized = false; 436 if (!initialized) { 437 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 438 frozen_icon_ = rb.GetBitmapNamed(IDR_FROZEN_TAB_ICON); 439 initialized = true; 440 } 441} 442 443/////////////////////////////////////////////////////////////////////////////// 444// HungRendererDialog 445 446 447static HungRendererDialogView* CreateHungRendererDialogView() { 448 HungRendererDialogView* cv = new HungRendererDialogView; 449 views::Window::CreateChromeWindow(NULL, gfx::Rect(), cv); 450 return cv; 451} 452 453namespace hung_renderer_dialog { 454 455void ShowForTabContents(TabContents* contents) { 456 if (!logging::DialogsAreSuppressed()) { 457 if (!g_instance) 458 g_instance = CreateHungRendererDialogView(); 459 g_instance->ShowForTabContents(contents); 460 } 461} 462 463// static 464void HideForTabContents(TabContents* contents) { 465 if (!logging::DialogsAreSuppressed() && g_instance) 466 g_instance->EndForTabContents(contents); 467} 468 469} // namespace hung_renderer_dialog 470 471