Home | History | Annotate | Download | only in test
      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 }
    276