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 "chrome/browser/hang_monitor/hung_window_detector.h"
6
7#include <windows.h>
8#include <atlbase.h>
9
10#include "base/logging.h"
11#include "chrome/browser/hang_monitor/hang_crash_dump_win.h"
12#include "content/public/common/result_codes.h"
13
14namespace {
15
16// How long do we wait for the terminated thread or process to die (in ms)
17static const int kTerminateTimeout = 2000;
18
19}  // namespace
20
21const wchar_t HungWindowDetector::kHungChildWindowTimeout[] =
22    L"Chrome_HungChildWindowTimeout";
23
24HungWindowDetector::HungWindowDetector(HungWindowNotification* notification)
25    : notification_(notification),
26      top_level_window_(NULL),
27      message_response_timeout_(0),
28      enumerating_(false) {
29  DCHECK(NULL != notification_);
30}
31// NOTE: It is the caller's responsibility to make sure that
32// callbacks on this object have been stopped before
33// destroying this object
34HungWindowDetector::~HungWindowDetector() {
35}
36
37bool HungWindowDetector::Initialize(HWND top_level_window,
38                                    int message_response_timeout) {
39  if (NULL ==  notification_) {
40    return false;
41  }
42  if (NULL == top_level_window) {
43    return false;
44  }
45  // It is OK to call Initialize on this object repeatedly
46  // with different top lebel HWNDs and timeout values each time.
47  // And we do not need a lock for this because we are just
48  // swapping DWORDs.
49  top_level_window_ = top_level_window;
50  message_response_timeout_ = message_response_timeout;
51  return true;
52}
53
54void HungWindowDetector::OnTick() {
55  do {
56    base::AutoLock lock(hang_detection_lock_);
57    // If we already are checking for hung windows on another thread,
58    // don't do this again.
59    if (enumerating_) {
60      return;
61    }
62    enumerating_ = true;
63  } while (false);  // To scope the AutoLock
64
65  EnumChildWindows(top_level_window_, ChildWndEnumProc,
66                   reinterpret_cast<LPARAM>(this));
67
68  // The window shouldn't be disabled unless we're showing a modal dialog.
69  // If we're not, then reenable the window.
70  if (!::IsWindowEnabled(top_level_window_) &&
71      !::GetWindow(top_level_window_, GW_ENABLEDPOPUP)) {
72    ::EnableWindow(top_level_window_, TRUE);
73  }
74
75  enumerating_ = false;
76}
77
78bool HungWindowDetector::CheckChildWindow(HWND child_window) {
79  // It can happen that the window is DOA. It specifically happens
80  // when we have just killed a plugin process and the enum is still
81  // enumerating windows from that process.
82  if (!IsWindow(child_window))  {
83    return true;
84  }
85
86  DWORD top_level_window_thread_id =
87      GetWindowThreadProcessId(top_level_window_, NULL);
88
89  DWORD child_window_process_id = 0;
90  DWORD child_window_thread_id =
91      GetWindowThreadProcessId(child_window, &child_window_process_id);
92  bool continue_hang_detection = true;
93
94  if (top_level_window_thread_id != child_window_thread_id) {
95    // The message timeout for a child window starts of with a default
96    // value specified by the message_response_timeout_ member. It is
97    // tracked by a property on the child window.
98#pragma warning(disable:4311)
99    int child_window_message_timeout =
100        reinterpret_cast<int>(GetProp(child_window, kHungChildWindowTimeout));
101#pragma warning(default:4311)
102    if (!child_window_message_timeout) {
103      child_window_message_timeout = message_response_timeout_;
104    }
105
106    DWORD_PTR result = 0;
107    if (0 == SendMessageTimeout(child_window,
108                                WM_NULL,
109                                0,
110                                0,
111                                SMTO_BLOCK,
112                                child_window_message_timeout,
113                                &result)) {
114      HungWindowNotification::ActionOnHungWindow action =
115          HungWindowNotification::HUNG_WINDOW_IGNORE;
116#pragma warning(disable:4312)
117      SetProp(child_window, kHungChildWindowTimeout,
118              reinterpret_cast<HANDLE>(child_window_message_timeout));
119#pragma warning(default:4312)
120      continue_hang_detection =
121        notification_->OnHungWindowDetected(child_window, top_level_window_,
122                                            &action);
123      // Make sure this window still a child of our top-level parent
124      if (!IsChild(top_level_window_, child_window)) {
125        return continue_hang_detection;
126      }
127
128      if (action == HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS) {
129        RemoveProp(child_window, kHungChildWindowTimeout);
130        CHandle child_process(OpenProcess(PROCESS_ALL_ACCESS,
131                                          FALSE,
132                                          child_window_process_id));
133
134        if (NULL == child_process.m_h) {
135          return continue_hang_detection;
136        }
137        // Before swinging the axe, do some sanity checks to make
138        // sure this window still belongs to the same process
139        DWORD process_id_check = 0;
140        GetWindowThreadProcessId(child_window, &process_id_check);
141        if (process_id_check !=  child_window_process_id) {
142          return continue_hang_detection;
143        }
144
145        // Before terminating the process we try collecting a dump. Which
146        // a transient thread in the child process will do for us.
147        CrashDumpAndTerminateHungChildProcess(child_process);
148        child_process.Close();
149      }
150    } else {
151      RemoveProp(child_window, kHungChildWindowTimeout);
152    }
153  }
154
155  return continue_hang_detection;
156}
157
158BOOL CALLBACK HungWindowDetector::ChildWndEnumProc(HWND child_window,
159                                                   LPARAM param) {
160  HungWindowDetector* detector_instance =
161      reinterpret_cast<HungWindowDetector*>(param);
162  if (NULL == detector_instance) {
163    NOTREACHED();
164    return FALSE;
165  }
166
167  return detector_instance->CheckChildWindow(child_window);
168}
169