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
16HungPluginAction::HungPluginAction() : current_hung_plugin_window_(NULL) {
17}
18
19HungPluginAction::~HungPluginAction() {
20}
21
22bool 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
99void 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
111bool 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
134BOOL 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
146void 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