| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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 | |
| 5 #include "chrome_frame/test/win_event_receiver.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/logging.h" | |
| 9 #include "base/memory/weak_ptr.h" | |
| 10 #include "base/message_loop/message_loop.h" | |
| 11 #include "base/strings/string_util.h" | |
| 12 #include "base/win/object_watcher.h" | |
| 13 #include "chrome_frame/function_stub.h" | |
| 14 | |
| 15 // WinEventReceiver methods | |
| 16 WinEventReceiver::WinEventReceiver() | |
| 17 : listener_(NULL), | |
| 18 hook_(NULL), | |
| 19 hook_stub_(NULL) { | |
| 20 } | |
| 21 | |
| 22 WinEventReceiver::~WinEventReceiver() { | |
| 23 StopReceivingEvents(); | |
| 24 } | |
| 25 | |
| 26 void WinEventReceiver::SetListenerForEvent(WinEventListener* listener, | |
| 27 DWORD event) { | |
| 28 SetListenerForEvents(listener, event, event); | |
| 29 } | |
| 30 | |
| 31 void WinEventReceiver::SetListenerForEvents(WinEventListener* listener, | |
| 32 DWORD event_min, DWORD event_max) { | |
| 33 DCHECK(listener != NULL); | |
| 34 StopReceivingEvents(); | |
| 35 | |
| 36 listener_ = listener; | |
| 37 | |
| 38 InitializeHook(event_min, event_max); | |
| 39 } | |
| 40 | |
| 41 void WinEventReceiver::StopReceivingEvents() { | |
| 42 if (hook_) { | |
| 43 ::UnhookWinEvent(hook_); | |
| 44 hook_ = NULL; | |
| 45 FunctionStub::Destroy(hook_stub_); | |
| 46 hook_stub_ = NULL; | |
| 47 } | |
| 48 } | |
| 49 | |
| 50 bool WinEventReceiver::InitializeHook(DWORD event_min, DWORD event_max) { | |
| 51 DCHECK(hook_ == NULL); | |
| 52 DCHECK(hook_stub_ == NULL); | |
| 53 hook_stub_ = FunctionStub::Create(reinterpret_cast<uintptr_t>(this), | |
| 54 WinEventHook); | |
| 55 // Don't use WINEVENT_SKIPOWNPROCESS here because we fake generate an event | |
| 56 // in the mock IE event sink (IA2_EVENT_DOCUMENT_LOAD_COMPLETE) that we want | |
| 57 // to catch. | |
| 58 hook_ = SetWinEventHook(event_min, event_max, NULL, | |
| 59 reinterpret_cast<WINEVENTPROC>(hook_stub_->code()), 0, | |
| 60 0, WINEVENT_OUTOFCONTEXT); | |
| 61 LOG_IF(ERROR, hook_ == NULL) << "Unable to SetWinEvent hook"; | |
| 62 return hook_ != NULL; | |
| 63 } | |
| 64 | |
| 65 // static | |
| 66 void WinEventReceiver::WinEventHook(WinEventReceiver* me, HWINEVENTHOOK hook, | |
| 67 DWORD event, HWND hwnd, LONG object_id, | |
| 68 LONG child_id, DWORD event_thread_id, | |
| 69 DWORD event_time) { | |
| 70 DCHECK(me->listener_ != NULL); | |
| 71 me->listener_->OnEventReceived(event, hwnd, object_id, child_id); | |
| 72 } | |
| 73 | |
| 74 // Notifies WindowWatchdog when the process owning a given window exits. | |
| 75 // | |
| 76 // If the process terminates before its handle may be obtained, this class will | |
| 77 // still properly notifyy the WindowWatchdog. | |
| 78 // | |
| 79 // Notification is always delivered via a message loop task in the message loop | |
| 80 // that is active when the instance is constructed. | |
| 81 class WindowWatchdog::ProcessExitObserver | |
| 82 : public base::win::ObjectWatcher::Delegate { | |
| 83 public: | |
| 84 // Initiates the process watch. Will always return without notifying the | |
| 85 // watchdog. | |
| 86 ProcessExitObserver(WindowWatchdog* window_watchdog, HWND hwnd); | |
| 87 virtual ~ProcessExitObserver(); | |
| 88 | |
| 89 // base::ObjectWatcher::Delegate implementation | |
| 90 virtual void OnObjectSignaled(HANDLE process_handle); | |
| 91 | |
| 92 private: | |
| 93 WindowWatchdog* window_watchdog_; | |
| 94 HANDLE process_handle_; | |
| 95 HWND hwnd_; | |
| 96 | |
| 97 base::WeakPtrFactory<ProcessExitObserver> weak_factory_; | |
| 98 base::win::ObjectWatcher object_watcher_; | |
| 99 | |
| 100 DISALLOW_COPY_AND_ASSIGN(ProcessExitObserver); | |
| 101 }; | |
| 102 | |
| 103 WindowWatchdog::ProcessExitObserver::ProcessExitObserver( | |
| 104 WindowWatchdog* window_watchdog, HWND hwnd) | |
| 105 : window_watchdog_(window_watchdog), | |
| 106 process_handle_(NULL), | |
| 107 hwnd_(hwnd), | |
| 108 weak_factory_(this) { | |
| 109 DWORD pid = 0; | |
| 110 ::GetWindowThreadProcessId(hwnd, &pid); | |
| 111 if (pid != 0) { | |
| 112 process_handle_ = ::OpenProcess(SYNCHRONIZE, FALSE, pid); | |
| 113 } | |
| 114 | |
| 115 if (process_handle_ != NULL) { | |
| 116 object_watcher_.StartWatching(process_handle_, this); | |
| 117 } else { | |
| 118 // Process is gone, so the window must be gone too. Notify our observer! | |
| 119 base::MessageLoop::current()->PostTask( | |
| 120 FROM_HERE, base::Bind(&ProcessExitObserver::OnObjectSignaled, | |
| 121 weak_factory_.GetWeakPtr(), HANDLE(NULL))); | |
| 122 } | |
| 123 } | |
| 124 | |
| 125 WindowWatchdog::ProcessExitObserver::~ProcessExitObserver() { | |
| 126 if (process_handle_ != NULL) { | |
| 127 ::CloseHandle(process_handle_); | |
| 128 } | |
| 129 } | |
| 130 | |
| 131 void WindowWatchdog::ProcessExitObserver::OnObjectSignaled( | |
| 132 HANDLE process_handle) { | |
| 133 window_watchdog_->OnHwndProcessExited(hwnd_); | |
| 134 } | |
| 135 | |
| 136 WindowWatchdog::WindowWatchdog() {} | |
| 137 | |
| 138 void WindowWatchdog::AddObserver(WindowObserver* observer, | |
| 139 const std::string& caption_pattern, | |
| 140 const std::string& class_name_pattern) { | |
| 141 if (observers_.empty()) { | |
| 142 // SetListenerForEvents takes an event_min and event_max. | |
| 143 // EVENT_OBJECT_DESTROY, EVENT_OBJECT_SHOW, and EVENT_OBJECT_HIDE are | |
| 144 // consecutive, in that order; hence we supply only DESTROY and HIDE to | |
| 145 // denote exactly the required set. | |
| 146 win_event_receiver_.SetListenerForEvents( | |
| 147 this, EVENT_OBJECT_DESTROY, EVENT_OBJECT_HIDE); | |
| 148 } | |
| 149 | |
| 150 ObserverEntry new_entry = { | |
| 151 observer, | |
| 152 caption_pattern, | |
| 153 class_name_pattern, | |
| 154 OpenWindowList() }; | |
| 155 | |
| 156 observers_.push_back(new_entry); | |
| 157 } | |
| 158 | |
| 159 void WindowWatchdog::RemoveObserver(WindowObserver* observer) { | |
| 160 for (ObserverEntryList::iterator i = observers_.begin(); | |
| 161 i != observers_.end(); ) { | |
| 162 i = (observer == i->observer) ? observers_.erase(i) : ++i; | |
| 163 } | |
| 164 | |
| 165 if (observers_.empty()) | |
| 166 win_event_receiver_.StopReceivingEvents(); | |
| 167 } | |
| 168 | |
| 169 std::string WindowWatchdog::GetWindowCaption(HWND hwnd) { | |
| 170 std::string caption; | |
| 171 int len = ::GetWindowTextLength(hwnd) + 1; | |
| 172 if (len > 1) | |
| 173 ::GetWindowTextA(hwnd, WriteInto(&caption, len), len); | |
| 174 return caption; | |
| 175 } | |
| 176 | |
| 177 bool WindowWatchdog::MatchingWindow(const ObserverEntry& entry, | |
| 178 const std::string& caption, | |
| 179 const std::string& class_name) { | |
| 180 bool should_match_caption = !entry.caption_pattern.empty(); | |
| 181 bool should_match_class = !entry.class_name_pattern.empty(); | |
| 182 | |
| 183 if (should_match_caption && | |
| 184 MatchPattern(caption, entry.caption_pattern) && | |
| 185 !should_match_class) { | |
| 186 return true; | |
| 187 } | |
| 188 if (should_match_class && | |
| 189 MatchPattern(class_name, entry.class_name_pattern)) { | |
| 190 return true; | |
| 191 } | |
| 192 return false; | |
| 193 } | |
| 194 | |
| 195 void WindowWatchdog::HandleOnOpen(HWND hwnd) { | |
| 196 std::string caption = GetWindowCaption(hwnd); | |
| 197 char class_name[MAX_PATH] = {0}; | |
| 198 GetClassNameA(hwnd, class_name, arraysize(class_name)); | |
| 199 | |
| 200 // Instantiated only if there is at least one interested observer. Each | |
| 201 // interested observer will maintain a reference to this object, such that it | |
| 202 // is deleted when the last observer disappears. | |
| 203 linked_ptr<ProcessExitObserver> process_exit_observer; | |
| 204 | |
| 205 // Identify the interested observers and mark them as watching this HWND for | |
| 206 // close. | |
| 207 ObserverEntryList interested_observers; | |
| 208 for (ObserverEntryList::iterator entry_iter = observers_.begin(); | |
| 209 entry_iter != observers_.end(); ++entry_iter) { | |
| 210 if (MatchingWindow(*entry_iter, caption, class_name)) { | |
| 211 if (process_exit_observer == NULL) { | |
| 212 process_exit_observer.reset(new ProcessExitObserver(this, hwnd)); | |
| 213 } | |
| 214 | |
| 215 entry_iter->open_windows.push_back( | |
| 216 OpenWindowEntry(hwnd, process_exit_observer)); | |
| 217 | |
| 218 interested_observers.push_back(*entry_iter); | |
| 219 } | |
| 220 } | |
| 221 | |
| 222 // Notify the interested observers in a separate pass in case AddObserver or | |
| 223 // RemoveObserver is called as a side-effect of the notification. | |
| 224 for (ObserverEntryList::iterator entry_iter = interested_observers.begin(); | |
| 225 entry_iter != interested_observers.end(); ++entry_iter) { | |
| 226 entry_iter->observer->OnWindowOpen(hwnd); | |
| 227 } | |
| 228 } | |
| 229 | |
| 230 void WindowWatchdog::HandleOnClose(HWND hwnd) { | |
| 231 // Identify the interested observers, reaping OpenWindow entries as | |
| 232 // appropriate | |
| 233 ObserverEntryList interested_observers; | |
| 234 for (ObserverEntryList::iterator entry_iter = observers_.begin(); | |
| 235 entry_iter != observers_.end(); ++entry_iter) { | |
| 236 size_t num_open_windows = entry_iter->open_windows.size(); | |
| 237 | |
| 238 OpenWindowList::iterator window_iter = entry_iter->open_windows.begin(); | |
| 239 while (window_iter != entry_iter->open_windows.end()) { | |
| 240 if (hwnd == window_iter->first) { | |
| 241 window_iter = entry_iter->open_windows.erase(window_iter); | |
| 242 } else { | |
| 243 ++window_iter; | |
| 244 } | |
| 245 } | |
| 246 | |
| 247 if (num_open_windows != entry_iter->open_windows.size()) { | |
| 248 interested_observers.push_back(*entry_iter); | |
| 249 } | |
| 250 } | |
| 251 | |
| 252 // Notify the interested observers in a separate pass in case AddObserver or | |
| 253 // RemoveObserver is called as a side-effect of the notification. | |
| 254 for (ObserverEntryList::iterator entry_iter = interested_observers.begin(); | |
| 255 entry_iter != interested_observers.end(); ++entry_iter) { | |
| 256 entry_iter->observer->OnWindowClose(hwnd); | |
| 257 } | |
| 258 } | |
| 259 | |
| 260 void WindowWatchdog::OnEventReceived( | |
| 261 DWORD event, HWND hwnd, LONG object_id, LONG child_id) { | |
| 262 // We need to look for top level windows and a natural check is for | |
| 263 // WS_CHILD. Instead, checking for WS_CAPTION allows us to filter | |
| 264 // out other stray popups | |
| 265 if (event == EVENT_OBJECT_SHOW) { | |
| 266 HandleOnOpen(hwnd); | |
| 267 } else { | |
| 268 DCHECK(event == EVENT_OBJECT_DESTROY || event == EVENT_OBJECT_HIDE); | |
| 269 HandleOnClose(hwnd); | |
| 270 } | |
| 271 } | |
| 272 | |
| 273 void WindowWatchdog::OnHwndProcessExited(HWND hwnd) { | |
| 274 HandleOnClose(hwnd); | |
| 275 } | |
| OLD | NEW |