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. 4 5 #include <windows.h> 6 7 #include "chrome/browser/hang_monitor/hung_plugin_action.h" 8 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 "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" 18 19 namespace { 20 21 const wchar_t kGTalkPluginName[] = L"Google Talk Plugin"; 22 const int kGTalkPluginLogMinVersion = 26; // For version 2.6 and below. 23 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 35 }; 36 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; 48 } 49 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); 55 } 56 57 } // namespace 58 59 HungPluginAction::HungPluginAction() : current_hung_plugin_window_(NULL) { 60 } 61 62 HungPluginAction::~HungPluginAction() { 63 } 64 65 bool HungPluginAction::OnHungWindowDetected(HWND hung_window, 66 HWND top_level_window, 67 ActionOnHungWindow* action) { 68 if (NULL == action) { 69 return false; 70 } 71 if (!IsWindow(hung_window)) { 72 return false; 73 } 74 75 bool continue_hang_detection = true; 76 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); 81 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, 88 &plugin_name, 89 &plugin_version); 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); 96 } 97 98 if (logging::DialogsAreSuppressed()) { 99 NOTREACHED() << "Terminated a hung plugin process."; 100 *action = HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS; 101 } else { 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, 110 WM_NULL, 111 0, 112 0, 113 HungWindowResponseCallback, 114 reinterpret_cast<ULONG_PTR>(this)); 115 current_hung_plugin_window_ = hung_window; 116 if (chrome::ShowMessageBox( 117 NULL, title, message, chrome::MESSAGE_BOX_TYPE_QUESTION) == 118 chrome::MESSAGE_BOX_RESULT_YES) { 119 *action = HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS; 120 } else { 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 125 // responsive. 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) 138 } 139 } 140 current_hung_plugin_window_ = NULL; 141 } 142 } 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); 147 } 148 return continue_hang_detection; 149 } 150 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), 159 NULL); 160 } 161 } 162 163 bool HungPluginAction::GetPluginNameAndVersion(HWND plugin_window, 164 DWORD browser_process_id, 165 base::string16* plugin_name, 166 base::string16* plugin_version) { 167 DCHECK(plugin_name); 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. 176 return false; 177 } 178 if (content::PluginService::GetInstance()->GetPluginInfoFromWindow( 179 window_to_check, plugin_name, plugin_version)) { 180 return true; 181 } 182 window_to_check = GetParent(window_to_check); 183 } 184 return false; 185 } 186 187 // static 188 BOOL CALLBACK HungPluginAction::DismissMessageBox(HWND window, LPARAM ignore) { 189 base::string16 class_name = gfx::GetClassName(window); 190 // #32770 is the dialog window class which is the window class of 191 // the message box being displayed. 192 if (class_name == L"#32770") { 193 EndDialog(window, IDNO); 194 return FALSE; 195 } 196 return TRUE; 197 } 198 199 // static 200 void CALLBACK HungPluginAction::HungWindowResponseCallback(HWND target_window, 201 UINT message, 202 ULONG_PTR data, 203 LRESULT result) { 204 HungPluginAction* instance = reinterpret_cast<HungPluginAction*>(data); 205 DCHECK(NULL != instance); 206 if (NULL != instance) { 207 instance->OnWindowResponsive(target_window); 208 } 209 } 210