普通文本  |  276行  |  9.12 KB

// 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);
}