blob: 82142862ab0ad00c4cb6474dd4647c88ecf825b0 [file] [log] [blame]
// 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 "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"
namespace {
const wchar_t kGTalkPluginName[] = L"Google Talk Plugin";
const int kGTalkPluginLogMinVersion = 26; // For version 2.6 and below.
enum GTalkPluginLogVersion {
GTALK_PLUGIN_VERSION_MIN = 0,
GTALK_PLUGIN_VERSION_27,
GTALK_PLUGIN_VERSION_28,
GTALK_PLUGIN_VERSION_29,
GTALK_PLUGIN_VERSION_30,
GTALK_PLUGIN_VERSION_31,
GTALK_PLUGIN_VERSION_32,
GTALK_PLUGIN_VERSION_33,
GTALK_PLUGIN_VERSION_34,
GTALK_PLUGIN_VERSION_MAX
};
// Converts the version string of Google Talk Plugin to a version enum. The
// version format is "major(1 digit).minor(1 digit).sub(1 or 2 digits)",
// for example, "2.7.10" and "2.8.1". Converts the string to a number as
// 10 * major + minor - kGTalkPluginLogMinVersion.
GTalkPluginLogVersion GetGTalkPluginVersion(const base::string16& version) {
int gtalk_plugin_version = GTALK_PLUGIN_VERSION_MIN;
Version plugin_version;
content::WebPluginInfo::CreateVersionFromString(version, &plugin_version);
if (plugin_version.IsValid() && plugin_version.components().size() >= 2) {
gtalk_plugin_version = 10 * plugin_version.components()[0] +
plugin_version.components()[1] - kGTalkPluginLogMinVersion;
}
if (gtalk_plugin_version < GTALK_PLUGIN_VERSION_MIN)
return GTALK_PLUGIN_VERSION_MIN;
if (gtalk_plugin_version > GTALK_PLUGIN_VERSION_MAX)
return GTALK_PLUGIN_VERSION_MAX;
return static_cast<GTalkPluginLogVersion>(gtalk_plugin_version);
}
} // namespace
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;
base::string16 plugin_version;
content::PluginService::GetInstance()->GetPluginInfoFromWindow(
hung_window, &plugin_name, &plugin_version);
if (plugin_name.empty()) {
plugin_name = l10n_util::GetStringUTF16(IDS_UNKNOWN_PLUGIN_NAME);
} else if (kGTalkPluginName == plugin_name) {
UMA_HISTOGRAM_ENUMERATION("GTalkPlugin.Hung",
GetGTalkPluginVersion(plugin_version),
GTALK_PLUGIN_VERSION_MAX + 1);
}
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::ShowMessageBox(
NULL, title, message, chrome::MESSAGE_BOX_TYPE_QUESTION) ==
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;
#pragma warning(disable:4311)
int child_window_message_timeout =
reinterpret_cast<int>(GetProp(
hung_window, HungWindowDetector::kHungChildWindowTimeout));
#pragma warning(default:4311)
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);
}
}