// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome_frame/test/win_event_receiver.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/string_util.h"
#include "base/win/object_watcher.h"
#include "chrome_frame/function_stub.h"
// WinEventReceiver methods
WinEventReceiver::WinEventReceiver()
: listener_(NULL),
hook_(NULL),
hook_stub_(NULL) {
}
WinEventReceiver::~WinEventReceiver() {
StopReceivingEvents();
}
void WinEventReceiver::SetListenerForEvent(WinEventListener* listener,
DWORD event) {
SetListenerForEvents(listener, event, event);
}
void WinEventReceiver::SetListenerForEvents(WinEventListener* listener,
DWORD event_min, DWORD event_max) {
DCHECK(listener != NULL);
StopReceivingEvents();
listener_ = listener;
InitializeHook(event_min, event_max);
}
void WinEventReceiver::StopReceivingEvents() {
if (hook_) {
::UnhookWinEvent(hook_);
hook_ = NULL;
FunctionStub::Destroy(hook_stub_);
hook_stub_ = NULL;
}
}
bool WinEventReceiver::InitializeHook(DWORD event_min, DWORD event_max) {
DCHECK(hook_ == NULL);
DCHECK(hook_stub_ == NULL);
hook_stub_ = FunctionStub::Create(reinterpret_cast<uintptr_t>(this),
WinEventHook);
// Don't use WINEVENT_SKIPOWNPROCESS here because we fake generate an event
// in the mock IE event sink (IA2_EVENT_DOCUMENT_LOAD_COMPLETE) that we want
// to catch.
hook_ = SetWinEventHook(event_min, event_max, NULL,
reinterpret_cast<WINEVENTPROC>(hook_stub_->code()), 0,
0, WINEVENT_OUTOFCONTEXT);
LOG_IF(ERROR, hook_ == NULL) << "Unable to SetWinEvent hook";
return hook_ != NULL;
}
// static
void WinEventReceiver::WinEventHook(WinEventReceiver* me, HWINEVENTHOOK hook,
DWORD event, HWND hwnd, LONG object_id,
LONG child_id, DWORD event_thread_id,
DWORD event_time) {
DCHECK(me->listener_ != NULL);
me->listener_->OnEventReceived(event, hwnd, object_id, child_id);
}
// 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::win::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_;
base::WeakPtrFactory<ProcessExitObserver> weak_factory_;
base::win::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),
weak_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!
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(&ProcessExitObserver::OnObjectSignaled,
weak_factory_.GetWeakPtr(), 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& caption_pattern,
const std::string& class_name_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,
class_name_pattern,
OpenWindowList() };
observers_.push_back(new_entry);
}
void WindowWatchdog::RemoveObserver(WindowObserver* observer) {
for (ObserverEntryList::iterator i = observers_.begin();
i != observers_.end(); ) {
i = (observer == i->observer) ? observers_.erase(i) : ++i;
}
if (observers_.empty())
win_event_receiver_.StopReceivingEvents();
}
std::string WindowWatchdog::GetWindowCaption(HWND hwnd) {
std::string caption;
int len = ::GetWindowTextLength(hwnd) + 1;
if (len > 1)
::GetWindowTextA(hwnd, WriteInto(&caption, len), len);
return caption;
}
bool WindowWatchdog::MatchingWindow(const ObserverEntry& entry,
const std::string& caption,
const std::string& class_name) {
bool should_match_caption = !entry.caption_pattern.empty();
bool should_match_class = !entry.class_name_pattern.empty();
if (should_match_caption &&
MatchPattern(caption, entry.caption_pattern) &&
!should_match_class) {
return true;
}
if (should_match_class &&
MatchPattern(class_name, entry.class_name_pattern)) {
return true;
}
return false;
}
void WindowWatchdog::HandleOnOpen(HWND hwnd) {
std::string caption = GetWindowCaption(hwnd);
char class_name[MAX_PATH] = {0};
GetClassNameA(hwnd, class_name, arraysize(class_name));
// 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 (MatchingWindow(*entry_iter, caption, class_name)) {
if (process_exit_observer == NULL) {
process_exit_observer.reset(new ProcessExitObserver(this, hwnd));
}
entry_iter->open_windows.push_back(
OpenWindowEntry(hwnd, process_exit_observer));
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->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;
}
}
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 (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);
}