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.
5 #include "chrome_frame/test/win_event_receiver.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"
15 // WinEventReceiver methods
16 WinEventReceiver::WinEventReceiver()
22 WinEventReceiver::~WinEventReceiver() {
23 StopReceivingEvents();
26 void WinEventReceiver::SetListenerForEvent(WinEventListener
* listener
,
28 SetListenerForEvents(listener
, event
, event
);
31 void WinEventReceiver::SetListenerForEvents(WinEventListener
* listener
,
32 DWORD event_min
, DWORD event_max
) {
33 DCHECK(listener
!= NULL
);
34 StopReceivingEvents();
38 InitializeHook(event_min
, event_max
);
41 void WinEventReceiver::StopReceivingEvents() {
43 ::UnhookWinEvent(hook_
);
45 FunctionStub::Destroy(hook_stub_
);
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),
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
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";
66 void WinEventReceiver::WinEventHook(WinEventReceiver
* me
, HWINEVENTHOOK hook
,
67 DWORD event
, HWND hwnd
, LONG object_id
,
68 LONG child_id
, DWORD event_thread_id
,
70 DCHECK(me
->listener_
!= NULL
);
71 me
->listener_
->OnEventReceived(event
, hwnd
, object_id
, child_id
);
74 // Notifies WindowWatchdog when the process owning a given window exits.
76 // If the process terminates before its handle may be obtained, this class will
77 // still properly notifyy the WindowWatchdog.
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
{
84 // Initiates the process watch. Will always return without notifying the
86 ProcessExitObserver(WindowWatchdog
* window_watchdog
, HWND hwnd
);
87 virtual ~ProcessExitObserver();
89 // base::ObjectWatcher::Delegate implementation
90 virtual void OnObjectSignaled(HANDLE process_handle
);
93 WindowWatchdog
* window_watchdog_
;
94 HANDLE process_handle_
;
97 base::WeakPtrFactory
<ProcessExitObserver
> weak_factory_
;
98 base::win::ObjectWatcher object_watcher_
;
100 DISALLOW_COPY_AND_ASSIGN(ProcessExitObserver
);
103 WindowWatchdog::ProcessExitObserver::ProcessExitObserver(
104 WindowWatchdog
* window_watchdog
, HWND hwnd
)
105 : window_watchdog_(window_watchdog
),
106 process_handle_(NULL
),
108 weak_factory_(this) {
110 ::GetWindowThreadProcessId(hwnd
, &pid
);
112 process_handle_
= ::OpenProcess(SYNCHRONIZE
, FALSE
, pid
);
115 if (process_handle_
!= NULL
) {
116 object_watcher_
.StartWatching(process_handle_
, this);
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
)));
125 WindowWatchdog::ProcessExitObserver::~ProcessExitObserver() {
126 if (process_handle_
!= NULL
) {
127 ::CloseHandle(process_handle_
);
131 void WindowWatchdog::ProcessExitObserver::OnObjectSignaled(
132 HANDLE process_handle
) {
133 window_watchdog_
->OnHwndProcessExited(hwnd_
);
136 WindowWatchdog::WindowWatchdog() {}
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
);
150 ObserverEntry new_entry
= {
156 observers_
.push_back(new_entry
);
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
;
165 if (observers_
.empty())
166 win_event_receiver_
.StopReceivingEvents();
169 std::string
WindowWatchdog::GetWindowCaption(HWND hwnd
) {
171 int len
= ::GetWindowTextLength(hwnd
) + 1;
173 ::GetWindowTextA(hwnd
, WriteInto(&caption
, len
), len
);
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();
183 if (should_match_caption
&&
184 MatchPattern(caption
, entry
.caption_pattern
) &&
185 !should_match_class
) {
188 if (should_match_class
&&
189 MatchPattern(class_name
, entry
.class_name_pattern
)) {
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
));
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
;
205 // Identify the interested observers and mark them as watching this HWND for
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
));
215 entry_iter
->open_windows
.push_back(
216 OpenWindowEntry(hwnd
, process_exit_observer
));
218 interested_observers
.push_back(*entry_iter
);
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
);
230 void WindowWatchdog::HandleOnClose(HWND hwnd
) {
231 // Identify the interested observers, reaping OpenWindow entries as
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();
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
);
247 if (num_open_windows
!= entry_iter
->open_windows
.size()) {
248 interested_observers
.push_back(*entry_iter
);
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
);
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
) {
268 DCHECK(event
== EVENT_OBJECT_DESTROY
|| event
== EVENT_OBJECT_HIDE
);
273 void WindowWatchdog::OnHwndProcessExited(HWND hwnd
) {