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