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
HungPagesTableModel()69 HungPagesTableModel::HungPagesTableModel() : observer_(NULL) {
70 }
71
~HungPagesTableModel()72 HungPagesTableModel::~HungPagesTableModel() {
73 }
74
InitForTabContents(TabContents * hung_contents)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
RowCount()90 int HungPagesTableModel::RowCount() {
91 return static_cast<int>(tab_contentses_.size());
92 }
93
GetText(int row,int column_id)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
GetIcon(int row)106 SkBitmap HungPagesTableModel::GetIcon(int row) {
107 DCHECK(row >= 0 && row < RowCount());
108 return tab_contentses_.at(row)->GetFavicon();
109 }
110
SetObserver(ui::TableModelObserver * observer)111 void HungPagesTableModel::SetObserver(ui::TableModelObserver* observer) {
112 observer_ = observer;
113 }
114
GetGroupRangeForItem(int item,views::GroupRange * range)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:
ButtonContainer()176 ButtonContainer() {}
~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
HungRendererDialogView()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
~HungRendererDialogView()225 HungRendererDialogView::~HungRendererDialogView() {
226 hung_pages_table_->SetModel(NULL);
227 }
228
ShowForTabContents(TabContents * contents)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
EndForTabContents(TabContents * contents)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
GetWindowTitle() const274 std::wstring HungRendererDialogView::GetWindowTitle() const {
275 return UTF16ToWide(
276 l10n_util::GetStringUTF16(IDS_BROWSER_HANGMONITOR_RENDERER_TITLE));
277 }
278
WindowClosing()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
GetDialogButtons() const284 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
GetDialogButtonLabel(MessageBoxFlags::DialogButton button) const294 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
GetExtraView()302 views::View* HungRendererDialogView::GetExtraView() {
303 return kill_button_container_;
304 }
305
Accept(bool window_closing)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
GetContentsView()318 views::View* HungRendererDialogView::GetContentsView() {
319 return this;
320 }
321
322 ///////////////////////////////////////////////////////////////////////////////
323 // HungRendererDialogView, views::ButtonListener implementation:
324
ButtonPressed(views::Button * sender,const views::Event & event)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
ViewHierarchyChanged(bool is_add,views::View * parent,views::View * child)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
Init()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
CreateKillButtonView()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
GetDisplayBounds(TabContents * contents)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
InitClass()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
CreateHungRendererDialogView()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
ShowHungRendererDialog(TabContents * contents)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
HideHungRendererDialog(TabContents * contents)461 void HideHungRendererDialog(TabContents* contents) {
462 if (!logging::DialogsAreSuppressed() && g_instance)
463 g_instance->EndForTabContents(contents);
464 }
465
466 } // namespace browser
467