Bug 1133351 - Part 2: Use SetWinEventHook to detect OLE Window. r=bent, a=lizzard
authorAaron Klotz <aklotz@mozilla.com>
Mon, 30 Mar 2015 10:42:39 -0700
changeset 266180 59793d7e1b7e
parent 266179 8f1677195e6f
child 266181 e2ed03987d19
push id4776
push userryanvm@gmail.com
push date2015-06-03 20:24 +0000
treeherdermozilla-beta@59793d7e1b7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent, lizzard
bugs1133351
milestone39.0
Bug 1133351 - Part 2: Use SetWinEventHook to detect OLE Window. r=bent, a=lizzard
ipc/glue/WindowsMessageLoop.cpp
--- a/ipc/glue/WindowsMessageLoop.cpp
+++ b/ipc/glue/WindowsMessageLoop.cpp
@@ -11,16 +11,17 @@
 #include "MessageChannel.h"
 
 #include "nsAutoPtr.h"
 #include "nsServiceManagerUtils.h"
 #include "nsString.h"
 #include "nsIXULAppInfo.h"
 #include "WinUtils.h"
 
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/PaintTracker.h"
 
 using namespace mozilla;
 using namespace mozilla::ipc;
 using namespace mozilla::ipc::windows;
 
 /**
  * The Windows-only code below exists to solve a general problem with deadlocks
@@ -100,20 +101,77 @@ nsTArray<HWND>* gNeuteredWindows = nullp
 typedef nsTArray<nsAutoPtr<DeferredMessage> > DeferredMessageArray;
 DeferredMessageArray* gDeferredMessages = nullptr;
 
 HHOOK gDeferredGetMsgHook = nullptr;
 HHOOK gDeferredCallWndProcHook = nullptr;
 
 DWORD gUIThreadId = 0;
 HWND gCOMWindow = 0;
+// Once initialized, gWinEventHook is never unhooked. We save the handle so
+// that we can check whether or not the hook is initialized.
+HWINEVENTHOOK gWinEventHook = nullptr;
+const wchar_t kCOMWindowClassName[] = L"OleMainThreadWndClass";
 
 // WM_GETOBJECT id pulled from uia headers
 #define MOZOBJID_UIAROOT -25
 
+HWND
+FindCOMWindow()
+{
+  MOZ_ASSERT(gUIThreadId);
+
+  HWND last = 0;
+  while ((last = FindWindowExW(HWND_MESSAGE, last, kCOMWindowClassName, NULL))) {
+    if (GetWindowThreadProcessId(last, NULL) == gUIThreadId) {
+      return last;
+    }
+  }
+
+  return (HWND)0;
+}
+
+void CALLBACK
+WinEventHook(HWINEVENTHOOK aWinEventHook, DWORD aEvent, HWND aHwnd,
+             LONG aIdObject, LONG aIdChild, DWORD aEventThread,
+             DWORD aMsEventTime)
+{
+  MOZ_ASSERT(aWinEventHook == gWinEventHook);
+  MOZ_ASSERT(gUIThreadId == aEventThread);
+  switch (aEvent) {
+    case EVENT_OBJECT_CREATE: {
+      if (aIdObject != OBJID_WINDOW || aIdChild != CHILDID_SELF) {
+        // Not an event we're interested in
+        return;
+      }
+      wchar_t classBuf[256] = {0};
+      int result = ::GetClassNameW(aHwnd, classBuf,
+                                   MOZ_ARRAY_LENGTH(classBuf));
+      if (result != (MOZ_ARRAY_LENGTH(kCOMWindowClassName) - 1) ||
+          wcsncmp(kCOMWindowClassName, classBuf, result)) {
+        // Not a class we're interested in
+        return;
+      }
+      MOZ_ASSERT(FindCOMWindow() == aHwnd);
+      gCOMWindow = aHwnd;
+      break;
+    }
+    case EVENT_OBJECT_DESTROY: {
+      if (aHwnd == gCOMWindow && aIdObject == OBJID_WINDOW) {
+        MOZ_ASSERT(aIdChild == CHILDID_SELF);
+        gCOMWindow = 0;
+      }
+      break;
+    }
+    default: {
+      return;
+    }
+  }
+}
+
 LRESULT CALLBACK
 DeferredMessageHook(int nCode,
                     WPARAM wParam,
                     LPARAM lParam)
 {
   // XXX This function is called for *both* the WH_CALLWNDPROC hook and the
   //     WH_GETMESSAGE hook, but they have different parameters. We don't
   //     use any of them except nCode which has the same meaning.
@@ -645,23 +703,32 @@ namespace windows {
 
 void
 InitUIThread()
 {
   // If we aren't setup before a call to NotifyWorkerThread, we'll hang
   // on startup.
   if (!gUIThreadId) {
     gUIThreadId = GetCurrentThreadId();
-
-    CoInitialize(nullptr);
   }
 
   MOZ_ASSERT(gUIThreadId);
   MOZ_ASSERT(gUIThreadId == GetCurrentThreadId(),
              "Called InitUIThread multiple times on different threads!");
+
+  if (!gWinEventHook) {
+    gWinEventHook = SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_DESTROY,
+                                    NULL, &WinEventHook, GetCurrentProcessId(),
+                                    gUIThreadId, WINEVENT_OUTOFCONTEXT);
+
+    // We need to execute this after setting the hook in case the OLE window
+    // already existed.
+    gCOMWindow = FindCOMWindow();
+  }
+  MOZ_ASSERT(gWinEventHook);
 }
 
 } // namespace windows
 } // namespace ipc
 } // namespace mozilla
 
 // See SpinInternalEventLoop below
 MessageChannel::SyncStackFrame::SyncStackFrame(MessageChannel* channel, bool interrupt)
@@ -805,31 +872,16 @@ MessageChannel::SpinInternalEventLoop()
 
 static inline bool
 IsTimeoutExpired(PRIntervalTime aStart, PRIntervalTime aTimeout)
 {
   return (aTimeout != PR_INTERVAL_NO_TIMEOUT) &&
     (aTimeout <= (PR_IntervalNow() - aStart));
 }
 
-HWND
-FindCOMWindow()
-{
-  MOZ_ASSERT(gUIThreadId);
-
-  HWND last = 0;
-  while ((last = FindWindowExW(HWND_MESSAGE, last, L"OleMainThreadWndClass", NULL))) {
-    if (GetWindowThreadProcessId(last, NULL) == gUIThreadId) {
-      return last;
-    }
-  }
-
-  return (HWND)0;
-}
-
 bool
 MessageChannel::WaitForSyncNotify()
 {
   mMonitor->AssertCurrentThreadOwns();
 
   MOZ_ASSERT(gUIThreadId, "InitUIThread was not called!");
 
   // Use a blocking wait if this channel does not require
@@ -860,22 +912,16 @@ MessageChannel::WaitForSyncNotify()
 
   NS_ASSERTION(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION,
                "Shouldn't be here for channels that don't use message deferral!");
   NS_ASSERTION(mTopFrame && !mTopFrame->mInterrupt,
                "Top frame is not a sync frame!");
 
   MonitorAutoUnlock unlock(*mMonitor);
 
-  MOZ_ASSERT_IF(gCOMWindow, FindCOMWindow() == gCOMWindow);
-
-  if (!gCOMWindow) {
-    gCOMWindow = FindCOMWindow();
-  }
-
   bool timedout = false;
 
   UINT_PTR timerId = 0;
   TimeoutData timeoutData = { 0 };
 
   if (mTimeoutMs != kNoTimeout) {
     InitTimeoutData(&timeoutData, mTimeoutMs);
 
@@ -1009,22 +1055,16 @@ MessageChannel::WaitForInterruptNotify()
 
   NS_ASSERTION(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION,
                "Shouldn't be here for channels that don't use message deferral!");
   NS_ASSERTION(mTopFrame && mTopFrame->mInterrupt,
                "Top frame is not a sync frame!");
 
   MonitorAutoUnlock unlock(*mMonitor);
 
-  MOZ_ASSERT_IF(gCOMWindow, FindCOMWindow() == gCOMWindow);
-
-  if (!gCOMWindow) {
-    gCOMWindow = FindCOMWindow();
-  }
-
   bool timedout = false;
 
   UINT_PTR timerId = 0;
   TimeoutData timeoutData = { 0 };
 
   // windowHook is used as a flag variable for the loop below: if it is set
   // and we start to spin a nested event loop, we need to clear the hook and
   // process deferred/pending messages.