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.
7 #include "chrome/browser/hang_monitor/hung_plugin_action.h"
9 #include "base/metrics/histogram.h"
10 #include "base/version.h"
11 #include "chrome/browser/ui/views/simple_message_box_win.h"
12 #include "chrome/common/logging_chrome.h"
13 #include "content/public/browser/plugin_service.h"
14 #include "content/public/common/webplugininfo.h"
15 #include "grit/generated_resources.h"
16 #include "ui/base/l10n/l10n_util.h"
17 #include "ui/gfx/win/hwnd_util.h"
21 const wchar_t kGTalkPluginName
[] = L
"Google Talk Plugin";
22 const int kGTalkPluginLogMinVersion
= 26; // For version 2.6 and below.
24 enum GTalkPluginLogVersion
{
25 GTALK_PLUGIN_VERSION_MIN
= 0,
26 GTALK_PLUGIN_VERSION_27
,
27 GTALK_PLUGIN_VERSION_28
,
28 GTALK_PLUGIN_VERSION_29
,
29 GTALK_PLUGIN_VERSION_30
,
30 GTALK_PLUGIN_VERSION_31
,
31 GTALK_PLUGIN_VERSION_32
,
32 GTALK_PLUGIN_VERSION_33
,
33 GTALK_PLUGIN_VERSION_34
,
34 GTALK_PLUGIN_VERSION_MAX
37 // Converts the version string of Google Talk Plugin to a version enum. The
38 // version format is "major(1 digit).minor(1 digit).sub(1 or 2 digits)",
39 // for example, "2.7.10" and "2.8.1". Converts the string to a number as
40 // 10 * major + minor - kGTalkPluginLogMinVersion.
41 GTalkPluginLogVersion
GetGTalkPluginVersion(const base::string16
& version
) {
42 int gtalk_plugin_version
= GTALK_PLUGIN_VERSION_MIN
;
43 Version plugin_version
;
44 content::WebPluginInfo::CreateVersionFromString(version
, &plugin_version
);
45 if (plugin_version
.IsValid() && plugin_version
.components().size() >= 2) {
46 gtalk_plugin_version
= 10 * plugin_version
.components()[0] +
47 plugin_version
.components()[1] - kGTalkPluginLogMinVersion
;
50 if (gtalk_plugin_version
< GTALK_PLUGIN_VERSION_MIN
)
51 return GTALK_PLUGIN_VERSION_MIN
;
52 if (gtalk_plugin_version
> GTALK_PLUGIN_VERSION_MAX
)
53 return GTALK_PLUGIN_VERSION_MAX
;
54 return static_cast<GTalkPluginLogVersion
>(gtalk_plugin_version
);
59 HungPluginAction::HungPluginAction() : current_hung_plugin_window_(NULL
) {
62 HungPluginAction::~HungPluginAction() {
65 bool HungPluginAction::OnHungWindowDetected(HWND hung_window
,
66 HWND top_level_window
,
67 ActionOnHungWindow
* action
) {
71 if (!IsWindow(hung_window
)) {
75 bool continue_hang_detection
= true;
77 DWORD hung_window_process_id
= 0;
78 DWORD top_level_window_process_id
= 0;
79 GetWindowThreadProcessId(hung_window
, &hung_window_process_id
);
80 GetWindowThreadProcessId(top_level_window
, &top_level_window_process_id
);
82 *action
= HungWindowNotification::HUNG_WINDOW_IGNORE
;
83 if (top_level_window_process_id
!= hung_window_process_id
) {
84 base::string16 plugin_name
;
85 base::string16 plugin_version
;
86 GetPluginNameAndVersion(hung_window
,
87 top_level_window_process_id
,
90 if (plugin_name
.empty()) {
91 plugin_name
= l10n_util::GetStringUTF16(IDS_UNKNOWN_PLUGIN_NAME
);
92 } else if (kGTalkPluginName
== plugin_name
) {
93 UMA_HISTOGRAM_ENUMERATION("GTalkPlugin.Hung",
94 GetGTalkPluginVersion(plugin_version
),
95 GTALK_PLUGIN_VERSION_MAX
+ 1);
98 if (logging::DialogsAreSuppressed()) {
99 NOTREACHED() << "Terminated a hung plugin process.";
100 *action
= HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS
;
102 const base::string16 title
= l10n_util::GetStringUTF16(
103 IDS_BROWSER_HANGMONITOR_TITLE
);
104 const base::string16 message
= l10n_util::GetStringFUTF16(
105 IDS_BROWSER_HANGMONITOR
, plugin_name
);
106 // Before displaying the message box, invoke SendMessageCallback on the
107 // hung window. If the callback ever hits, the window is not hung anymore
108 // and we can dismiss the message box.
109 SendMessageCallback(hung_window
,
113 HungWindowResponseCallback
,
114 reinterpret_cast<ULONG_PTR
>(this));
115 current_hung_plugin_window_
= hung_window
;
116 // We use chrome::NativeShowMessageBox instead of chrome::ShowMessageBox
117 // because the latter depends on UI-thread classes on Win Aura. See
118 // http://crbug.com/330424.
119 if (chrome::NativeShowMessageBox(
120 NULL
, title
, message
, chrome::MESSAGE_BOX_TYPE_QUESTION
) ==
121 chrome::MESSAGE_BOX_RESULT_YES
) {
122 *action
= HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS
;
124 // If the user choses to ignore the hung window warning, the
125 // message timeout for this window should be doubled. We only
126 // double the timeout property on the window if the property
127 // exists. The property is deleted if the window becomes
129 continue_hang_detection
= false;
130 #pragma warning(disable:4311)
131 int child_window_message_timeout
=
132 reinterpret_cast<int>(GetProp(
133 hung_window
, HungWindowDetector::kHungChildWindowTimeout
));
134 #pragma warning(default:4311)
135 if (child_window_message_timeout
) {
136 child_window_message_timeout
*= 2;
137 #pragma warning(disable:4312)
138 SetProp(hung_window
, HungWindowDetector::kHungChildWindowTimeout
,
139 reinterpret_cast<HANDLE
>(child_window_message_timeout
));
140 #pragma warning(default:4312)
143 current_hung_plugin_window_
= NULL
;
146 if (HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS
== *action
) {
147 // Enable the top-level window just in case the plugin had been
148 // displaying a modal box that had disabled the top-level window
149 EnableWindow(top_level_window
, TRUE
);
151 return continue_hang_detection
;
154 void HungPluginAction::OnWindowResponsive(HWND window
) {
155 if (window
== current_hung_plugin_window_
) {
156 // The message timeout for this window should fallback to the default
157 // timeout as this window is now responsive.
158 RemoveProp(window
, HungWindowDetector::kHungChildWindowTimeout
);
159 // The monitored plugin recovered. Let's dismiss the message box.
160 EnumThreadWindows(GetCurrentThreadId(),
161 reinterpret_cast<WNDENUMPROC
>(DismissMessageBox
),
166 bool HungPluginAction::GetPluginNameAndVersion(HWND plugin_window
,
167 DWORD browser_process_id
,
168 base::string16
* plugin_name
,
169 base::string16
* plugin_version
) {
171 DCHECK(plugin_version
);
172 HWND window_to_check
= plugin_window
;
173 while (NULL
!= window_to_check
) {
174 DWORD process_id
= 0;
175 GetWindowThreadProcessId(window_to_check
, &process_id
);
176 if (process_id
== browser_process_id
) {
177 // If we have reached a window the that belongs to the browser process
178 // we have gone too far.
181 if (content::PluginService::GetInstance()->GetPluginInfoFromWindow(
182 window_to_check
, plugin_name
, plugin_version
)) {
185 window_to_check
= GetParent(window_to_check
);
191 BOOL CALLBACK
HungPluginAction::DismissMessageBox(HWND window
, LPARAM ignore
) {
192 base::string16 class_name
= gfx::GetClassName(window
);
193 // #32770 is the dialog window class which is the window class of
194 // the message box being displayed.
195 if (class_name
== L
"#32770") {
196 EndDialog(window
, IDNO
);
203 void CALLBACK
HungPluginAction::HungWindowResponseCallback(HWND target_window
,
207 HungPluginAction
* instance
= reinterpret_cast<HungPluginAction
*>(data
);
208 DCHECK(NULL
!= instance
);
209 if (NULL
!= instance
) {
210 instance
->OnWindowResponsive(target_window
);