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 } 276