| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <windows.h> |
| |
| #include "chrome/browser/hang_monitor/hung_plugin_action.h" |
| |
| #include "base/metrics/histogram.h" |
| #include "base/version.h" |
| #include "base/win/win_util.h" |
| #include "chrome/browser/ui/simple_message_box.h" |
| #include "chrome/common/logging_chrome.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "content/public/browser/plugin_service.h" |
| #include "content/public/common/webplugininfo.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/gfx/win/hwnd_util.h" |
| |
| HungPluginAction::HungPluginAction() : current_hung_plugin_window_(NULL) { |
| } |
| |
| HungPluginAction::~HungPluginAction() { |
| } |
| |
| bool HungPluginAction::OnHungWindowDetected(HWND hung_window, |
| HWND top_level_window, |
| ActionOnHungWindow* action) { |
| if (NULL == action) { |
| return false; |
| } |
| if (!IsWindow(hung_window)) { |
| return false; |
| } |
| |
| bool continue_hang_detection = true; |
| |
| DWORD hung_window_process_id = 0; |
| DWORD top_level_window_process_id = 0; |
| GetWindowThreadProcessId(hung_window, &hung_window_process_id); |
| GetWindowThreadProcessId(top_level_window, &top_level_window_process_id); |
| |
| *action = HungWindowNotification::HUNG_WINDOW_IGNORE; |
| if (top_level_window_process_id != hung_window_process_id) { |
| base::string16 plugin_name = |
| l10n_util::GetStringUTF16(IDS_UNKNOWN_PLUGIN_NAME); |
| |
| if (logging::DialogsAreSuppressed()) { |
| NOTREACHED() << "Terminated a hung plugin process."; |
| *action = HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS; |
| } else { |
| const base::string16 title = l10n_util::GetStringUTF16( |
| IDS_BROWSER_HANGMONITOR_TITLE); |
| const base::string16 message = l10n_util::GetStringFUTF16( |
| IDS_BROWSER_HANGMONITOR, plugin_name); |
| // Before displaying the message box, invoke SendMessageCallback on the |
| // hung window. If the callback ever hits, the window is not hung anymore |
| // and we can dismiss the message box. |
| SendMessageCallback(hung_window, |
| WM_NULL, |
| 0, |
| 0, |
| HungWindowResponseCallback, |
| reinterpret_cast<ULONG_PTR>(this)); |
| current_hung_plugin_window_ = hung_window; |
| if (chrome::ShowQuestionMessageBox(NULL, title, message) == |
| chrome::MESSAGE_BOX_RESULT_YES) { |
| *action = HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS; |
| } else { |
| // If the user choses to ignore the hung window warning, the |
| // message timeout for this window should be doubled. We only |
| // double the timeout property on the window if the property |
| // exists. The property is deleted if the window becomes |
| // responsive. |
| continue_hang_detection = false; |
| int child_window_message_timeout = base::win::HandleToUint32( |
| GetProp(hung_window, HungWindowDetector::kHungChildWindowTimeout)); |
| if (child_window_message_timeout) { |
| child_window_message_timeout *= 2; |
| #pragma warning(disable:4312) |
| SetProp(hung_window, HungWindowDetector::kHungChildWindowTimeout, |
| reinterpret_cast<HANDLE>(child_window_message_timeout)); |
| #pragma warning(default:4312) |
| } |
| } |
| current_hung_plugin_window_ = NULL; |
| } |
| } |
| if (HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS == *action) { |
| // Enable the top-level window just in case the plugin had been |
| // displaying a modal box that had disabled the top-level window |
| EnableWindow(top_level_window, TRUE); |
| } |
| return continue_hang_detection; |
| } |
| |
| void HungPluginAction::OnWindowResponsive(HWND window) { |
| if (window == current_hung_plugin_window_) { |
| // The message timeout for this window should fallback to the default |
| // timeout as this window is now responsive. |
| RemoveProp(window, HungWindowDetector::kHungChildWindowTimeout); |
| // The monitored plugin recovered. Let's dismiss the message box. |
| EnumThreadWindows(GetCurrentThreadId(), |
| reinterpret_cast<WNDENUMPROC>(DismissMessageBox), |
| NULL); |
| } |
| } |
| |
| // static |
| BOOL CALLBACK HungPluginAction::DismissMessageBox(HWND window, LPARAM ignore) { |
| base::string16 class_name = gfx::GetClassName(window); |
| // #32770 is the dialog window class which is the window class of |
| // the message box being displayed. |
| if (class_name == L"#32770") { |
| EndDialog(window, IDNO); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| // static |
| void CALLBACK HungPluginAction::HungWindowResponseCallback(HWND target_window, |
| UINT message, |
| ULONG_PTR data, |
| LRESULT result) { |
| HungPluginAction* instance = reinterpret_cast<HungPluginAction*>(data); |
| DCHECK(NULL != instance); |
| if (NULL != instance) { |
| instance->OnWindowResponsive(target_window); |
| } |
| } |