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 |