- add sources.
[platform/framework/web/crosswalk.git] / src / chrome_frame / test / win_event_receiver.cc
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 }