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.
5 #include "chrome/browser/ui/browser_dialogs.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/favicon/favicon_tab_helper.h"
11 #include "chrome/browser/ui/gtk/gtk_util.h"
12 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
13 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
14 #include "chrome/common/logging_chrome.h"
15 #include "content/public/browser/render_process_host.h"
16 #include "content/public/browser/render_view_host.h"
17 #include "content/public/browser/web_contents.h"
18 #include "content/public/common/result_codes.h"
19 #include "grit/chromium_strings.h"
20 #include "grit/generated_resources.h"
21 #include "grit/theme_resources.h"
22 #include "third_party/skia/include/core/SkBitmap.h"
23 #include "ui/base/gtk/gtk_hig_constants.h"
24 #include "ui/base/gtk/gtk_signal.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "ui/base/resource/resource_bundle.h"
27 #include "ui/gfx/gtk_util.h"
28 #include "ui/gfx/image/image.h"
30 using content::WebContents
;
34 // A wrapper class that represents the Gtk dialog.
35 class HungRendererDialogGtk
{
37 HungRendererDialogGtk();
38 ~HungRendererDialogGtk() {}
39 void ShowForWebContents(WebContents
* hung_contents
);
41 void EndForWebContents(WebContents
* hung_contents
);
44 // Dismiss the panel if |contents_| is closed or its renderer exits.
45 class WebContentsObserverImpl
: public content::WebContentsObserver
{
47 WebContentsObserverImpl(HungRendererDialogGtk
* dialog
,
48 WebContents
* contents
)
49 : content::WebContentsObserver(contents
),
53 // content::WebContentsObserver overrides:
54 virtual void RenderProcessGone(base::TerminationStatus status
) OVERRIDE
{
57 virtual void WebContentsDestroyed(WebContents
* tab
) OVERRIDE
{
62 HungRendererDialogGtk
* dialog_
; // weak
64 DISALLOW_COPY_AND_ASSIGN(WebContentsObserverImpl
);
67 // The GtkTreeView column ids.
74 // Create the gtk dialog and add the widgets.
77 CHROMEGTK_CALLBACK_1(HungRendererDialogGtk
, void, OnResponse
, int);
81 WebContents
* contents_
;
82 scoped_ptr
<WebContentsObserverImpl
> contents_observer_
;
84 DISALLOW_COPY_AND_ASSIGN(HungRendererDialogGtk
);
87 // We only support showing one of these at a time per app.
88 HungRendererDialogGtk
* g_instance
= NULL
;
90 // The response ID for the "Kill pages" button. Anything positive should be
91 // fine (the built in GtkResponseTypes are negative numbers).
92 const int kKillPagesButtonResponse
= 1;
94 HungRendererDialogGtk::HungRendererDialogGtk()
95 : dialog_(NULL
), model_(NULL
), contents_(NULL
) {
99 void HungRendererDialogGtk::Init() {
100 dialog_
= GTK_DIALOG(gtk_dialog_new_with_buttons(
101 l10n_util::GetStringUTF8(IDS_BROWSER_HANGMONITOR_RENDERER_TITLE
).c_str(),
102 NULL
, // No parent because tabs can span multiple windows.
103 GTK_DIALOG_NO_SEPARATOR
,
104 l10n_util::GetStringUTF8(IDS_BROWSER_HANGMONITOR_RENDERER_END
).c_str(),
105 kKillPagesButtonResponse
,
106 l10n_util::GetStringUTF8(IDS_BROWSER_HANGMONITOR_RENDERER_WAIT
).c_str(),
109 gtk_dialog_set_default_response(dialog_
, GTK_RESPONSE_OK
);
110 g_signal_connect(dialog_
, "delete-event",
111 G_CALLBACK(gtk_widget_hide_on_delete
), NULL
);
112 g_signal_connect(dialog_
, "response", G_CALLBACK(OnResponseThunk
), this);
114 // We have an hbox with the frozen icon on the left. On the right,
115 // we have a vbox with the unresponsive text on top and a table of
117 // ·-----------------------------------·
118 // |·---------------------------------·|
119 // ||·----·|·------------------------·||
121 // ||·----·|| The folowing page(s).. |||
123 // || ||------------------------|||
124 // || || table of tabs |||
125 // || |·------------------------·||
126 // |·---------------------------------·|
128 // | kill button wait button|
129 // ·-----------------------------------·
130 GtkWidget
* content_area
= gtk_dialog_get_content_area(dialog_
);
131 gtk_box_set_spacing(GTK_BOX(content_area
), ui::kContentAreaSpacing
);
133 GtkWidget
* hbox
= gtk_hbox_new(FALSE
, 12);
134 gtk_box_pack_start(GTK_BOX(content_area
), hbox
, TRUE
, TRUE
, 0);
136 // Wrap the icon in a vbox so it stays top aligned.
137 GtkWidget
* icon_vbox
= gtk_vbox_new(FALSE
, 0);
138 gtk_box_pack_start(GTK_BOX(hbox
), icon_vbox
, FALSE
, FALSE
, 0);
139 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
140 GdkPixbuf
* icon_pixbuf
= rb
.GetNativeImageNamed(
141 IDR_FROZEN_TAB_ICON
).ToGdkPixbuf();
142 GtkWidget
* icon
= gtk_image_new_from_pixbuf(icon_pixbuf
);
143 gtk_box_pack_start(GTK_BOX(icon_vbox
), icon
, FALSE
, FALSE
, 0);
145 GtkWidget
* vbox
= gtk_vbox_new(FALSE
, ui::kControlSpacing
);
146 gtk_box_pack_start(GTK_BOX(hbox
), vbox
, TRUE
, TRUE
, 0);
148 GtkWidget
* text
= gtk_label_new(
149 l10n_util::GetStringUTF8(IDS_BROWSER_HANGMONITOR_RENDERER
).c_str());
150 gtk_label_set_line_wrap(GTK_LABEL(text
), TRUE
);
151 gtk_box_pack_start(GTK_BOX(vbox
), text
, FALSE
, FALSE
, 0);
153 GtkWidget
* scroll_list
= gtk_scrolled_window_new(NULL
, NULL
);
154 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_list
),
155 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
156 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll_list
),
157 GTK_SHADOW_ETCHED_IN
);
158 gtk_box_pack_start(GTK_BOX(vbox
), scroll_list
, TRUE
, TRUE
, 0);
160 // The list of hung tabs is GtkTreeView with a GtkListStore as the model.
161 model_
= gtk_list_store_new(COL_COUNT
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
);
162 GtkWidget
* tree_view
= gtk_tree_view_new_with_model(
163 GTK_TREE_MODEL(model_
));
164 g_object_unref(model_
);
165 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_view
), FALSE
);
166 GtkTreeViewColumn
* column
= gtk_tree_view_column_new();
167 GtkCellRenderer
* renderer
= gtk_cell_renderer_pixbuf_new();
168 gtk_tree_view_column_pack_start(column
, renderer
, FALSE
);
169 gtk_tree_view_column_add_attribute(column
, renderer
, "pixbuf", COL_FAVICON
);
170 renderer
= gtk_cell_renderer_text_new();
171 gtk_tree_view_column_pack_start(column
, renderer
, TRUE
);
172 gtk_tree_view_column_add_attribute(column
, renderer
, "text", COL_TITLE
);
174 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view
), column
);
175 gtk_container_add(GTK_CONTAINER(scroll_list
), tree_view
);
178 void HungRendererDialogGtk::ShowForWebContents(WebContents
* hung_contents
) {
179 DCHECK(hung_contents
&& dialog_
);
180 contents_
= hung_contents
;
181 contents_observer_
.reset(new WebContentsObserverImpl(this, contents_
));
182 gtk_list_store_clear(model_
);
184 GtkTreeIter tree_iter
;
185 for (TabContentsIterator it
; !it
.done(); it
.Next()) {
186 if (it
->GetRenderProcessHost() == hung_contents
->GetRenderProcessHost()) {
187 gtk_list_store_append(model_
, &tree_iter
);
188 std::string title
= base::UTF16ToUTF8(it
->GetTitle());
190 title
= base::UTF16ToUTF8(CoreTabHelper::GetDefaultTitle());
191 FaviconTabHelper
* favicon_tab_helper
=
192 FaviconTabHelper::FromWebContents(*it
);
193 SkBitmap favicon
= favicon_tab_helper
->GetFavicon().AsBitmap();
195 GdkPixbuf
* pixbuf
= NULL
;
196 if (favicon
.width() > 0)
197 pixbuf
= gfx::GdkPixbufFromSkBitmap(favicon
);
198 gtk_list_store_set(model_
, &tree_iter
,
200 COL_TITLE
, title
.c_str(),
203 g_object_unref(pixbuf
);
206 gtk_util::ShowDialog(GTK_WIDGET(dialog_
));
209 void HungRendererDialogGtk::Hide() {
210 gtk_widget_hide(GTK_WIDGET(dialog_
));
211 // Since we're closing, we no longer need this WebContents.
212 contents_observer_
.reset();
216 void HungRendererDialogGtk::EndForWebContents(WebContents
* contents
) {
218 if (contents_
&& contents_
->GetRenderProcessHost() ==
219 contents
->GetRenderProcessHost()) {
224 // When the user clicks a button on the dialog or closes the dialog, this
225 // callback is called.
226 void HungRendererDialogGtk::OnResponse(GtkWidget
* dialog
, int response_id
) {
227 DCHECK(g_instance
== this);
228 switch (response_id
) {
229 case kKillPagesButtonResponse
:
231 if (contents_
&& contents_
->GetRenderProcessHost()) {
232 base::KillProcess(contents_
->GetRenderProcessHost()->GetHandle(),
233 content::RESULT_CODE_HUNG
, false);
237 case GTK_RESPONSE_OK
:
238 case GTK_RESPONSE_DELETE_EVENT
:
239 // Start waiting again for responsiveness.
240 if (contents_
&& contents_
->GetRenderViewHost())
241 contents_
->GetRenderViewHost()->RestartHangMonitorTimeout();
247 gtk_widget_destroy(GTK_WIDGET(dialog_
));
256 void ShowHungRendererDialog(WebContents
* contents
) {
257 if (!logging::DialogsAreSuppressed()) {
259 g_instance
= new HungRendererDialogGtk();
260 g_instance
->ShowForWebContents(contents
);
264 void HideHungRendererDialog(WebContents
* contents
) {
265 if (!logging::DialogsAreSuppressed() && g_instance
)
266 g_instance
->EndForWebContents(contents
);
269 } // namespace chrome