hung_renderer_view.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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#include "chrome/browser/ui/views/hung_renderer_view.h" 6 7#if defined(OS_WIN) && !defined(USE_AURA) 8#include <windows.h> 9#endif 10 11#include "base/i18n/rtl.h" 12#include "base/memory/scoped_vector.h" 13#include "base/process_util.h" 14#include "base/utf_string_conversions.h" 15#include "chrome/browser/favicon/favicon_tab_helper.h" 16#include "chrome/browser/platform_util.h" 17#include "chrome/browser/ui/browser_dialogs.h" 18#include "chrome/browser/ui/tab_contents/core_tab_helper.h" 19#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h" 20#include "chrome/common/chrome_constants.h" 21#include "chrome/common/logging_chrome.h" 22#include "content/public/browser/render_process_host.h" 23#include "content/public/browser/render_view_host.h" 24#include "content/public/browser/web_contents.h" 25#include "content/public/browser/web_contents_view.h" 26#include "content/public/common/result_codes.h" 27#include "grit/chromium_strings.h" 28#include "grit/generated_resources.h" 29#include "grit/theme_resources.h" 30#include "ui/base/l10n/l10n_util.h" 31#include "ui/base/resource/resource_bundle.h" 32#include "ui/gfx/canvas.h" 33#include "ui/views/controls/button/label_button.h" 34#include "ui/views/controls/image_view.h" 35#include "ui/views/controls/label.h" 36#include "ui/views/layout/grid_layout.h" 37#include "ui/views/layout/layout_constants.h" 38#include "ui/views/widget/widget.h" 39#include "ui/views/window/client_view.h" 40 41#if defined(USE_AURA) 42#include "ui/aura/window.h" 43#endif 44 45using content::WebContents; 46 47// These functions allow certain chrome platforms to override the default hung 48// renderer dialog. For e.g. Chrome on Windows 8 metro 49bool PlatformShowCustomHungRendererDialog(WebContents* contents); 50bool PlatformHideCustomHungRendererDialog(WebContents* contents); 51 52#if !defined(OS_WIN) 53bool PlatformShowCustomHungRendererDialog(WebContents* contents) { 54 return false; 55} 56 57bool PlatformHideCustomHungRendererDialog(WebContents* contents) { 58 return false; 59} 60#endif // OS_WIN 61 62HungRendererDialogView* HungRendererDialogView::g_instance_ = NULL; 63 64/////////////////////////////////////////////////////////////////////////////// 65// HungPagesTableModel, public: 66 67HungPagesTableModel::HungPagesTableModel(Delegate* delegate) 68 : observer_(NULL), 69 delegate_(delegate) { 70} 71 72HungPagesTableModel::~HungPagesTableModel() { 73} 74 75content::RenderProcessHost* HungPagesTableModel::GetRenderProcessHost() { 76 return tab_observers_.empty() ? NULL : 77 tab_observers_[0]->web_contents()->GetRenderProcessHost(); 78} 79 80content::RenderViewHost* HungPagesTableModel::GetRenderViewHost() { 81 return tab_observers_.empty() ? NULL : 82 tab_observers_[0]->web_contents()->GetRenderViewHost(); 83} 84 85void HungPagesTableModel::InitForWebContents(WebContents* hung_contents) { 86 tab_observers_.clear(); 87 if (hung_contents) { 88 // Force hung_contents to be first. 89 if (hung_contents) { 90 tab_observers_.push_back(new WebContentsObserverImpl(this, 91 hung_contents)); 92 } 93 for (TabContentsIterator it; !it.done(); it.Next()) { 94 if (*it != hung_contents && 95 it->GetRenderProcessHost() == hung_contents->GetRenderProcessHost()) 96 tab_observers_.push_back(new WebContentsObserverImpl(this, *it)); 97 } 98 } 99 // The world is different. 100 if (observer_) 101 observer_->OnModelChanged(); 102} 103 104/////////////////////////////////////////////////////////////////////////////// 105// HungPagesTableModel, ui::TableModel implementation: 106 107int HungPagesTableModel::RowCount() { 108 return static_cast<int>(tab_observers_.size()); 109} 110 111string16 HungPagesTableModel::GetText(int row, int column_id) { 112 DCHECK(row >= 0 && row < RowCount()); 113 string16 title = tab_observers_[row]->web_contents()->GetTitle(); 114 if (title.empty()) 115 title = CoreTabHelper::GetDefaultTitle(); 116 // TODO(xji): Consider adding a special case if the title text is a URL, 117 // since those should always have LTR directionality. Please refer to 118 // http://crbug.com/6726 for more information. 119 base::i18n::AdjustStringForLocaleDirection(&title); 120 return title; 121} 122 123gfx::ImageSkia HungPagesTableModel::GetIcon(int row) { 124 DCHECK(row >= 0 && row < RowCount()); 125 return FaviconTabHelper::FromWebContents( 126 tab_observers_[row]->web_contents())->GetFavicon().AsImageSkia(); 127} 128 129void HungPagesTableModel::SetObserver(ui::TableModelObserver* observer) { 130 observer_ = observer; 131} 132 133void HungPagesTableModel::GetGroupRange(int model_index, 134 views::GroupRange* range) { 135 DCHECK(range); 136 range->start = 0; 137 range->length = RowCount(); 138} 139 140void HungPagesTableModel::TabDestroyed(WebContentsObserverImpl* tab) { 141 // Clean up tab_observers_ and notify our observer. 142 TabObservers::iterator i = std::find( 143 tab_observers_.begin(), tab_observers_.end(), tab); 144 DCHECK(i != tab_observers_.end()); 145 int index = static_cast<int>(i - tab_observers_.begin()); 146 tab_observers_.erase(i); 147 if (observer_) 148 observer_->OnItemsRemoved(index, 1); 149 150 // Notify the delegate. 151 delegate_->TabDestroyed(); 152 // WARNING: we've likely been deleted. 153} 154 155HungPagesTableModel::WebContentsObserverImpl::WebContentsObserverImpl( 156 HungPagesTableModel* model, WebContents* tab) 157 : content::WebContentsObserver(tab), 158 model_(model) { 159} 160 161void HungPagesTableModel::WebContentsObserverImpl::RenderViewGone( 162 base::TerminationStatus status) { 163 model_->TabDestroyed(this); 164} 165 166void HungPagesTableModel::WebContentsObserverImpl::WebContentsDestroyed( 167 WebContents* tab) { 168 model_->TabDestroyed(this); 169} 170 171/////////////////////////////////////////////////////////////////////////////// 172// HungRendererDialogView 173 174// static 175gfx::ImageSkia* HungRendererDialogView::frozen_icon_ = NULL; 176 177// The distance in pixels from the top of the relevant contents to place the 178// warning window. 179static const int kOverlayContentsOffsetY = 50; 180 181// The dimensions of the hung pages list table view, in pixels. 182static const int kTableViewWidth = 300; 183static const int kTableViewHeight = 100; 184 185// Padding space in pixels between frozen icon to the info label, hung pages 186// list table view and the Kill pages button. 187static const int kCentralColumnPadding = 188 views::kUnrelatedControlLargeHorizontalSpacing; 189 190/////////////////////////////////////////////////////////////////////////////// 191// HungRendererDialogView, public: 192 193// static 194HungRendererDialogView* HungRendererDialogView::Create( 195 gfx::NativeView context) { 196 if (!g_instance_) { 197 g_instance_ = new HungRendererDialogView; 198 views::DialogDelegate::CreateDialogWidget(g_instance_, context, NULL); 199 } 200 return g_instance_; 201} 202 203// static 204HungRendererDialogView* HungRendererDialogView::GetInstance() { 205 return g_instance_; 206} 207 208// static 209bool HungRendererDialogView::IsFrameActive(WebContents* contents) { 210 gfx::NativeView frame_view = 211 platform_util::GetTopLevel(contents->GetView()->GetNativeView()); 212 return platform_util::IsWindowActive(frame_view); 213} 214 215#if !defined(OS_WIN) 216// static 217void HungRendererDialogView::KillRendererProcess( 218 base::ProcessHandle process_handle) { 219 base::KillProcess(process_handle, content::RESULT_CODE_HUNG, false); 220} 221#endif // OS_WIN 222 223 224HungRendererDialogView::HungRendererDialogView() 225 : hung_pages_table_(NULL), 226 kill_button_(NULL), 227 initialized_(false) { 228 InitClass(); 229} 230 231HungRendererDialogView::~HungRendererDialogView() { 232 hung_pages_table_->SetModel(NULL); 233} 234 235void HungRendererDialogView::ShowForWebContents(WebContents* contents) { 236 DCHECK(contents && GetWidget()); 237 238 // Don't show the warning unless the foreground window is the frame, or this 239 // window (but still invisible). If the user has another window or 240 // application selected, activating ourselves is rude. 241 if (!IsFrameActive(contents) && 242 !platform_util::IsWindowActive(GetWidget()->GetNativeWindow())) 243 return; 244 245 if (!GetWidget()->IsActive()) { 246 gfx::Rect bounds = GetDisplayBounds(contents); 247 248 gfx::NativeView frame_view = 249 platform_util::GetTopLevel(contents->GetView()->GetNativeView()); 250 251 views::Widget* insert_after = 252 views::Widget::GetWidgetForNativeView(frame_view); 253 GetWidget()->SetBoundsConstrained(bounds); 254 if (insert_after) 255 GetWidget()->StackAboveWidget(insert_after); 256 257 // We only do this if the window isn't active (i.e. hasn't been shown yet, 258 // or is currently shown but deactivated for another WebContents). This is 259 // because this window is a singleton, and it's possible another active 260 // renderer may hang while this one is showing, and we don't want to reset 261 // the list of hung pages for a potentially unrelated renderer while this 262 // one is showing. 263 hung_pages_table_model_->InitForWebContents(contents); 264 GetWidget()->Show(); 265 } 266} 267 268void HungRendererDialogView::EndForWebContents(WebContents* contents) { 269 DCHECK(contents); 270 if (hung_pages_table_model_->RowCount() == 0 || 271 hung_pages_table_model_->GetRenderProcessHost() == 272 contents->GetRenderProcessHost()) { 273 GetWidget()->Close(); 274 // Close is async, make sure we drop our references to the tab immediately 275 // (it may be going away). 276 hung_pages_table_model_->InitForWebContents(NULL); 277 } 278} 279 280/////////////////////////////////////////////////////////////////////////////// 281// HungRendererDialogView, views::DialogDelegate implementation: 282 283string16 HungRendererDialogView::GetWindowTitle() const { 284 return l10n_util::GetStringUTF16(IDS_BROWSER_HANGMONITOR_RENDERER_TITLE); 285} 286 287void HungRendererDialogView::WindowClosing() { 288 // We are going to be deleted soon, so make sure our instance is destroyed. 289 g_instance_ = NULL; 290} 291 292int HungRendererDialogView::GetDialogButtons() const { 293 // We specifically don't want a CANCEL button here because that code path is 294 // also called when the window is closed by the user clicking the X button in 295 // the window's titlebar, and also if we call Window::Close. Rather, we want 296 // the OK button to wait for responsiveness (and close the dialog) and our 297 // additional button (which we create) to kill the process (which will result 298 // in the dialog being destroyed). 299 return ui::DIALOG_BUTTON_OK; 300} 301 302string16 HungRendererDialogView::GetDialogButtonLabel( 303 ui::DialogButton button) const { 304 if (button == ui::DIALOG_BUTTON_OK) 305 return l10n_util::GetStringUTF16(IDS_BROWSER_HANGMONITOR_RENDERER_WAIT); 306 return views::DialogDelegateView::GetDialogButtonLabel(button); 307} 308 309views::View* HungRendererDialogView::CreateExtraView() { 310 DCHECK(!kill_button_); 311 kill_button_ = new views::LabelButton(this, 312 l10n_util::GetStringUTF16(IDS_BROWSER_HANGMONITOR_RENDERER_END)); 313 kill_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 314 return kill_button_; 315} 316 317bool HungRendererDialogView::Accept(bool window_closing) { 318 // Don't do anything if we're being called only because the dialog is being 319 // destroyed and we don't supply a Cancel function... 320 if (window_closing) 321 return true; 322 323 // Start waiting again for responsiveness. 324 if (hung_pages_table_model_->GetRenderViewHost()) 325 hung_pages_table_model_->GetRenderViewHost()->RestartHangMonitorTimeout(); 326 return true; 327} 328 329/////////////////////////////////////////////////////////////////////////////// 330// HungRendererDialogView, views::ButtonListener implementation: 331 332void HungRendererDialogView::ButtonPressed( 333 views::Button* sender, const ui::Event& event) { 334 if (sender == kill_button_ && 335 hung_pages_table_model_->GetRenderProcessHost()) { 336 337 base::ProcessHandle process_handle = 338 hung_pages_table_model_->GetRenderProcessHost()->GetHandle(); 339 340 KillRendererProcess(process_handle); 341 } 342} 343 344/////////////////////////////////////////////////////////////////////////////// 345// HungRendererDialogView, HungPagesTableModel::Delegate overrides: 346 347void HungRendererDialogView::TabDestroyed() { 348 GetWidget()->Close(); 349} 350 351/////////////////////////////////////////////////////////////////////////////// 352// HungRendererDialogView, views::View overrides: 353 354void HungRendererDialogView::ViewHierarchyChanged( 355 const ViewHierarchyChangedDetails& details) { 356 if (!initialized_ && details.is_add && details.child == this && GetWidget()) 357 Init(); 358} 359 360/////////////////////////////////////////////////////////////////////////////// 361// HungRendererDialogView, private: 362 363void HungRendererDialogView::Init() { 364 views::ImageView* frozen_icon_view = new views::ImageView; 365 frozen_icon_view->SetImage(frozen_icon_); 366 367 views::Label* info_label = new views::Label( 368 l10n_util::GetStringUTF16(IDS_BROWSER_HANGMONITOR_RENDERER)); 369 info_label->SetMultiLine(true); 370 info_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 371 372 hung_pages_table_model_.reset(new HungPagesTableModel(this)); 373 std::vector<ui::TableColumn> columns; 374 columns.push_back(ui::TableColumn()); 375 hung_pages_table_ = new views::TableView( 376 hung_pages_table_model_.get(), columns, views::ICON_AND_TEXT, true); 377 hung_pages_table_->SetGrouper(hung_pages_table_model_.get()); 378 379 using views::GridLayout; 380 using views::ColumnSet; 381 382 GridLayout* layout = GridLayout::CreatePanel(this); 383 SetLayoutManager(layout); 384 385 const int double_column_set_id = 0; 386 ColumnSet* column_set = layout->AddColumnSet(double_column_set_id); 387 column_set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0, 388 GridLayout::FIXED, frozen_icon_->width(), 0); 389 column_set->AddPaddingColumn(0, kCentralColumnPadding); 390 column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, 391 GridLayout::USE_PREF, 0, 0); 392 393 layout->StartRow(0, double_column_set_id); 394 layout->AddView(frozen_icon_view, 1, 3); 395 // Add the label with a preferred width of 1, this way it doesn't effect the 396 // overall preferred size of the dialog. 397 layout->AddView( 398 info_label, 1, 1, GridLayout::FILL, GridLayout::LEADING, 1, 0); 399 400 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 401 402 layout->StartRow(0, double_column_set_id); 403 layout->SkipColumns(1); 404 layout->AddView(hung_pages_table_->CreateParentIfNecessary(), 1, 1, 405 views::GridLayout::FILL, 406 views::GridLayout::FILL, kTableViewWidth, kTableViewHeight); 407 408 initialized_ = true; 409} 410 411gfx::Rect HungRendererDialogView::GetDisplayBounds( 412 WebContents* contents) { 413#if defined(USE_AURA) 414 gfx::Rect contents_bounds( 415 contents->GetView()->GetNativeView()->GetBoundsInRootWindow()); 416#elif defined(OS_WIN) 417 HWND contents_hwnd = contents->GetView()->GetNativeView(); 418 RECT contents_bounds_rect; 419 GetWindowRect(contents_hwnd, &contents_bounds_rect); 420 gfx::Rect contents_bounds(contents_bounds_rect); 421#endif 422 gfx::Rect window_bounds = GetWidget()->GetWindowBoundsInScreen(); 423 424 int window_x = contents_bounds.x() + 425 (contents_bounds.width() - window_bounds.width()) / 2; 426 int window_y = contents_bounds.y() + kOverlayContentsOffsetY; 427 return gfx::Rect(window_x, window_y, window_bounds.width(), 428 window_bounds.height()); 429} 430 431// static 432void HungRendererDialogView::InitClass() { 433 static bool initialized = false; 434 if (!initialized) { 435 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 436 frozen_icon_ = rb.GetImageSkiaNamed(IDR_FROZEN_TAB_ICON); 437 initialized = true; 438 } 439} 440 441namespace chrome { 442 443void ShowHungRendererDialog(WebContents* contents) { 444 if (!logging::DialogsAreSuppressed() && 445 !PlatformShowCustomHungRendererDialog(contents)) { 446 HungRendererDialogView* view = HungRendererDialogView::Create( 447 platform_util::GetTopLevel(contents->GetView()->GetNativeView())); 448 view->ShowForWebContents(contents); 449 } 450} 451 452void HideHungRendererDialog(WebContents* contents) { 453 if (!logging::DialogsAreSuppressed() && 454 !PlatformHideCustomHungRendererDialog(contents) && 455 HungRendererDialogView::GetInstance()) 456 HungRendererDialogView::GetInstance()->EndForWebContents(contents); 457} 458 459} // namespace chrome 460