Home | History | Annotate | Download | only in views
      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