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
{
16 HWND
FindNamedWindowForProcess(const base::string16 name
, base::ProcessId pid
) {
17 HWND candidate
= base::win::MessageWindow::FindWindow(name
);
19 DWORD actual_process_id
= 0;
20 ::GetWindowThreadProcessId(candidate
, &actual_process_id
);
21 if (actual_process_id
== pid
)
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());
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).
61 FROM_HERE
, ping_interval_
,
62 base::Bind(&WindowHangMonitor::PollForWindow
, base::Unretained(this)));
65 void WindowHangMonitor::PollForWindow() {
67 if (window_process_
.WaitForExitWithTimeout(base::TimeDelta(), &exit_code
)) {
68 callback_
.Run(WINDOW_NOT_FOUND
);
72 HWND hwnd
= FindNamedWindowForProcess(window_name_
, window_process_
.Pid());
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.
83 void CALLBACK
WindowHangMonitor::OnPongReceived(HWND window
,
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;
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
);
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
;
133 FindNamedWindowForProcess(window_name_
, window_process_
.Pid())) {
134 // The window vanished.
135 callback_
.Run(WINDOW_VANISHED
);
138 callback_
.Run(WINDOW_HUNG
);
141 // No ping outstanding, window is not yet hung. Schedule the next retry.
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
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
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());
162 callback_
.Run(WINDOW_VANISHED
);
165 } // namespace browser_watcher