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