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/simple_message_box.h"
12 #include "chrome/common/logging_chrome.h"
13 #include "grit/generated_resources.h"
14 #include "ui/base/l10n/l10n_util.h"
15 #include "ui/base/win/hwnd_util.h"
16 #include "webkit/plugins/npapi/plugin_utils.h"
17 #include "webkit/plugins/npapi/webplugin_delegate_impl.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 string16
& version
) {
42 int gtalk_plugin_version
= GTALK_PLUGIN_VERSION_MIN
;
43 Version plugin_version
;
44 webkit::npapi::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
) {
85 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 string16 title
= l10n_util::GetStringUTF16(
103 IDS_BROWSER_HANGMONITOR_TITLE
);
104 const 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 if (chrome::ShowMessageBox(NULL
, title
, message
,
117 chrome::MESSAGE_BOX_TYPE_QUESTION
) ==
118 chrome::MESSAGE_BOX_RESULT_YES
) {
119 *action
= HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS
;
121 // If the user choses to ignore the hung window warning, the
122 // message timeout for this window should be doubled. We only
123 // double the timeout property on the window if the property
124 // exists. The property is deleted if the window becomes
126 continue_hang_detection
= false;
127 #pragma warning(disable:4311)
128 int child_window_message_timeout
=
129 reinterpret_cast<int>(GetProp(
130 hung_window
, HungWindowDetector::kHungChildWindowTimeout
));
131 #pragma warning(default:4311)
132 if (child_window_message_timeout
) {
133 child_window_message_timeout
*= 2;
134 #pragma warning(disable:4312)
135 SetProp(hung_window
, HungWindowDetector::kHungChildWindowTimeout
,
136 reinterpret_cast<HANDLE
>(child_window_message_timeout
));
137 #pragma warning(default:4312)
140 current_hung_plugin_window_
= NULL
;
143 if (HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS
== *action
) {
144 // Enable the top-level window just in case the plugin had been
145 // displaying a modal box that had disabled the top-level window
146 EnableWindow(top_level_window
, TRUE
);
148 return continue_hang_detection
;
151 void HungPluginAction::OnWindowResponsive(HWND window
) {
152 if (window
== current_hung_plugin_window_
) {
153 // The message timeout for this window should fallback to the default
154 // timeout as this window is now responsive.
155 RemoveProp(window
, HungWindowDetector::kHungChildWindowTimeout
);
156 // The monitored plugin recovered. Let's dismiss the message box.
157 EnumThreadWindows(GetCurrentThreadId(),
158 reinterpret_cast<WNDENUMPROC
>(DismissMessageBox
),
163 bool HungPluginAction::GetPluginNameAndVersion(HWND plugin_window
,
164 DWORD browser_process_id
,
165 string16
* plugin_name
,
166 string16
* plugin_version
) {
168 DCHECK(plugin_version
);
169 HWND window_to_check
= plugin_window
;
170 while (NULL
!= window_to_check
) {
171 DWORD process_id
= 0;
172 GetWindowThreadProcessId(window_to_check
, &process_id
);
173 if (process_id
== browser_process_id
) {
174 // If we have reached a window the that belongs to the browser process
175 // we have gone too far.
178 if (webkit::npapi::WebPluginDelegateImpl::GetPluginNameFromWindow(
179 window_to_check
, plugin_name
)) {
180 webkit::npapi::WebPluginDelegateImpl::GetPluginVersionFromWindow(
181 window_to_check
, plugin_version
);
184 window_to_check
= GetParent(window_to_check
);
190 BOOL CALLBACK
HungPluginAction::DismissMessageBox(HWND window
, LPARAM ignore
) {
191 string16 class_name
= ui::GetClassName(window
);
192 // #32770 is the dialog window class which is the window class of
193 // the message box being displayed.
194 if (class_name
== L
"#32770") {
195 EndDialog(window
, IDNO
);
202 void CALLBACK
HungPluginAction::HungWindowResponseCallback(HWND target_window
,
206 HungPluginAction
* instance
= reinterpret_cast<HungPluginAction
*>(data
);
207 DCHECK(NULL
!= instance
);
208 if (NULL
!= instance
) {
209 instance
->OnWindowResponsive(target_window
);