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