1 // Copyright (c) 2006-2008 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 "chrome/browser/platform_util.h" 10 #include "chrome/common/logging_chrome.h" 11 #include "grit/generated_resources.h" 12 #include "ui/base/l10n/l10n_util.h" 13 #include "ui/base/win/hwnd_util.h" 14 #include "webkit/plugins/npapi/webplugin_delegate_impl.h" 15 16 HungPluginAction::HungPluginAction() : current_hung_plugin_window_(NULL) { 17 } 18 19 HungPluginAction::~HungPluginAction() { 20 } 21 22 bool HungPluginAction::OnHungWindowDetected(HWND hung_window, 23 HWND top_level_window, 24 ActionOnHungWindow* action) { 25 if (NULL == action) { 26 return false; 27 } 28 if (!IsWindow(hung_window)) { 29 return false; 30 } 31 32 bool continue_hang_detection = true; 33 34 DWORD hung_window_process_id = 0; 35 DWORD top_level_window_process_id = 0; 36 GetWindowThreadProcessId(hung_window, &hung_window_process_id); 37 GetWindowThreadProcessId(top_level_window, &top_level_window_process_id); 38 39 *action = HungWindowNotification::HUNG_WINDOW_IGNORE; 40 if (top_level_window_process_id != hung_window_process_id) { 41 if (logging::DialogsAreSuppressed()) { 42 NOTREACHED() << "Terminated a hung plugin process."; 43 *action = HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS; 44 } else { 45 string16 plugin_name; 46 GetPluginName(hung_window, 47 top_level_window_process_id, 48 &plugin_name); 49 if (plugin_name.empty()) { 50 plugin_name = l10n_util::GetStringUTF16(IDS_UNKNOWN_PLUGIN_NAME); 51 } 52 string16 msg = l10n_util::GetStringFUTF16(IDS_BROWSER_HANGMONITOR, 53 plugin_name); 54 string16 title = l10n_util::GetStringUTF16(IDS_BROWSER_HANGMONITOR_TITLE); 55 // Before displaying the message box, invoke SendMessageCallback on the 56 // hung window. If the callback ever hits, the window is not hung anymore 57 // and we can dismiss the message box. 58 SendMessageCallback(hung_window, 59 WM_NULL, 60 0, 61 0, 62 HungWindowResponseCallback, 63 reinterpret_cast<ULONG_PTR>(this)); 64 current_hung_plugin_window_ = hung_window; 65 if (platform_util::SimpleYesNoBox(NULL, title, msg)) { 66 *action = HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS; 67 } else { 68 // If the user choses to ignore the hung window warning, the 69 // message timeout for this window should be doubled. We only 70 // double the timeout property on the window if the property 71 // exists. The property is deleted if the window becomes 72 // responsive. 73 continue_hang_detection = false; 74 #pragma warning(disable:4311) 75 int child_window_message_timeout = 76 reinterpret_cast<int>(GetProp( 77 hung_window, HungWindowDetector::kHungChildWindowTimeout)); 78 #pragma warning(default:4311) 79 if (child_window_message_timeout) { 80 child_window_message_timeout *= 2; 81 #pragma warning(disable:4312) 82 // TODO: this leaks. 83 SetProp(hung_window, HungWindowDetector::kHungChildWindowTimeout, 84 reinterpret_cast<HANDLE>(child_window_message_timeout)); 85 #pragma warning(default:4312) 86 } 87 } 88 current_hung_plugin_window_ = NULL; 89 } 90 } 91 if (HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS == *action) { 92 // Enable the top-level window just in case the plugin had been 93 // displaying a modal box that had disabled the top-level window 94 EnableWindow(top_level_window, TRUE); 95 } 96 return continue_hang_detection; 97 } 98 99 void HungPluginAction::OnWindowResponsive(HWND window) { 100 if (window == current_hung_plugin_window_) { 101 // The message timeout for this window should fallback to the default 102 // timeout as this window is now responsive. 103 RemoveProp(window, HungWindowDetector::kHungChildWindowTimeout); 104 // The monitored plugin recovered. Let's dismiss the message box. 105 EnumThreadWindows(GetCurrentThreadId(), 106 reinterpret_cast<WNDENUMPROC>(DismissMessageBox), 107 NULL); 108 } 109 } 110 111 bool HungPluginAction::GetPluginName(HWND plugin_window, 112 DWORD browser_process_id, 113 std::wstring* plugin_name) { 114 DCHECK(plugin_name); 115 HWND window_to_check = plugin_window; 116 while (NULL != window_to_check) { 117 DWORD process_id = 0; 118 GetWindowThreadProcessId(window_to_check, &process_id); 119 if (process_id == browser_process_id) { 120 // If we have reached a window the that belongs to the browser process 121 // we have gone too far. 122 return false; 123 } 124 if (webkit::npapi::WebPluginDelegateImpl::GetPluginNameFromWindow( 125 window_to_check, plugin_name)) { 126 return true; 127 } 128 window_to_check = GetParent(window_to_check); 129 } 130 return false; 131 } 132 133 // static 134 BOOL CALLBACK HungPluginAction::DismissMessageBox(HWND window, LPARAM ignore) { 135 string16 class_name = ui::GetClassName(window); 136 // #32770 is the dialog window class which is the window class of 137 // the message box being displayed. 138 if (class_name == L"#32770") { 139 EndDialog(window, IDNO); 140 return FALSE; 141 } 142 return TRUE; 143 } 144 145 // static 146 void CALLBACK HungPluginAction::HungWindowResponseCallback(HWND target_window, 147 UINT message, 148 ULONG_PTR data, 149 LRESULT result) { 150 HungPluginAction* instance = reinterpret_cast<HungPluginAction*>(data); 151 DCHECK(NULL != instance); 152 if (NULL != instance) { 153 instance->OnWindowResponsive(target_window); 154 } 155 } 156