summaryrefslogtreecommitdiffstats
path: root/chrome_frame/test/win_event_receiver.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome_frame/test/win_event_receiver.cc')
-rw-r--r--chrome_frame/test/win_event_receiver.cc191
1 files changed, 165 insertions, 26 deletions
diff --git a/chrome_frame/test/win_event_receiver.cc b/chrome_frame/test/win_event_receiver.cc
index e6295cd..c3ae0b3 100644
--- a/chrome_frame/test/win_event_receiver.cc
+++ b/chrome_frame/test/win_event_receiver.cc
@@ -5,6 +5,8 @@
#include "chrome_frame/test/win_event_receiver.h"
#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/object_watcher.h"
#include "base/string_util.h"
#include "chrome_frame/function_stub.h"
@@ -68,18 +70,93 @@ void WinEventReceiver::WinEventHook(WinEventReceiver* me, HWINEVENTHOOK hook,
me->listener_->OnEventReceived(event, hwnd, object_id, child_id);
}
-// WindowWatchdog methods
+// Notifies WindowWatchdog when the process owning a given window exits.
+//
+// If the process terminates before its handle may be obtained, this class will
+// still properly notifyy the WindowWatchdog.
+//
+// Notification is always delivered via a message loop task in the message loop
+// that is active when the instance is constructed.
+class WindowWatchdog::ProcessExitObserver
+ : public base::ObjectWatcher::Delegate {
+ public:
+ // Initiates the process watch. Will always return without notifying the
+ // watchdog.
+ ProcessExitObserver(WindowWatchdog* window_watchdog, HWND hwnd);
+ virtual ~ProcessExitObserver();
+
+ // base::ObjectWatcher::Delegate implementation
+ virtual void OnObjectSignaled(HANDLE process_handle);
+
+ private:
+ WindowWatchdog* window_watchdog_;
+ HANDLE process_handle_;
+ HWND hwnd_;
+
+ ScopedRunnableMethodFactory<ProcessExitObserver> method_task_factory_;
+ base::ObjectWatcher object_watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProcessExitObserver);
+};
+
+WindowWatchdog::ProcessExitObserver::ProcessExitObserver(
+ WindowWatchdog* window_watchdog, HWND hwnd)
+ : window_watchdog_(window_watchdog),
+ process_handle_(NULL),
+ hwnd_(hwnd),
+ ALLOW_THIS_IN_INITIALIZER_LIST(method_task_factory_(this)) {
+ DWORD pid = 0;
+ ::GetWindowThreadProcessId(hwnd, &pid);
+ if (pid != 0) {
+ process_handle_ = ::OpenProcess(SYNCHRONIZE, FALSE, pid);
+ }
+
+ if (process_handle_ != NULL) {
+ object_watcher_.StartWatching(process_handle_, this);
+ } else {
+ // Process is gone, so the window must be gone too. Notify our observer!
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ method_task_factory_.NewRunnableMethod(
+ &ProcessExitObserver::OnObjectSignaled, HANDLE(NULL)));
+ }
+}
+
+WindowWatchdog::ProcessExitObserver::~ProcessExitObserver() {
+ if (process_handle_ != NULL) {
+ ::CloseHandle(process_handle_);
+ }
+}
+
+void WindowWatchdog::ProcessExitObserver::OnObjectSignaled(
+ HANDLE process_handle) {
+ window_watchdog_->OnHwndProcessExited(hwnd_);
+}
+
+WindowWatchdog::WindowWatchdog() {}
+
void WindowWatchdog::AddObserver(WindowObserver* observer,
- const std::string& window_class) {
- if (observers_.empty())
- win_event_receiver_.SetListenerForEvent(this, EVENT_OBJECT_SHOW);
+ const std::string& caption_pattern) {
+ if (observers_.empty()) {
+ // SetListenerForEvents takes an event_min and event_max.
+ // EVENT_OBJECT_DESTROY, EVENT_OBJECT_SHOW, and EVENT_OBJECT_HIDE are
+ // consecutive, in that order; hence we supply only DESTROY and HIDE to
+ // denote exactly the required set.
+ win_event_receiver_.SetListenerForEvents(
+ this, EVENT_OBJECT_DESTROY, EVENT_OBJECT_HIDE);
+ }
+
+ ObserverEntry new_entry = {
+ observer,
+ caption_pattern,
+ OpenWindowList() };
- WindowObserverEntry new_entry = { observer, window_class };
observers_.push_back(new_entry);
}
void WindowWatchdog::RemoveObserver(WindowObserver* observer) {
- for (ObserverMap::iterator i = observers_.begin(); i != observers_.end();) {
+ for (ObserverEntryList::iterator i = observers_.begin();
+ i != observers_.end(); ) {
i = (observer == i->observer) ? observers_.erase(i) : ++i;
}
@@ -87,31 +164,93 @@ void WindowWatchdog::RemoveObserver(WindowObserver* observer) {
win_event_receiver_.StopReceivingEvents();
}
-void WindowWatchdog::OnEventReceived(DWORD event, HWND hwnd, LONG object_id,
- LONG child_id) {
- // We need to look for top level windows and a natural check is for
- // WS_CHILD. Instead, checking for WS_CAPTION allows us to filter
- // out other stray popups
- if (!(WS_CAPTION & GetWindowLong(hwnd, GWL_STYLE)))
- return;
+std::string WindowWatchdog::GetWindowCaption(HWND hwnd) {
+ std::string caption;
+ int len = ::GetWindowTextLength(hwnd) + 1;
+ ::GetWindowTextA(hwnd, WriteInto(&caption, len), len);
- char class_name[MAX_PATH] = {0};
- ::GetClassNameA(hwnd, class_name, arraysize(class_name));
+ return caption;
+}
+
+void WindowWatchdog::HandleOnOpen(HWND hwnd) {
+ std::string caption = GetWindowCaption(hwnd);
+
+ // Instantiated only if there is at least one interested observer. Each
+ // interested observer will maintain a reference to this object, such that it
+ // is deleted when the last observer disappears.
+ linked_ptr<ProcessExitObserver> process_exit_observer;
+
+ // Identify the interested observers and mark them as watching this HWND for
+ // close.
+ ObserverEntryList interested_observers;
+ for (ObserverEntryList::iterator entry_iter = observers_.begin();
+ entry_iter != observers_.end(); ++entry_iter) {
+ if (MatchPattern(caption, entry_iter->caption_pattern)) {
+ if (process_exit_observer == NULL) {
+ process_exit_observer.reset(new ProcessExitObserver(this, hwnd));
+ }
- ObserverMap interested_observers;
- for (ObserverMap::iterator i = observers_.begin();
- i != observers_.end(); i++) {
- if (0 == lstrcmpA(i->window_class.c_str(), class_name)) {
- interested_observers.push_back(*i);
+ entry_iter->open_windows.push_back(
+ OpenWindowEntry(hwnd, process_exit_observer));
+
+ interested_observers.push_back(*entry_iter);
}
}
- std::string caption;
- int len = ::GetWindowTextLength(hwnd) + 1;
- ::GetWindowTextA(hwnd, WriteInto(&caption, len), len);
+ // Notify the interested observers in a separate pass in case AddObserver or
+ // RemoveObserver is called as a side-effect of the notification.
+ for (ObserverEntryList::iterator entry_iter = interested_observers.begin();
+ entry_iter != interested_observers.end(); ++entry_iter) {
+ entry_iter->observer->OnWindowOpen(hwnd);
+ }
+}
+
+void WindowWatchdog::HandleOnClose(HWND hwnd) {
+ // Identify the interested observers, reaping OpenWindow entries as
+ // appropriate
+ ObserverEntryList interested_observers;
+ for (ObserverEntryList::iterator entry_iter = observers_.begin();
+ entry_iter != observers_.end(); ++entry_iter) {
+ size_t num_open_windows = entry_iter->open_windows.size();
+
+ OpenWindowList::iterator window_iter = entry_iter->open_windows.begin();
+ while (window_iter != entry_iter->open_windows.end()) {
+ if (hwnd == window_iter->first) {
+ window_iter = entry_iter->open_windows.erase(window_iter);
+ } else {
+ ++window_iter;
+ }
+ }
- for (ObserverMap::iterator i = interested_observers.begin();
- i != interested_observers.end(); i++) {
- i->observer->OnWindowDetected(hwnd, caption);
+ if (num_open_windows != entry_iter->open_windows.size()) {
+ interested_observers.push_back(*entry_iter);
+ }
+ }
+
+ // Notify the interested observers in a separate pass in case AddObserver or
+ // RemoveObserver is called as a side-effect of the notification.
+ for (ObserverEntryList::iterator entry_iter = interested_observers.begin();
+ entry_iter != interested_observers.end(); ++entry_iter) {
+ entry_iter->observer->OnWindowClose(hwnd);
}
}
+
+void WindowWatchdog::OnEventReceived(
+ DWORD event, HWND hwnd, LONG object_id, LONG child_id) {
+ // We need to look for top level windows and a natural check is for
+ // WS_CHILD. Instead, checking for WS_CAPTION allows us to filter
+ // out other stray popups
+ if (!WS_CAPTION & GetWindowLong(hwnd, GWL_STYLE)) {
+ return;
+ }
+ if (event == EVENT_OBJECT_SHOW) {
+ HandleOnOpen(hwnd);
+ } else {
+ DCHECK(event == EVENT_OBJECT_DESTROY || event == EVENT_OBJECT_HIDE);
+ HandleOnClose(hwnd);
+ }
+}
+
+void WindowWatchdog::OnHwndProcessExited(HWND hwnd) {
+ HandleOnClose(hwnd);
+}