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