Bug 1133351: Make Windows IPC play nicely with COM STA marshaling; r=bsmedberg
authorAaron Klotz <aklotz@mozilla.com>
Wed, 25 Mar 2015 20:54:23 -0700
changeset 266607 0cc8abe4e2bbcc18e0bf45d6fa77ac3484e9f0fc
parent 266606 5000b940578b090d416273fe88a10c18deec86ba
child 266608 2e1818e7d8b0cf1cf3d030a28cae64acad2417f0
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg
bugs1133351
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1133351: Make Windows IPC play nicely with COM STA marshaling; r=bsmedberg
dom/plugins/ipc/COMMessageFilter.cpp
dom/plugins/ipc/COMMessageFilter.h
dom/plugins/ipc/PluginModuleChild.cpp
dom/plugins/ipc/moz.build
ipc/glue/WindowsMessageLoop.cpp
widget/windows/nsWindow.cpp
deleted file mode 100644
--- a/dom/plugins/ipc/COMMessageFilter.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "base/basictypes.h"
-
-#include "COMMessageFilter.h"
-#include "base/message_loop.h"
-#include "mozilla/plugins/PluginModuleChild.h"
-
-#include <stdio.h>
-
-namespace mozilla {
-namespace plugins {
-
-HRESULT
-COMMessageFilter::QueryInterface(REFIID riid, void** ppv)
-{
-  if (riid == IID_IUnknown || riid == IID_IMessageFilter) {
-    *ppv = static_cast<IMessageFilter*>(this);
-    AddRef();
-    return S_OK;
-  }
-  return E_NOINTERFACE;
-}
-
-DWORD COMMessageFilter::AddRef()
-{
-  ++mRefCnt;
-  return mRefCnt;
-}
-
-DWORD COMMessageFilter::Release()
-{
-  DWORD r = --mRefCnt;
-  if (0 == r)
-    delete this;
-  return r;
-}
-
-DWORD 
-COMMessageFilter::HandleInComingCall(DWORD dwCallType,
-				     HTASK htaskCaller,
-				     DWORD dwTickCount,
-				     LPINTERFACEINFO lpInterfaceInfo)
-{
-  if (mPreviousFilter)
-    return mPreviousFilter->HandleInComingCall(dwCallType, htaskCaller,
-					       dwTickCount, lpInterfaceInfo);
-  return SERVERCALL_ISHANDLED;
-}
-
-DWORD
-COMMessageFilter::RetryRejectedCall(HTASK htaskCallee,
-				    DWORD dwTickCount,
-				    DWORD dwRejectType)
-{
-  if (mPreviousFilter)
-    return mPreviousFilter->RetryRejectedCall(htaskCallee, dwTickCount,
-					      dwRejectType);
-  return -1;
-}
-
-DWORD
-COMMessageFilter::MessagePending(HTASK htaskCallee,
-				 DWORD dwTickCount,
-				 DWORD dwPendingType)
-{
-  mPlugin->FlushPendingInterruptQueue();
-  if (mPreviousFilter)
-    return mPreviousFilter->MessagePending(htaskCallee, dwTickCount,
-					   dwPendingType);
-  return PENDINGMSG_WAITNOPROCESS;
-}
-
-void
-COMMessageFilter::Initialize(PluginModuleChild* module)
-{
-  nsRefPtr<COMMessageFilter> f = new COMMessageFilter(module);
-  ::CoRegisterMessageFilter(f, getter_AddRefs(f->mPreviousFilter));
-}
-
-} // namespace plugins
-} // namespace mozilla
deleted file mode 100644
--- a/dom/plugins/ipc/COMMessageFilter.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef mozilla_plugins_COMMessageFilter_h
-#define mozilla_plugins_COMMessageFilter_h
-
-#include <objidl.h>
-#include "nsISupportsImpl.h"
-#include "nsAutoPtr.h"
-
-namespace mozilla {
-namespace plugins {
-
-class PluginModuleChild;
-
-class COMMessageFilter final : public IMessageFilter
-{
-public:
-  static void Initialize(PluginModuleChild* plugin);
-
-  COMMessageFilter(PluginModuleChild* plugin)
-    : mPlugin(plugin)
-  { }
-
-  HRESULT WINAPI QueryInterface(REFIID riid, void** ppv);
-  DWORD WINAPI AddRef();
-  DWORD WINAPI Release();
-
-  DWORD WINAPI HandleInComingCall(DWORD dwCallType,
-                                  HTASK htaskCaller,
-                                  DWORD dwTickCount,
-                                  LPINTERFACEINFO lpInterfaceInfo);
-  DWORD WINAPI RetryRejectedCall(HTASK htaskCallee,
-                                 DWORD dwTickCount,
-                                 DWORD dwRejectType);
-  DWORD WINAPI MessagePending(HTASK htaskCallee,
-                              DWORD dwTickCount,
-                              DWORD dwPendingType);
-
-private:
-  nsAutoRefCnt mRefCnt;
-  PluginModuleChild* mPlugin;
-  nsRefPtr<IMessageFilter> mPreviousFilter;
-};
-
-} // namespace plugins
-} // namespace mozilla
-
-#endif // COMMessageFilter_h
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -37,17 +37,16 @@
 #include "mozilla/plugins/StreamNotifyChild.h"
 #include "mozilla/plugins/BrowserStreamChild.h"
 #include "mozilla/plugins/PluginStreamChild.h"
 #include "mozilla/dom/CrashReporterChild.h"
 
 #include "nsNPAPIPlugin.h"
 
 #ifdef XP_WIN
-#include "COMMessageFilter.h"
 #include "nsWindowsDllInterceptor.h"
 #include "mozilla/widget/AudioSession.h"
 #endif
 
 #ifdef MOZ_WIDGET_COCOA
 #include "PluginInterposeOSX.h"
 #include "PluginUtilsOSX.h"
 #endif
@@ -259,20 +258,16 @@ PluginModuleChild::RecvDisableFlashProte
 }
 
 bool
 PluginModuleChild::InitForChrome(const std::string& aPluginFilename,
                                  base::ProcessHandle aParentProcessHandle,
                                  MessageLoop* aIOLoop,
                                  IPC::Channel* aChannel)
 {
-#ifdef XP_WIN
-    COMMessageFilter::Initialize(this);
-#endif
-
     NS_ASSERTION(aChannel, "need a channel");
 
     if (!InitGraphics())
         return false;
 
     mPluginFilename = aPluginFilename.c_str();
     nsCOMPtr<nsIFile> localFile;
     NS_NewLocalFile(NS_ConvertUTF8toUTF16(mPluginFilename),
--- a/dom/plugins/ipc/moz.build
+++ b/dom/plugins/ipc/moz.build
@@ -45,17 +45,16 @@ EXPORTS.mozilla.plugins += [
     'TaskFactory.h',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     EXPORTS.mozilla.plugins += [
         'PluginSurfaceParent.h',
     ]
     UNIFIED_SOURCES += [
-        'COMMessageFilter.cpp',
         'PluginHangUIParent.cpp',
         'PluginSurfaceParent.cpp',
     ]
     SOURCES += [
         'MiniShmParent.cpp', # Issues with CreateEvent
     ]
     DEFINES['MOZ_HANGUI_PROCESS_NAME'] = '"plugin-hang-ui%s"' % CONFIG['BIN_SUFFIX']
     LOCAL_INCLUDES += [
--- a/ipc/glue/WindowsMessageLoop.cpp
+++ b/ipc/glue/WindowsMessageLoop.cpp
@@ -99,16 +99,17 @@ nsTArray<HWND>* gNeuteredWindows = nullp
 
 typedef nsTArray<nsAutoPtr<DeferredMessage> > DeferredMessageArray;
 DeferredMessageArray* gDeferredMessages = nullptr;
 
 HHOOK gDeferredGetMsgHook = nullptr;
 HHOOK gDeferredCallWndProcHook = nullptr;
 
 DWORD gUIThreadId = 0;
+HWND gCOMWindow = 0;
 
 // WM_GETOBJECT id pulled from uia headers
 #define MOZOBJID_UIAROOT -25
 
 LRESULT CALLBACK
 DeferredMessageHook(int nCode,
                     WPARAM wParam,
                     LPARAM lParam)
@@ -335,23 +336,25 @@ ProcessOrDeferMessage(HWND hwnd,
       return 0;
 
     // We only support a query for our IAccessible or UIA pointers.
     // This should be safe, and needs to be sync.
 #if defined(ACCESSIBILITY)
    case WM_GETOBJECT: {
       if (!::GetPropW(hwnd, k3rdPartyWindowProp)) {
         DWORD objId = static_cast<DWORD>(lParam);
-        WNDPROC oldWndProc = (WNDPROC)GetProp(hwnd, kOldWndProcProp);
-        if ((objId == OBJID_CLIENT || objId == MOZOBJID_UIAROOT) && oldWndProc) {
-          return CallWindowProcW(oldWndProc, hwnd, uMsg, wParam, lParam);
+        if ((objId == OBJID_CLIENT || objId == MOZOBJID_UIAROOT)) {
+          WNDPROC oldWndProc = (WNDPROC)GetProp(hwnd, kOldWndProcProp);
+          if (oldWndProc) {
+            return CallWindowProcW(oldWndProc, hwnd, uMsg, wParam, lParam);
+          }
         }
       }
-      break;
-    }
+      return DefWindowProc(hwnd, uMsg, wParam, lParam);
+   }
 #endif // ACCESSIBILITY
 
     default: {
       // Unknown messages only are logged in debug builds and sent to
       // DefWindowProc.
       if (uMsg && uMsg == mozilla::widget::sAppShellGeckoMsgId) {
         // Widget's registered native event callback
         deferred = new DeferredSendMessage(hwnd, uMsg, wParam, lParam);
@@ -642,17 +645,20 @@ 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!");
 }
 
 } // namespace windows
 } // namespace ipc
 } // namespace mozilla
@@ -799,16 +805,31 @@ 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
@@ -839,16 +860,22 @@ 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);
 
@@ -907,21 +934,30 @@ MessageChannel::WaitForSyncNotify()
       // PeekMessage will return false if there are no "queued" messages, but it
       // will run all "nonqueued" messages before returning. So if PeekMessage
       // returns false and there are no "nonqueued" messages that were run then
       // we know that the message we woke for was intended for a window on
       // another thread.
       bool haveSentMessagesPending =
         (HIWORD(GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0;
 
-      // This PeekMessage call will actually process all "nonqueued" messages
-      // that are pending before returning. If we have "nonqueued" messages
-      // pending then we should have switched out all the window procedures
-      // above. In that case this PeekMessage call won't actually cause any
-      // mozilla code (or plugin code) to run.
+      // Either of the PeekMessage calls below will actually process all
+      // "nonqueued" messages that are pending before returning. If we have
+      // "nonqueued" messages pending then we should have switched out all the
+      // window procedures above. In that case this PeekMessage call won't
+      // actually cause any mozilla code (or plugin code) to run.
+
+      // We have to manually pump all COM messages *after* looking at the queue
+      // queue status but before yielding our thread below.
+      if (gCOMWindow) {
+        if (PeekMessageW(&msg, gCOMWindow, 0, 0, PM_REMOVE)) {
+          TranslateMessage(&msg);
+          ::DispatchMessageW(&msg);
+        }
+      }
 
       // If the following PeekMessage call fails to return a message for us (and
       // returns false) and we didn't run any "nonqueued" messages then we must
       // have woken up for a message designated for a window in another thread.
       // If we loop immediately then we could enter a tight loop, so we'll give
       // up our time slice here to let the child process its message.
       if (!PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE) &&
           !haveSentMessagesPending) {
@@ -973,16 +1009,22 @@ 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.
@@ -1062,16 +1104,24 @@ MessageChannel::WaitForInterruptNotify()
       timedout = true;
       break;
     }
 
     // See MessageChannel's WaitFor*Notify for details.
     bool haveSentMessagesPending =
       (HIWORD(GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0;
 
+    // Run all COM messages *after* looking at the queue status.
+    if (gCOMWindow) {
+        if (PeekMessageW(&msg, gCOMWindow, 0, 0, PM_REMOVE)) {
+            TranslateMessage(&msg);
+            ::DispatchMessageW(&msg);
+        }
+    }
+
     // PeekMessage markes the messages as "old" so that they don't wake up
     // MsgWaitForMultipleObjects every time.
     if (!PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE) &&
         !haveSentMessagesPending) {
       // Message was for child, we should wait a bit.
       SwitchToThread();
     }
   }
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -4139,18 +4139,18 @@ nsWindow::IsAsyncResponseEvent(UINT aMsg
 #endif
 
   return false;
 }
 
 void
 nsWindow::IPCWindowProcHandler(UINT& msg, WPARAM& wParam, LPARAM& lParam)
 {
-  NS_ASSERTION(!mozilla::ipc::MessageChannel::IsPumpingMessages(),
-               "Failed to prevent a nonqueued message from running!");
+  MOZ_ASSERT_IF(msg != WM_GETOBJECT,
+                !mozilla::ipc::MessageChannel::IsPumpingMessages());
 
   // Modal UI being displayed in windowless plugins.
   if (mozilla::ipc::MessageChannel::IsSpinLoopActive() &&
       (InSendMessageEx(nullptr) & (ISMEX_REPLIED|ISMEX_SEND)) == ISMEX_SEND) {
     LRESULT res;
     if (IsAsyncResponseEvent(msg, res)) {
       ReplyMessage(res);
     }