• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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