Roll src/third_party/WebKit f298044:aa8346d (svn 202628:202629)
[chromium-blink-merge.git] / components / browser_watcher / window_hang_monitor_win.cc
blob964a6f76f4620a56aa2d2d4a557d1bec65b63ec5
1 // Copyright 2015 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 #include "components/browser_watcher/window_hang_monitor_win.h"
6 #include "base/callback.h"
7 #include "base/location.h"
8 #include "base/logging.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/win/message_window.h"
12 namespace browser_watcher {
14 namespace {
16 HWND FindNamedWindowForProcess(const base::string16 name, base::ProcessId pid) {
17 HWND candidate = base::win::MessageWindow::FindWindow(name);
18 if (candidate) {
19 DWORD actual_process_id = 0;
20 ::GetWindowThreadProcessId(candidate, &actual_process_id);
21 if (actual_process_id == pid)
22 return candidate;
24 return nullptr;
27 } // namespace
29 WindowHangMonitor::WindowHangMonitor(base::TimeDelta ping_interval,
30 base::TimeDelta timeout,
31 const WindowEventCallback& callback)
32 : callback_(callback),
33 ping_interval_(ping_interval),
34 hang_timeout_(timeout),
35 timer_(false /* don't retain user task */, false /* don't repeat */),
36 outstanding_ping_(nullptr) {
39 WindowHangMonitor::~WindowHangMonitor() {
40 if (outstanding_ping_) {
41 // We have an outstanding ping, disable it and leak it intentionally as
42 // if the callback arrives eventually, it'll cause a use-after-free.
43 outstanding_ping_->monitor = nullptr;
44 outstanding_ping_ = nullptr;
48 void WindowHangMonitor::Initialize(base::Process process,
49 const base::string16& window_name) {
50 window_name_ = window_name;
51 window_process_ = process.Pass();
52 timer_.SetTaskRunner(base::MessageLoop::current()->task_runner());
54 ScheduleFindWindow();
57 void WindowHangMonitor::ScheduleFindWindow() {
58 // TODO(erikwright): We could reduce the polling by using WaitForInputIdle,
59 // but it is hard to test (requiring a non-Console executable).
60 timer_.Start(
61 FROM_HERE, ping_interval_,
62 base::Bind(&WindowHangMonitor::PollForWindow, base::Unretained(this)));
65 void WindowHangMonitor::PollForWindow() {
66 int exit_code = 0;
67 if (window_process_.WaitForExitWithTimeout(base::TimeDelta(), &exit_code)) {
68 callback_.Run(WINDOW_NOT_FOUND);
69 return;
72 HWND hwnd = FindNamedWindowForProcess(window_name_, window_process_.Pid());
73 if (hwnd) {
74 // Sends a ping and schedules a timeout task. Upon receiving a ping response
75 // further pings will be scheduled ad infinitum. Will signal any failure now
76 // or later via the callback.
77 SendPing(hwnd);
78 } else {
79 ScheduleFindWindow();
83 void CALLBACK WindowHangMonitor::OnPongReceived(HWND window,
84 UINT msg,
85 ULONG_PTR data,
86 LRESULT lresult) {
87 OutstandingPing* outstanding = reinterpret_cast<OutstandingPing*>(data);
89 // If the monitor is still around, clear its pointer.
90 if (outstanding->monitor)
91 outstanding->monitor->outstanding_ping_ = nullptr;
93 delete outstanding;
96 void WindowHangMonitor::SendPing(HWND hwnd) {
97 // Set up all state ahead of time to allow for the possibility of the callback
98 // being invoked from within SendMessageCallback.
99 outstanding_ping_ = new OutstandingPing;
100 outstanding_ping_->monitor = this;
102 // Note that this is racy to |hwnd| having been re-assigned. If that occurs,
103 // we might fail to identify the disappearance of the window with this ping.
104 // This is acceptable, as the next ping should detect it.
105 if (!::SendMessageCallback(hwnd, WM_NULL, 0, 0, &OnPongReceived,
106 reinterpret_cast<ULONG_PTR>(outstanding_ping_))) {
107 // Message sending failed, assume the window is no longer valid,
108 // issue the callback and stop the polling.
109 delete outstanding_ping_;
110 outstanding_ping_ = nullptr;
112 callback_.Run(WINDOW_VANISHED);
113 return;
116 // Issue the count-out callback.
117 timer_.Start(FROM_HERE, hang_timeout_,
118 base::Bind(&WindowHangMonitor::OnHangTimeout,
119 base::Unretained(this), hwnd));
122 void WindowHangMonitor::OnHangTimeout(HWND hwnd) {
123 DCHECK(window_process_.IsValid());
125 if (outstanding_ping_) {
126 // The ping is still outstanding, the window is hung or has vanished.
127 // Orphan the outstanding ping. If the callback arrives late, it will
128 // delete it, or if the callback never arrives it'll leak.
129 outstanding_ping_->monitor = NULL;
130 outstanding_ping_ = NULL;
132 if (hwnd !=
133 FindNamedWindowForProcess(window_name_, window_process_.Pid())) {
134 // The window vanished.
135 callback_.Run(WINDOW_VANISHED);
136 } else {
137 // The window hung.
138 callback_.Run(WINDOW_HUNG);
140 } else {
141 // No ping outstanding, window is not yet hung. Schedule the next retry.
142 timer_.Start(
143 FROM_HERE, hang_timeout_ - ping_interval_,
144 base::Bind(&WindowHangMonitor::OnRetryTimeout, base::Unretained(this)));
148 void WindowHangMonitor::OnRetryTimeout() {
149 DCHECK(window_process_.IsValid());
150 DCHECK(!outstanding_ping_);
151 // We can't simply hold onto the previously located HWND due to potential
152 // aliasing.
153 // 1. The window handle might have been re-assigned to a different window
154 // from the time we found it to the point where we query for its owning
155 // process.
156 // 2. The window handle might have been re-assigned to a different process
157 // at any point after we found it.
158 HWND hwnd = FindNamedWindowForProcess(window_name_, window_process_.Pid());
159 if (hwnd)
160 SendPing(hwnd);
161 else
162 callback_.Run(WINDOW_VANISHED);
165 } // namespace browser_watcher