| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 #include "components/browser_watcher/window_hang_monitor_win.h" | 4 #include "components/browser_watcher/window_hang_monitor_win.h" |
| 5 | 5 |
| 6 #include "base/callback.h" | 6 #include "base/callback.h" |
| 7 #include "base/files/file_util.h" |
| 7 #include "base/location.h" | 8 #include "base/location.h" |
| 8 #include "base/logging.h" | 9 #include "base/logging.h" |
| 9 #include "base/message_loop/message_loop.h" | 10 #include "base/message_loop/message_loop.h" |
| 11 #include "base/strings/string_piece.h" |
| 12 #include "base/strings/string_util.h" |
| 10 #include "base/win/message_window.h" | 13 #include "base/win/message_window.h" |
| 11 | 14 |
| 12 namespace browser_watcher { | 15 namespace browser_watcher { |
| 13 | 16 |
| 14 namespace { | 17 namespace { |
| 15 | 18 |
| 16 HWND FindNamedWindowForProcess(const base::string16 name, base::ProcessId pid) { | 19 // Returns true if the class name for |window| equals |str|. |
| 17 HWND candidate = base::win::MessageWindow::FindWindow(name); | 20 bool WindowClassNameEqualsString(HWND window, base::StringPiece16 str) { |
| 18 if (candidate) { | 21 wchar_t class_name[MAX_PATH]; |
| 22 int str_length = ::GetClassName(window, class_name, MAX_PATH); |
| 23 return str_length && str.compare(class_name) == 0; |
| 24 } |
| 25 |
| 26 // Returns true if the window text is an existing directory. Ensures that |
| 27 // |window| is the right Chrome message window to ping. This could be improved |
| 28 // by testing for a valid profile in the directory. |
| 29 bool WindowNameIsExistingDirectory(HWND window) { |
| 30 base::string16 window_name; |
| 31 int str_length = ::GetWindowText( |
| 32 window, base::WriteInto(&window_name, MAX_PATH), MAX_PATH); |
| 33 window_name.resize(str_length); |
| 34 return base::DirectoryExists(base::FilePath(window_name)); |
| 35 } |
| 36 |
| 37 // Returns the Chrome message window handle for the specified |pid| or nullptr |
| 38 // if not found. |
| 39 HWND FindChromeMessageWindow(base::ProcessId pid) { |
| 40 HWND candidate = ::FindWindowEx(HWND_MESSAGE, nullptr, nullptr, nullptr); |
| 41 while (candidate) { |
| 19 DWORD actual_process_id = 0; | 42 DWORD actual_process_id = 0; |
| 20 ::GetWindowThreadProcessId(candidate, &actual_process_id); | 43 ::GetWindowThreadProcessId(candidate, &actual_process_id); |
| 21 if (actual_process_id == pid) | 44 if (WindowClassNameEqualsString(candidate, L"Chrome_MessageWindow") && |
| 45 WindowNameIsExistingDirectory(candidate) && actual_process_id == pid) { |
| 22 return candidate; | 46 return candidate; |
| 47 } |
| 48 candidate = ::GetNextWindow(candidate, GW_HWNDNEXT); |
| 23 } | 49 } |
| 24 return nullptr; | 50 return nullptr; |
| 25 } | 51 } |
| 26 | 52 |
| 27 } // namespace | 53 } // namespace |
| 28 | 54 |
| 29 WindowHangMonitor::WindowHangMonitor(base::TimeDelta ping_interval, | 55 WindowHangMonitor::WindowHangMonitor(base::TimeDelta ping_interval, |
| 30 base::TimeDelta timeout, | 56 base::TimeDelta timeout, |
| 31 const WindowEventCallback& callback) | 57 const WindowEventCallback& callback) |
| 32 : callback_(callback), | 58 : callback_(callback), |
| 33 ping_interval_(ping_interval), | 59 ping_interval_(ping_interval), |
| 34 hang_timeout_(timeout), | 60 hang_timeout_(timeout), |
| 35 timer_(false /* don't retain user task */, false /* don't repeat */), | 61 timer_(false /* don't retain user task */, false /* don't repeat */), |
| 36 outstanding_ping_(nullptr) { | 62 outstanding_ping_(nullptr) { |
| 37 } | 63 } |
| 38 | 64 |
| 39 WindowHangMonitor::~WindowHangMonitor() { | 65 WindowHangMonitor::~WindowHangMonitor() { |
| 40 if (outstanding_ping_) { | 66 if (outstanding_ping_) { |
| 41 // We have an outstanding ping, disable it and leak it intentionally as | 67 // 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. | 68 // if the callback arrives eventually, it'll cause a use-after-free. |
| 43 outstanding_ping_->monitor = nullptr; | 69 outstanding_ping_->monitor = nullptr; |
| 44 outstanding_ping_ = nullptr; | 70 outstanding_ping_ = nullptr; |
| 45 } | 71 } |
| 46 } | 72 } |
| 47 | 73 |
| 48 void WindowHangMonitor::Initialize(base::Process process, | 74 void WindowHangMonitor::Initialize(base::Process process) { |
| 49 const base::string16& window_name) { | |
| 50 window_name_ = window_name; | |
| 51 window_process_ = process.Pass(); | 75 window_process_ = process.Pass(); |
| 52 timer_.SetTaskRunner(base::MessageLoop::current()->task_runner()); | 76 timer_.SetTaskRunner(base::MessageLoop::current()->task_runner()); |
| 53 | 77 |
| 54 ScheduleFindWindow(); | 78 ScheduleFindWindow(); |
| 55 } | 79 } |
| 56 | 80 |
| 57 void WindowHangMonitor::ScheduleFindWindow() { | 81 void WindowHangMonitor::ScheduleFindWindow() { |
| 58 // TODO(erikwright): We could reduce the polling by using WaitForInputIdle, | 82 // TODO(erikwright): We could reduce the polling by using WaitForInputIdle, |
| 59 // but it is hard to test (requiring a non-Console executable). | 83 // but it is hard to test (requiring a non-Console executable). |
| 60 timer_.Start( | 84 timer_.Start( |
| 61 FROM_HERE, ping_interval_, | 85 FROM_HERE, ping_interval_, |
| 62 base::Bind(&WindowHangMonitor::PollForWindow, base::Unretained(this))); | 86 base::Bind(&WindowHangMonitor::PollForWindow, base::Unretained(this))); |
| 63 } | 87 } |
| 64 | 88 |
| 65 void WindowHangMonitor::PollForWindow() { | 89 void WindowHangMonitor::PollForWindow() { |
| 66 int exit_code = 0; | 90 int exit_code = 0; |
| 67 if (window_process_.WaitForExitWithTimeout(base::TimeDelta(), &exit_code)) { | 91 if (window_process_.WaitForExitWithTimeout(base::TimeDelta(), &exit_code)) { |
| 68 callback_.Run(WINDOW_NOT_FOUND); | 92 callback_.Run(WINDOW_NOT_FOUND); |
| 69 return; | 93 return; |
| 70 } | 94 } |
| 71 | 95 |
| 72 HWND hwnd = FindNamedWindowForProcess(window_name_, window_process_.Pid()); | 96 HWND hwnd = FindChromeMessageWindow(window_process_.Pid()); |
| 73 if (hwnd) { | 97 if (hwnd) { |
| 74 // Sends a ping and schedules a timeout task. Upon receiving a ping response | 98 // 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 | 99 // further pings will be scheduled ad infinitum. Will signal any failure now |
| 76 // or later via the callback. | 100 // or later via the callback. |
| 77 SendPing(hwnd); | 101 SendPing(hwnd); |
| 78 } else { | 102 } else { |
| 79 ScheduleFindWindow(); | 103 ScheduleFindWindow(); |
| 80 } | 104 } |
| 81 } | 105 } |
| 82 | 106 |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 114 } | 138 } |
| 115 | 139 |
| 116 // Issue the count-out callback. | 140 // Issue the count-out callback. |
| 117 timer_.Start(FROM_HERE, hang_timeout_, | 141 timer_.Start(FROM_HERE, hang_timeout_, |
| 118 base::Bind(&WindowHangMonitor::OnHangTimeout, | 142 base::Bind(&WindowHangMonitor::OnHangTimeout, |
| 119 base::Unretained(this), hwnd)); | 143 base::Unretained(this), hwnd)); |
| 120 } | 144 } |
| 121 | 145 |
| 122 void WindowHangMonitor::OnHangTimeout(HWND hwnd) { | 146 void WindowHangMonitor::OnHangTimeout(HWND hwnd) { |
| 123 DCHECK(window_process_.IsValid()); | 147 DCHECK(window_process_.IsValid()); |
| 124 | |
| 125 if (outstanding_ping_) { | 148 if (outstanding_ping_) { |
| 126 // The ping is still outstanding, the window is hung or has vanished. | 149 // The ping is still outstanding, the window is hung or has vanished. |
| 127 // Orphan the outstanding ping. If the callback arrives late, it will | 150 // Orphan the outstanding ping. If the callback arrives late, it will |
| 128 // delete it, or if the callback never arrives it'll leak. | 151 // delete it, or if the callback never arrives it'll leak. |
| 129 outstanding_ping_->monitor = NULL; | 152 outstanding_ping_->monitor = NULL; |
| 130 outstanding_ping_ = NULL; | 153 outstanding_ping_ = NULL; |
| 131 | 154 |
| 132 if (hwnd != | 155 if (hwnd != FindChromeMessageWindow(window_process_.Pid())) { |
| 133 FindNamedWindowForProcess(window_name_, window_process_.Pid())) { | |
| 134 // The window vanished. | 156 // The window vanished. |
| 135 callback_.Run(WINDOW_VANISHED); | 157 callback_.Run(WINDOW_VANISHED); |
| 136 } else { | 158 } else { |
| 137 // The window hung. | 159 // The window hung. |
| 138 callback_.Run(WINDOW_HUNG); | 160 callback_.Run(WINDOW_HUNG); |
| 139 } | 161 } |
| 140 } else { | 162 } else { |
| 141 // No ping outstanding, window is not yet hung. Schedule the next retry. | 163 // No ping outstanding, window is not yet hung. Schedule the next retry. |
| 142 timer_.Start( | 164 timer_.Start( |
| 143 FROM_HERE, hang_timeout_ - ping_interval_, | 165 FROM_HERE, hang_timeout_ - ping_interval_, |
| 144 base::Bind(&WindowHangMonitor::OnRetryTimeout, base::Unretained(this))); | 166 base::Bind(&WindowHangMonitor::OnRetryTimeout, base::Unretained(this))); |
| 145 } | 167 } |
| 146 } | 168 } |
| 147 | 169 |
| 148 void WindowHangMonitor::OnRetryTimeout() { | 170 void WindowHangMonitor::OnRetryTimeout() { |
| 149 DCHECK(window_process_.IsValid()); | 171 DCHECK(window_process_.IsValid()); |
| 172 DCHECK(window_process_.IsValid()); |
| 150 DCHECK(!outstanding_ping_); | 173 DCHECK(!outstanding_ping_); |
| 151 // We can't simply hold onto the previously located HWND due to potential | 174 // We can't simply hold onto the previously located HWND due to potential |
| 152 // aliasing. | 175 // aliasing. |
| 153 // 1. The window handle might have been re-assigned to a different window | 176 // 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 | 177 // from the time we found it to the point where we query for its owning |
| 155 // process. | 178 // process. |
| 156 // 2. The window handle might have been re-assigned to a different process | 179 // 2. The window handle might have been re-assigned to a different process |
| 157 // at any point after we found it. | 180 // at any point after we found it. |
| 158 HWND hwnd = FindNamedWindowForProcess(window_name_, window_process_.Pid()); | 181 HWND hwnd = FindChromeMessageWindow(window_process_.Pid()); |
| 159 if (hwnd) | 182 if (hwnd) { |
| 160 SendPing(hwnd); | 183 SendPing(hwnd); |
| 161 else | 184 } else { |
| 162 callback_.Run(WINDOW_VANISHED); | 185 callback_.Run(WINDOW_VANISHED); |
| 186 } |
| 163 } | 187 } |
| 164 | 188 |
| 165 } // namespace browser_watcher | 189 } // namespace browser_watcher |
| OLD | NEW |