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 <gtk/gtk.h>
8
9 #include "base/process_util.h"
10 #include "base/utf_string_conversions.h"
11 #include "chrome/browser/ui/browser_list.h"
12 #include "chrome/browser/ui/gtk/gtk_util.h"
13 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
14 #include "chrome/common/logging_chrome.h"
15 #include "content/browser/renderer_host/render_process_host.h"
16 #include "content/browser/renderer_host/render_view_host.h"
17 #include "content/browser/tab_contents/tab_contents.h"
18 #include "content/common/result_codes.h"
19 #include "grit/chromium_strings.h"
20 #include "grit/generated_resources.h"
21 #include "grit/theme_resources.h"
22 #include "ui/base/gtk/gtk_signal.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/base/resource/resource_bundle.h"
25 #include "ui/gfx/gtk_util.h"
26
27 namespace {
28
29 // A wrapper class that represents the Gtk dialog.
30 class HungRendererDialogGtk {
31 public:
32 HungRendererDialogGtk();
33 void ShowForTabContents(TabContents* hung_contents);
34 void EndForTabContents(TabContents* hung_contents);
35
36 private:
37 // The GtkTreeView column ids.
38 enum {
39 COL_FAVICON,
40 COL_TITLE,
41 COL_COUNT,
42 };
43
44 // Create the gtk dialog and add the widgets.
45 void Init();
46
47 CHROMEGTK_CALLBACK_1(HungRendererDialogGtk, void, OnResponse, int);
48
49 GtkDialog* dialog_;
50 GtkListStore* model_;
51 TabContents* contents_;
52
53 DISALLOW_COPY_AND_ASSIGN(HungRendererDialogGtk);
54 };
55
56 // We only support showing one of these at a time per app.
57 HungRendererDialogGtk* g_instance = NULL;
58
59 // The response ID for the "Kill pages" button. Anything positive should be
60 // fine (the built in GtkResponseTypes are negative numbers).
61 const int kKillPagesButtonResponse = 1;
62
HungRendererDialogGtk()63 HungRendererDialogGtk::HungRendererDialogGtk()
64 : dialog_(NULL), model_(NULL), contents_(NULL) {
65 Init();
66 }
67
Init()68 void HungRendererDialogGtk::Init() {
69 dialog_ = GTK_DIALOG(gtk_dialog_new_with_buttons(
70 l10n_util::GetStringUTF8(IDS_BROWSER_HANGMONITOR_RENDERER_TITLE).c_str(),
71 NULL, // No parent because tabs can span multiple windows.
72 GTK_DIALOG_NO_SEPARATOR,
73 l10n_util::GetStringUTF8(IDS_BROWSER_HANGMONITOR_RENDERER_END).c_str(),
74 kKillPagesButtonResponse,
75 l10n_util::GetStringUTF8(IDS_BROWSER_HANGMONITOR_RENDERER_WAIT).c_str(),
76 GTK_RESPONSE_OK,
77 NULL));
78 gtk_dialog_set_default_response(dialog_, GTK_RESPONSE_OK);
79 g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this);
80
81 // We have an hbox with the frozen icon on the left. On the right,
82 // we have a vbox with the unresponsive text on top and a table of
83 // tabs on bottom.
84 // ·-----------------------------------·
85 // |·---------------------------------·|
86 // ||·----·|·------------------------·||
87 // |||icon||| |||
88 // ||·----·|| The folowing page(s).. |||
89 // || || |||
90 // || ||------------------------|||
91 // || || table of tabs |||
92 // || |·------------------------·||
93 // |·---------------------------------·|
94 // | |
95 // | kill button wait button|
96 // ·-----------------------------------·
97 GtkWidget* contents_vbox = dialog_->vbox;
98 gtk_box_set_spacing(GTK_BOX(contents_vbox), gtk_util::kContentAreaSpacing);
99
100 GtkWidget* hbox = gtk_hbox_new(FALSE, 12);
101 gtk_box_pack_start(GTK_BOX(contents_vbox), hbox, TRUE, TRUE, 0);
102
103 // Wrap the icon in a vbox so it stays top aligned.
104 GtkWidget* icon_vbox = gtk_vbox_new(FALSE, 0);
105 gtk_box_pack_start(GTK_BOX(hbox), icon_vbox, FALSE, FALSE, 0);
106 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
107 GdkPixbuf* icon_pixbuf = rb.GetPixbufNamed(IDR_FROZEN_TAB_ICON);
108 GtkWidget* icon = gtk_image_new_from_pixbuf(icon_pixbuf);
109 gtk_box_pack_start(GTK_BOX(icon_vbox), icon, FALSE, FALSE, 0);
110
111 GtkWidget* vbox = gtk_vbox_new(FALSE, gtk_util::kControlSpacing);
112 gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
113
114 GtkWidget* text = gtk_label_new(
115 l10n_util::GetStringUTF8(IDS_BROWSER_HANGMONITOR_RENDERER).c_str());
116 gtk_label_set_line_wrap(GTK_LABEL(text), TRUE);
117 gtk_box_pack_start(GTK_BOX(vbox), text, FALSE, FALSE, 0);
118
119 GtkWidget* scroll_list = gtk_scrolled_window_new(NULL, NULL);
120 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_list),
121 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
122 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll_list),
123 GTK_SHADOW_ETCHED_IN);
124 gtk_box_pack_start(GTK_BOX(vbox), scroll_list, TRUE, TRUE, 0);
125
126 // The list of hung tabs is GtkTreeView with a GtkListStore as the model.
127 model_ = gtk_list_store_new(COL_COUNT, GDK_TYPE_PIXBUF, G_TYPE_STRING);
128 GtkWidget* tree_view = gtk_tree_view_new_with_model(
129 GTK_TREE_MODEL(model_));
130 g_object_unref(model_);
131 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_view), FALSE);
132 GtkTreeViewColumn* column = gtk_tree_view_column_new();
133 GtkCellRenderer* renderer = gtk_cell_renderer_pixbuf_new();
134 gtk_tree_view_column_pack_start(column, renderer, FALSE);
135 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", COL_FAVICON);
136 renderer = gtk_cell_renderer_text_new();
137 gtk_tree_view_column_pack_start(column, renderer, TRUE);
138 gtk_tree_view_column_add_attribute(column, renderer, "text", COL_TITLE);
139
140 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
141 gtk_container_add(GTK_CONTAINER(scroll_list), tree_view);
142 }
143
ShowForTabContents(TabContents * hung_contents)144 void HungRendererDialogGtk::ShowForTabContents(TabContents* hung_contents) {
145 DCHECK(hung_contents && dialog_);
146 contents_ = hung_contents;
147 gtk_list_store_clear(model_);
148
149 GtkTreeIter tree_iter;
150 for (TabContentsIterator it; !it.done(); ++it) {
151 if (it->tab_contents()->GetRenderProcessHost() ==
152 hung_contents->GetRenderProcessHost()) {
153 gtk_list_store_append(model_, &tree_iter);
154 std::string title = UTF16ToUTF8(it->tab_contents()->GetTitle());
155 if (title.empty())
156 title = UTF16ToUTF8(TabContentsWrapper::GetDefaultTitle());
157 SkBitmap favicon = it->tab_contents()->GetFavicon();
158
159 GdkPixbuf* pixbuf = NULL;
160 if (favicon.width() > 0)
161 pixbuf = gfx::GdkPixbufFromSkBitmap(&favicon);
162 gtk_list_store_set(model_, &tree_iter,
163 COL_FAVICON, pixbuf,
164 COL_TITLE, title.c_str(),
165 -1);
166 if (pixbuf)
167 g_object_unref(pixbuf);
168 }
169 }
170 gtk_util::ShowDialog(GTK_WIDGET(dialog_));
171 }
172
EndForTabContents(TabContents * contents)173 void HungRendererDialogGtk::EndForTabContents(TabContents* contents) {
174 DCHECK(contents);
175 if (contents_ && contents_->GetRenderProcessHost() ==
176 contents->GetRenderProcessHost()) {
177 gtk_widget_hide(GTK_WIDGET(dialog_));
178 // Since we're closing, we no longer need this TabContents.
179 contents_ = NULL;
180 }
181 }
182
183 // When the user clicks a button on the dialog or closes the dialog, this
184 // callback is called.
OnResponse(GtkWidget * dialog,int response_id)185 void HungRendererDialogGtk::OnResponse(GtkWidget* dialog, int response_id) {
186 DCHECK(g_instance == this);
187 switch (response_id) {
188 case kKillPagesButtonResponse:
189 // Kill the process.
190 if (contents_ && contents_->GetRenderProcessHost()) {
191 base::KillProcess(contents_->GetRenderProcessHost()->GetHandle(),
192 ResultCodes::HUNG, false);
193 }
194 break;
195
196 case GTK_RESPONSE_OK:
197 case GTK_RESPONSE_DELETE_EVENT:
198 // Start waiting again for responsiveness.
199 if (contents_ && contents_->render_view_host())
200 contents_->render_view_host()->RestartHangMonitorTimeout();
201 break;
202 default:
203 NOTREACHED();
204 }
205
206 gtk_widget_destroy(GTK_WIDGET(dialog_));
207 delete g_instance;
208 g_instance = NULL;
209 }
210
211 } // namespace
212
213 namespace browser {
214
ShowHungRendererDialog(TabContents * contents)215 void ShowHungRendererDialog(TabContents* contents) {
216 if (!logging::DialogsAreSuppressed()) {
217 if (!g_instance)
218 g_instance = new HungRendererDialogGtk();
219 g_instance->ShowForTabContents(contents);
220 }
221 }
222
HideHungRendererDialog(TabContents * contents)223 void HideHungRendererDialog(TabContents* contents) {
224 if (!logging::DialogsAreSuppressed() && g_instance)
225 g_instance->EndForTabContents(contents);
226 }
227
228 } // namespace browser
229