Home | History | Annotate | Download | only in hang_monitor
      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/base/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 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     string16 plugin_name;
     85     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 string16 title = l10n_util::GetStringUTF16(
    103           IDS_BROWSER_HANGMONITOR_TITLE);
    104       const 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(NULL, title, message,
    117           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                                                string16* plugin_name,
    166                                                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   string16 class_name = ui::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