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 "chrome/grit/generated_resources.h"
14 #include "content/public/browser/plugin_service.h"
15 #include "content/public/common/webplugininfo.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
;
87 content::PluginService::GetInstance()->GetPluginInfoFromWindow(
88 hung_window
, &plugin_name
, &plugin_version
);
89 if (plugin_name
.empty()) {
90 plugin_name
= l10n_util::GetStringUTF16(IDS_UNKNOWN_PLUGIN_NAME
);
91 } else if (kGTalkPluginName
== plugin_name
) {
92 UMA_HISTOGRAM_ENUMERATION("GTalkPlugin.Hung",
93 GetGTalkPluginVersion(plugin_version
),
94 GTALK_PLUGIN_VERSION_MAX
+ 1);
97 if (logging::DialogsAreSuppressed()) {
98 NOTREACHED() << "Terminated a hung plugin process.";
99 *action
= HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS
;
101 const base::string16 title
= l10n_util::GetStringUTF16(
102 IDS_BROWSER_HANGMONITOR_TITLE
);
103 const base::string16 message
= l10n_util::GetStringFUTF16(
104 IDS_BROWSER_HANGMONITOR
, plugin_name
);
105 // Before displaying the message box, invoke SendMessageCallback on the
106 // hung window. If the callback ever hits, the window is not hung anymore
107 // and we can dismiss the message box.
108 SendMessageCallback(hung_window
,
112 HungWindowResponseCallback
,
113 reinterpret_cast<ULONG_PTR
>(this));
114 current_hung_plugin_window_
= hung_window
;
115 if (chrome::ShowMessageBox(
116 NULL
, title
, message
, chrome::MESSAGE_BOX_TYPE_QUESTION
) ==
117 chrome::MESSAGE_BOX_RESULT_YES
) {
118 *action
= HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS
;
120 // If the user choses to ignore the hung window warning, the
121 // message timeout for this window should be doubled. We only
122 // double the timeout property on the window if the property
123 // exists. The property is deleted if the window becomes
125 continue_hang_detection
= false;
126 #pragma warning(disable:4311)
127 int child_window_message_timeout
=
128 reinterpret_cast<int>(GetProp(
129 hung_window
, HungWindowDetector::kHungChildWindowTimeout
));
130 #pragma warning(default:4311)
131 if (child_window_message_timeout
) {
132 child_window_message_timeout
*= 2;
133 #pragma warning(disable:4312)
134 SetProp(hung_window
, HungWindowDetector::kHungChildWindowTimeout
,
135 reinterpret_cast<HANDLE
>(child_window_message_timeout
));
136 #pragma warning(default:4312)
139 current_hung_plugin_window_
= NULL
;
142 if (HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS
== *action
) {
143 // Enable the top-level window just in case the plugin had been
144 // displaying a modal box that had disabled the top-level window
145 EnableWindow(top_level_window
, TRUE
);
147 return continue_hang_detection
;
150 void HungPluginAction::OnWindowResponsive(HWND window
) {
151 if (window
== current_hung_plugin_window_
) {
152 // The message timeout for this window should fallback to the default
153 // timeout as this window is now responsive.
154 RemoveProp(window
, HungWindowDetector::kHungChildWindowTimeout
);
155 // The monitored plugin recovered. Let's dismiss the message box.
156 EnumThreadWindows(GetCurrentThreadId(),
157 reinterpret_cast<WNDENUMPROC
>(DismissMessageBox
),
163 BOOL CALLBACK
HungPluginAction::DismissMessageBox(HWND window
, LPARAM ignore
) {
164 base::string16 class_name
= gfx::GetClassName(window
);
165 // #32770 is the dialog window class which is the window class of
166 // the message box being displayed.
167 if (class_name
== L
"#32770") {
168 EndDialog(window
, IDNO
);
175 void CALLBACK
HungPluginAction::HungWindowResponseCallback(HWND target_window
,
179 HungPluginAction
* instance
= reinterpret_cast<HungPluginAction
*>(data
);
180 DCHECK(NULL
!= instance
);
181 if (NULL
!= instance
) {
182 instance
->OnWindowResponsive(target_window
);