summaryrefslogtreecommitdiffstats
path: root/components/browser_watcher/window_hang_monitor_win.cc
blob: a3ca17b097e034467f06065af61e2b9ffa2bea1c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// Copyright 2015 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 "components/browser_watcher/window_hang_monitor_win.h"

#include "base/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/win/message_window.h"

namespace browser_watcher {

namespace {

HWND FindNamedWindowForProcess(const base::string16 name, base::ProcessId pid) {
  HWND candidate = base::win::MessageWindow::FindWindow(name);
  if (candidate) {
    DWORD actual_process_id = 0;
    ::GetWindowThreadProcessId(candidate, &actual_process_id);
    if (actual_process_id == pid)
      return candidate;
  }
  return nullptr;
}

}  // namespace

WindowHangMonitor::WindowHangMonitor(base::TimeDelta ping_interval,
                                     base::TimeDelta timeout,
                                     const WindowEventCallback& callback)
    : callback_(callback),
      outstanding_ping_(nullptr),
      timer_(false /* don't retain user task */, false /* don't repeat */),
      ping_interval_(ping_interval),
      hang_timeout_(timeout) {
}

WindowHangMonitor::~WindowHangMonitor() {
  if (outstanding_ping_) {
    // We have an outstanding ping, disable it and leak it intentionally as
    // if the callback arrives eventually, it'll cause a use-after-free.
    outstanding_ping_->monitor = nullptr;
    outstanding_ping_ = nullptr;
  }
}

void WindowHangMonitor::Initialize(base::Process process,
                                   const base::string16& window_name) {
  window_name_ = window_name;
  window_process_ = process.Pass();
  timer_.SetTaskRunner(base::MessageLoop::current()->task_runner());

  ScheduleFindWindow();
}

void WindowHangMonitor::ScheduleFindWindow() {
  // TODO(erikwright): We could reduce the polling by using WaitForInputIdle,
  // but it is hard to test (requiring a non-Console executable).
  timer_.Start(
      FROM_HERE, ping_interval_,
      base::Bind(&WindowHangMonitor::PollForWindow, base::Unretained(this)));
}

void WindowHangMonitor::PollForWindow() {
  int exit_code = 0;
  if (window_process_.WaitForExitWithTimeout(base::TimeDelta(), &exit_code)) {
    callback_.Run(WINDOW_NOT_FOUND);
    return;
  }

  HWND hwnd = FindNamedWindowForProcess(window_name_, window_process_.Pid());
  if (hwnd) {
    // Sends a ping and schedules a timeout task. Upon receiving a ping response
    // further pings will be scheduled ad infinitum. Will signal any failure now
    // or later via the callback.
    SendPing(hwnd);
  } else {
    ScheduleFindWindow();
  }
}

void CALLBACK WindowHangMonitor::OnPongReceived(HWND window,
                                                UINT msg,
                                                ULONG_PTR data,
                                                LRESULT lresult) {
  OutstandingPing* outstanding = reinterpret_cast<OutstandingPing*>(data);

  // If the monitor is still around, clear its pointer.
  if (outstanding->monitor)
    outstanding->monitor->outstanding_ping_ = nullptr;

  delete outstanding;
}

void WindowHangMonitor::SendPing(HWND hwnd) {
  // Set up all state ahead of time to allow for the possibility of the callback
  // being invoked from within SendMessageCallback.
  outstanding_ping_ = new OutstandingPing;
  outstanding_ping_->monitor = this;

  // Note that this is racy to |hwnd| having been re-assigned. If that occurs,
  // we might fail to identify the disappearance of the window with this ping.
  // This is acceptable, as the next ping should detect it.
  if (!::SendMessageCallback(hwnd, WM_NULL, 0, 0, &OnPongReceived,
                             reinterpret_cast<ULONG_PTR>(outstanding_ping_))) {
    // Message sending failed, assume the window is no longer valid,
    // issue the callback and stop the polling.
    delete outstanding_ping_;
    outstanding_ping_ = nullptr;

    callback_.Run(WINDOW_VANISHED);
    return;
  }

  // Issue the count-out callback.
  timer_.Start(FROM_HERE, hang_timeout_,
               base::Bind(&WindowHangMonitor::OnHangTimeout,
                          base::Unretained(this), hwnd));
}

void WindowHangMonitor::OnHangTimeout(HWND hwnd) {
  DCHECK(window_process_.IsValid());

  if (outstanding_ping_) {
    // The ping is still outstanding, the window is hung or has vanished.
    // Orphan the outstanding ping. If the callback arrives late, it will
    // delete it, or if the callback never arrives it'll leak.
    outstanding_ping_->monitor = NULL;
    outstanding_ping_ = NULL;

    if (hwnd !=
        FindNamedWindowForProcess(window_name_, window_process_.Pid())) {
      // The window vanished.
      callback_.Run(WINDOW_VANISHED);
    } else {
      // The window hung.
      callback_.Run(WINDOW_HUNG);
    }
  } else {
    // No ping outstanding, window is not yet hung. Schedule the next retry.
    timer_.Start(
        FROM_HERE, hang_timeout_ - ping_interval_,
        base::Bind(&WindowHangMonitor::OnRetryTimeout, base::Unretained(this)));
  }
}

void WindowHangMonitor::OnRetryTimeout() {
  DCHECK(window_process_.IsValid());
  DCHECK(!outstanding_ping_);
  // We can't simply hold onto the previously located HWND due to potential
  // aliasing.
  // 1. The window handle might have been re-assigned to a different window
  //    from the time we found it to the point where we query for its owning
  //    process.
  // 2. The window handle might have been re-assigned to a different process
  //    at any point after we found it.
  HWND hwnd = FindNamedWindowForProcess(window_name_, window_process_.Pid());
  if (hwnd)
    SendPing(hwnd);
  else
    callback_.Run(WINDOW_VANISHED);
}

}  // namespace browser_watcher