Bug 538918 - [OOPP] Modal system dialogs cause UI freeze w/windowless controls. r=bent, cjones.
authorJim Mathies <jmathies@mozilla.com>
Tue, 09 Feb 2010 16:34:38 -0600
changeset 38103 acdee57eefffc867cde8d82387860456e990922e
parent 38102 bb789c6c77132b9b4551fe2d5f79f6d98ad59450
child 38104 6cd32ccca1fec0bda9427e07a6275c94d9f1eea6
push id11617
push usercjones@mozilla.com
push dateFri, 12 Feb 2010 05:46:47 +0000
treeherdermozilla-central@c5ca3076da1b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent, cjones
bugs538918
milestone1.9.3a2pre
Bug 538918 - [OOPP] Modal system dialogs cause UI freeze w/windowless controls. r=bent, cjones.
dom/plugins/PPluginInstance.ipdl
dom/plugins/PluginInstanceChild.cpp
dom/plugins/PluginInstanceChild.h
dom/plugins/PluginInstanceParent.cpp
dom/plugins/PluginInstanceParent.h
ipc/chromium/src/base/message_loop.cc
ipc/chromium/src/base/message_loop.h
ipc/glue/RPCChannel.cpp
ipc/glue/RPCChannel.h
ipc/glue/SyncChannel.cpp
ipc/glue/WindowsMessageLoop.cpp
widget/src/windows/nsWindow.cpp
widget/src/windows/nsWindow.h
widget/src/windows/nsWindowGfx.cpp
--- a/dom/plugins/PPluginInstance.ipdl
+++ b/dom/plugins/PPluginInstance.ipdl
@@ -159,15 +159,16 @@ child:
 parent:
   /* NPN_NewStream */
   rpc PPluginStream(nsCString mimeType,
                     nsCString target)
     returns (NPError result);
 
 parent:
   rpc PluginGotFocus();
+  sync SetNestedEventState(bool aState);
 child:
   rpc SetPluginFocus();
   rpc UpdateWindow();
 };
 
 } // namespace plugins
 } // namespace mozilla
--- a/dom/plugins/PluginInstanceChild.cpp
+++ b/dom/plugins/PluginInstanceChild.cpp
@@ -52,29 +52,44 @@ using namespace mozilla::plugins;
 #include <gtk/gtk.h>
 #include <gdk/gdkx.h>
 #include <gdk/gdk.h>
 #include "gtk2xtbin.h"
 
 #elif defined(MOZ_WIDGET_QT)
 #include <QX11Info>
 #elif defined(OS_WIN)
+
 using mozilla::gfx::SharedDIB;
 
 #include <windows.h>
 
 #define NS_OOPP_DOUBLEPASS_MSGID TEXT("MozDoublePassMsg")
-#endif
+
+// During nested ui loops, parent is processing windows events via spin loop,
+// which results in rpc in-calls to child. If child falls behind in processing
+// these, an ugly stall condition occurs. To ensure child stays in sync, we use
+// a timer callback to schedule work on in-calls.
+#define CHILD_MODALPUMPTIMEOUT 50
+#define CHILD_MODALLOOPTIMER   654321
+
+#endif // defined(OS_WIN)
 
 PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface) :
     mPluginIface(aPluginIface)
 #if defined(OS_WIN)
     , mPluginWindowHWND(0)
     , mPluginWndProc(0)
     , mPluginParentHWND(0)
+    , mNestedEventHook(0)
+    , mNestedPumpHook(0)
+    , mNestedEventLevelDepth(0)
+    , mNestedEventState(false)
+    , mCachedWinlessPluginHWND(0)
+    , mEventPumpTimer(0)
 #endif // OS_WIN
 {
     memset(&mWindow, 0, sizeof(mWindow));
     mData.ndata = (void*) this;
 #if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
     mWindow.ws_info = &mWsInfo;
     memset(&mWsInfo, 0, sizeof(mWsInfo));
 #ifdef MOZ_WIDGET_GTK2
@@ -200,22 +215,25 @@ PluginInstanceChild::NPN_GetValue(NPNVar
         PluginModuleChild::sBrowserFuncs.retainobject(object);
         *((NPObject**)aValue) = object;
         return NPERR_NO_ERROR;
     }
 
     case NPNVnetscapeWindow: {
 #ifdef XP_WIN
         if (mWindow.type == NPWindowTypeDrawable) {
-            HWND hwnd = NULL;
+            if (mCachedWinlessPluginHWND) {
+              *static_cast<HWND*>(aValue) = mCachedWinlessPluginHWND;
+              return NPERR_NO_ERROR;
+            }
             NPError result;
-            if (!CallNPN_GetValue_NPNVnetscapeWindow(&hwnd, &result)) {
+            if (!CallNPN_GetValue_NPNVnetscapeWindow(&mCachedWinlessPluginHWND, &result)) {
                 return NPERR_GENERIC_ERROR;
             }
-            *static_cast<HWND*>(aValue) = hwnd;
+            *static_cast<HWND*>(aValue) = mCachedWinlessPluginHWND;
             return result;
         }
         else {
             *static_cast<HWND*>(aValue) = mPluginWindowHWND;
             return NPERR_NO_ERROR;
         }
 #elif defined(MOZ_X11)
         NPError result;
@@ -401,16 +419,18 @@ PluginInstanceChild::AnswerNPP_HandleEve
             // We'll render to mSharedSurfaceDib first, then render to a cached bitmap
             // we store locally. The two passes are for alpha extraction, so the second
             // pass must be to a flat white surface in order for things to work.
             mAlphaExtract.doublePass = RENDER_BACK_ONE;
             *handled = true;
             return true;
        }
     }
+    *handled = WinlessHandleEvent(evcopy);
+    return true;
 #endif
 
     *handled = mPluginIface->event(&mData, reinterpret_cast<void*>(&evcopy));
 
 #ifdef MOZ_X11
     if (GraphicsExpose == event.event.type) {
         // Make sure the X server completes the drawing before the parent
         // draws on top and destroys the Drawable.
@@ -714,16 +734,188 @@ PluginInstanceChild::PluginWindowProc(HW
         self->DestroyPluginWindow();
 
     if (message == WM_NCDESTROY)
         RemoveProp(hWnd, kPluginInstanceChildProperty);
 
     return res;
 }
 
+/* winless modal ui loop logic */
+
+static bool
+IsUserInputEvent(UINT msg)
+{
+  // Events we assume some sort of modal ui *might* be generated.
+  switch (msg) {
+      case WM_LBUTTONUP:
+      case WM_RBUTTONUP:
+      case WM_MBUTTONUP:
+      case WM_LBUTTONDOWN:
+      case WM_RBUTTONDOWN:
+      case WM_MBUTTONDOWN:
+      case WM_CONTEXTMENU:
+          return true;
+  }
+  return false;
+}
+
+VOID CALLBACK
+PluginInstanceChild::PumpTimerProc(HWND hwnd,
+                                   UINT uMsg,
+                                   UINT_PTR idEvent,
+                                   DWORD dwTime)
+{
+    MessageLoop::current()->ScheduleWork();
+}
+
+LRESULT CALLBACK
+PluginInstanceChild::NestedInputPumpHook(int nCode,
+                                         WPARAM wParam,
+                                         LPARAM lParam)
+{
+    if (nCode >= 0) {
+        MessageLoop::current()->ScheduleWork();
+    }
+    return CallNextHookEx(NULL, nCode, wParam, lParam);
+}
+
+// gTempChildPointer is only in use from the time we enter handle event, to the
+// point where ui might be created by that call. If ui isn't created, there's
+// no issue. If ui is created, the parent can't start processing messages in
+// spin loop until InternalCallSetNestedEventState is set, at which point,
+// gTempChildPointer is no longer needed.
+static PluginInstanceChild* gTempChildPointer;
+
+LRESULT CALLBACK
+PluginInstanceChild::NestedInputEventHook(int nCode,
+                                          WPARAM wParam,
+                                          LPARAM lParam)
+{
+    if (!gTempChildPointer) {
+        return CallNextHookEx(NULL, nCode, wParam, lParam);
+    }
+
+    if (nCode >= 0) {
+        NS_ASSERTION(gTempChildPointer, "Never should be null here!");
+        gTempChildPointer->ResetNestedEventHook();
+        gTempChildPointer->SetNestedInputPumpHook();
+        gTempChildPointer->InternalCallSetNestedEventState(true);
+
+        gTempChildPointer = NULL;
+    }
+    return CallNextHookEx(NULL, nCode, wParam, lParam);
+}
+
+void
+PluginInstanceChild::SetNestedInputPumpHook()
+{
+    NS_ASSERTION(!mNestedPumpHook,
+        "mNestedPumpHook already setup in call to SetNestedInputPumpHook?");
+
+    PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION));
+
+    mNestedPumpHook = SetWindowsHookEx(WH_CALLWNDPROC,
+                                       NestedInputPumpHook,
+                                       NULL,
+                                       GetCurrentThreadId());
+    mEventPumpTimer = 
+        SetTimer(NULL,
+                 CHILD_MODALLOOPTIMER,
+                 CHILD_MODALPUMPTIMEOUT,
+                 PumpTimerProc);
+}
+
+void
+PluginInstanceChild::ResetPumpHooks()
+{
+    if (mNestedPumpHook)
+        UnhookWindowsHookEx(mNestedPumpHook);
+    mNestedPumpHook = NULL;
+    if (mEventPumpTimer)
+        KillTimer(NULL, mEventPumpTimer);
+}
+
+void
+PluginInstanceChild::SetNestedInputEventHook()
+{
+    NS_ASSERTION(!mNestedEventHook,
+        "mNestedEventHook already setup in call to SetNestedInputEventHook?");
+
+    PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION));
+
+    // WH_GETMESSAGE hooks are triggered by peek message calls in parent due to
+    // attached message queues, resulting in stomped in-process ipc calls.  So
+    // we use a filter hook specific to dialogs, menus, and scroll bars to kick
+    // things off.
+    mNestedEventHook = SetWindowsHookEx(WH_MSGFILTER,
+                                        NestedInputEventHook,
+                                        NULL,
+                                        GetCurrentThreadId());
+}
+
+void
+PluginInstanceChild::ResetNestedEventHook()
+{
+    PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION));
+    if (mNestedEventHook)
+        UnhookWindowsHookEx(mNestedEventHook);
+    mNestedEventHook = NULL;
+}
+
+void
+PluginInstanceChild::InternalCallSetNestedEventState(bool aState)
+{
+    if (aState != mNestedEventState) {
+        PLUGIN_LOG_DEBUG(
+            ("PluginInstanceChild::InternalCallSetNestedEventState(%i)",
+            (int)aState));
+        mNestedEventState = aState;
+        SendSetNestedEventState(mNestedEventState);
+    }
+}
+
+int16_t
+PluginInstanceChild::WinlessHandleEvent(NPEvent& event)
+{
+  if (!IsUserInputEvent(event.event)) {
+      return mPluginIface->event(&mData, reinterpret_cast<void*>(&event));
+  }
+
+  int16_t handled;
+
+  mNestedEventLevelDepth++;
+  PLUGIN_LOG_DEBUG(("WinlessHandleEvent start depth: %i", mNestedEventLevelDepth));
+
+  // On the first, non-reentrant call, setup our modal ui detection hook.
+  if (mNestedEventLevelDepth == 1) {
+      NS_ASSERTION(!gTempChildPointer, "valid gTempChildPointer here?");
+      gTempChildPointer = this;
+      SetNestedInputEventHook();
+  }
+
+  bool old_state = MessageLoop::current()->NestableTasksAllowed();
+  MessageLoop::current()->SetNestableTasksAllowed(true);
+  handled = mPluginIface->event(&mData, reinterpret_cast<void*>(&event));
+  MessageLoop::current()->SetNestableTasksAllowed(old_state);
+
+  gTempChildPointer = NULL;
+
+  mNestedEventLevelDepth--;
+  PLUGIN_LOG_DEBUG(("WinlessHandleEvent end depth: %i", mNestedEventLevelDepth));
+
+  NS_ASSERTION(!(mNestedEventLevelDepth < 0), "mNestedEventLevelDepth < 0?");
+  if (mNestedEventLevelDepth <= 0) {
+      ResetNestedEventHook();
+      ResetPumpHooks();
+      InternalCallSetNestedEventState(false);
+  }
+  return handled;
+}
+
 /* windowless drawing helpers */
 
 bool
 PluginInstanceChild::SharedSurfaceSetWindow(const NPRemoteWindow& aWindow,
                                             NPError* rv)
 {
     // If the surfaceHandle is empty, parent is telling us we can reuse our cached
     // memory surface and hdc. Otherwise, we need to reset, usually due to a
@@ -1165,12 +1357,14 @@ PluginInstanceChild::AnswerNPP_Destroy(N
 
     mTimers.Clear();
 
     PluginModuleChild* module = PluginModuleChild::current();
     bool retval = module->PluginInstanceDestroyed(this, aResult);
 
 #if defined(OS_WIN)
     SharedSurfaceRelease();
+    ResetNestedEventHook();
+    ResetPumpHooks();
 #endif
 
     return retval;
 }
--- a/dom/plugins/PluginInstanceChild.h
+++ b/dom/plugins/PluginInstanceChild.h
@@ -186,36 +186,58 @@ public:
 private:
 
 #if defined(OS_WIN)
     static bool RegisterWindowClass();
     bool CreatePluginWindow();
     void DestroyPluginWindow();
     void ReparentPluginWindow(HWND hWndParent);
     void SizePluginWindow(int width, int height);
+    int16_t WinlessHandleEvent(NPEvent& event);
+    void SetNestedInputEventHook();
+    void ResetNestedEventHook();
+    void SetNestedInputPumpHook();
+    void ResetPumpHooks();
+    void InternalCallSetNestedEventState(bool aState);
     static LRESULT CALLBACK DummyWindowProc(HWND hWnd,
                                             UINT message,
                                             WPARAM wParam,
                                             LPARAM lParam);
     static LRESULT CALLBACK PluginWindowProc(HWND hWnd,
                                              UINT message,
                                              WPARAM wParam,
                                              LPARAM lParam);
+    static VOID CALLBACK PumpTimerProc(HWND hwnd,
+                                       UINT uMsg,
+                                       UINT_PTR idEvent,
+                                       DWORD dwTime);
+    static LRESULT CALLBACK NestedInputEventHook(int code,
+                                                 WPARAM wParam,
+                                                 LPARAM lParam);
+    static LRESULT CALLBACK NestedInputPumpHook(int code,
+                                                WPARAM wParam,
+                                                LPARAM lParam);
 #endif
 
     const NPPluginFuncs* mPluginIface;
     NPP_t mData;
     NPWindow mWindow;
 
 #if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
     NPSetWindowCallbackStruct mWsInfo;
 #elif defined(OS_WIN)
     HWND mPluginWindowHWND;
     WNDPROC mPluginWndProc;
     HWND mPluginParentHWND;
+    HHOOK mNestedEventHook;
+    HHOOK mNestedPumpHook;
+    int mNestedEventLevelDepth;
+    bool mNestedEventState;
+    HWND mCachedWinlessPluginHWND;
+    UINT_PTR mEventPumpTimer;
 #endif
 
     friend class ChildAsyncCall;
     nsTArray<ChildAsyncCall*> mPendingAsyncCalls;
     nsTArray<nsAutoPtr<ChildTimer> > mTimers;
 
 #if defined(OS_WIN)
 private:
--- a/dom/plugins/PluginInstanceParent.cpp
+++ b/dom/plugins/PluginInstanceParent.cpp
@@ -48,16 +48,20 @@
 
 #if defined(OS_WIN)
 #include <windowsx.h>
 
 // Plugin focus event for widget.
 extern const PRUnichar* kOOPPPluginFocusEventId;
 UINT gOOPPPluginFocusEvent =
     RegisterWindowMessage(kOOPPPluginFocusEventId);
+UINT gOOPPSpinNativeLoopEvent =
+    RegisterWindowMessage(L"SyncChannel Spin Inner Loop Message");
+UINT gOOPPStopNativeLoopEvent =
+    RegisterWindowMessage(L"SyncChannel Stop Inner Loop Message");
 #endif
 
 using namespace mozilla::plugins;
 
 PluginInstanceParent::PluginInstanceParent(PluginModuleParent* parent,
                                            NPP npp,
                                            const NPNetscapeFuncs* npniface)
   : mParent(parent)
@@ -106,32 +110,46 @@ void
 PluginInstanceParent::ActorDestroy(ActorDestroyReason why)
 {
 #if defined(OS_WIN)
     if (why == AbnormalShutdown) {
         // If the plugin process crashes, this is the only
         // chance we get to destroy resources.
         SharedSurfaceRelease();
         UnsubclassPluginWindow();
+        // If we crashed in a modal loop in the child, reset
+        // the rpc event spin loop state.
+        if (mNestedEventState) {
+            mNestedEventState = false;
+            PostThreadMessage(GetCurrentThreadId(),
+                              gOOPPStopNativeLoopEvent,
+                              0, 0);
+        }
     }
 #endif
 }
 
 NPError
 PluginInstanceParent::Destroy()
 {
     NPError retval;
     if (!CallNPP_Destroy(&retval)) {
         NS_WARNING("Failed to send message!");
         return NPERR_GENERIC_ERROR;
     }
 
 #if defined(OS_WIN)
     SharedSurfaceRelease();
     UnsubclassPluginWindow();
+    if (mNestedEventState) {
+        mNestedEventState = false;
+        PostThreadMessage(GetCurrentThreadId(),
+                          gOOPPStopNativeLoopEvent,
+                          0, 0);
+    }
 #endif
 
     return retval;
 }
 
 PBrowserStreamParent*
 PluginInstanceParent::AllocPBrowserStream(const nsCString& url,
                                           const uint32_t& length,
@@ -1083,8 +1101,24 @@ PluginInstanceParent::AnswerPluginGotFoc
 #if defined(OS_WIN)
     ::SendMessage(mPluginHWND, gOOPPPluginFocusEvent, 0, 0);
     return true;
 #else
     NS_NOTREACHED("PluginInstanceParent::AnswerPluginGotFocus not implemented!");
     return false;
 #endif
 }
+
+bool
+PluginInstanceParent::RecvSetNestedEventState(const bool& aState)
+{
+    PLUGIN_LOG_DEBUG(("%s state=%i", FULLFUNCTION, (int)aState));
+#if defined(OS_WIN)
+    PostThreadMessage(GetCurrentThreadId(), aState ?
+        gOOPPSpinNativeLoopEvent : gOOPPStopNativeLoopEvent, 0, 0);
+    mNestedEventState = aState;
+    return true;
+#else
+    NS_NOTREACHED(
+        "PluginInstanceParent::AnswerSetNestedEventState not implemented!");
+    return false;
+#endif
+}
--- a/dom/plugins/PluginInstanceParent.h
+++ b/dom/plugins/PluginInstanceParent.h
@@ -222,16 +222,19 @@ public:
     GetNPP()
     {
       return mNPP;
     }
 
     virtual bool
     AnswerPluginGotFocus();
 
+    virtual bool
+    RecvSetNestedEventState(const bool& aState);
+
 private:
     bool InternalGetValueForNPObject(NPNVariable aVariable,
                                      PPluginScriptableObjectParent** aValue,
                                      NPError* aResult);
 
 private:
     PluginModuleParent* mParent;
     NPP mNPP;
@@ -254,16 +257,17 @@ private:
     void UnsubclassPluginWindow();
 
 private:
     gfx::SharedDIBWin  mSharedSurfaceDib;
     nsIntRect          mPluginPort;
     nsIntRect          mSharedSize;
     HWND               mPluginHWND;
     WNDPROC            mPluginWndProc;
+    bool               mNestedEventState;
 #endif // defined(XP_WIN)
 };
 
 
 } // namespace plugins
 } // namespace mozilla
 
 #endif // ifndef dom_plugins_PluginInstanceParent_h
--- a/ipc/chromium/src/base/message_loop.cc
+++ b/ipc/chromium/src/base/message_loop.cc
@@ -306,16 +306,21 @@ void MessageLoop::SetNestableTasksAllowe
     nestable_tasks_allowed_ = allowed;
     if (!nestable_tasks_allowed_)
       return;
     // Start the native pump if we are not already pumping.
     pump_->ScheduleWork();
   }
 }
 
+void MessageLoop::ScheduleWork() {
+  // Start the native pump if we are not already pumping.
+  pump_->ScheduleWork();
+}
+
 bool MessageLoop::NestableTasksAllowed() const {
   return nestable_tasks_allowed_;
 }
 
 //------------------------------------------------------------------------------
 
 void MessageLoop::RunTask(Task* task) {
   DCHECK(nestable_tasks_allowed_);
--- a/ipc/chromium/src/base/message_loop.h
+++ b/ipc/chromium/src/base/message_loop.h
@@ -243,16 +243,17 @@ public:
   // - The task #1 implicitly start a message loop, like a MessageBox in the
   //   unit test. This can also be StartDoc or GetSaveFileName.
   // - The thread receives a task #2 before or while in this second message
   //   loop.
   // - With NestableTasksAllowed set to true, the task #2 will run right away.
   //   Otherwise, it will get executed right after task #1 completes at "thread
   //   message loop level".
   void SetNestableTasksAllowed(bool allowed);
+  void ScheduleWork();
   bool NestableTasksAllowed() const;
 
   // Enables or disables the restoration during an exception of the unhandled
   // exception filter that was active when Run() was called. This can happen
   // if some third party code call SetUnhandledExceptionFilter() and never
   // restores the previous filter.
   void set_exception_restoration(bool restore) {
     exception_restoration_ = restore;
--- a/ipc/glue/RPCChannel.cpp
+++ b/ipc/glue/RPCChannel.cpp
@@ -104,16 +104,19 @@ RPCChannel::RPCChannel(RPCListener* aLis
 }
 
 RPCChannel::~RPCChannel()
 {
     MOZ_COUNT_DTOR(RPCChannel);
     // FIXME/cjones: impl
 }
 
+// static
+int RPCChannel::sInnerEventLoopDepth = 0;
+
 bool
 RPCChannel::Call(Message* msg, Message* reply)
 {
     AssertWorkerThread();
     mMutex.AssertNotCurrentThreadOwns();
     RPC_ASSERT(!ProcessingSyncMessage(),
                "violation of sync handler invariant");
     RPC_ASSERT(msg->is_rpc(), "can only Call() RPC messages here");
@@ -138,30 +141,33 @@ RPCChannel::Call(Message* msg, Message* 
         // now might be the time to process a message deferred because
         // of race resolution
         MaybeProcessDeferredIncall();
 
         // here we're waiting for something to happen. see long
         // comment about the queue in RPCChannel.h
         while (Connected() && mPending.empty() &&
                (mOutOfTurnReplies.empty() ||
-                mOutOfTurnReplies.top().seqno() < mStack.top().seqno())) {
-            WaitForNotify();
+                mOutOfTurnReplies.find(mStack.top().seqno())
+                == mOutOfTurnReplies.end())) {
+            RPCChannel::WaitForNotify();
         }
 
         if (!Connected()) {
             ReportConnectionError("RPCChannel");
             return false;
         }
 
         Message recvd;
+        MessageMap::iterator it;
         if (!mOutOfTurnReplies.empty() &&
-            mOutOfTurnReplies.top().seqno() == mStack.top().seqno()) {
-            recvd = mOutOfTurnReplies.top();
-            mOutOfTurnReplies.pop();
+            ((it = mOutOfTurnReplies.find(mStack.top().seqno())) !=
+            mOutOfTurnReplies.end())) {
+            recvd = it->second;
+            mOutOfTurnReplies.erase(it);
         }
         else {
             recvd = mPending.front();
             mPending.pop();
         }
 
         if (!recvd.is_sync() && !recvd.is_rpc()) {
             MutexAutoUnlock unlock(mMutex);
@@ -180,17 +186,17 @@ RPCChannel::Call(Message* msg, Message* 
         RPC_ASSERT(recvd.is_rpc(), "wtf???");
 
         if (recvd.is_reply()) {
             RPC_ASSERT(0 < mStack.size(), "invalid RPC stack");
 
             const Message& outcall = mStack.top();
 
             if (recvd.seqno() < outcall.seqno()) {
-                mOutOfTurnReplies.push(recvd);
+                mOutOfTurnReplies[recvd.seqno()] = recvd;
                 continue;
             }
 
             // FIXME/cjones: handle error
             RPC_ASSERT(
                 recvd.is_reply_error() ||
                 (recvd.type() == (outcall.type()+1) &&
                  recvd.seqno() == outcall.seqno()),
@@ -331,25 +337,28 @@ RPCChannel::Incall(const Message& call, 
     AssertWorkerThread();
     mMutex.AssertNotCurrentThreadOwns();
     RPC_ASSERT(call.is_rpc() && !call.is_reply(), "wrong message type");
 
     // Race detection: see the long comment near
     // mRemoteStackDepthGuess in RPCChannel.h.  "Remote" stack depth
     // means our side, and "local" means other side.
     if (call.rpc_remote_stack_depth_guess() != stackDepth) {
-        NS_WARNING("RPC in-calls have raced!");
-
+        //NS_WARNING("RPC in-calls have raced!");
+#ifndef OS_WIN
         RPC_ASSERT(call.rpc_remote_stack_depth_guess() < stackDepth,
                    "fatal logic error");
         RPC_ASSERT(1 == (stackDepth - call.rpc_remote_stack_depth_guess()),
                    "got more than 1 RPC message out of sync???");
-        RPC_ASSERT(1 == (call.rpc_local_stack_depth() -mRemoteStackDepthGuess),
+        RPC_ASSERT(1 == (call.rpc_local_stack_depth() - mRemoteStackDepthGuess),
                    "RPC unexpected not symmetric");
-
+#else
+        // See WindowsEventLoop, windows can race heavily when modal ui
+        // loops are displayed by plugins.
+#endif
         // the "winner", if there is one, gets to defer processing of
         // the other side's in-call
         bool defer;
         const char* winner;
         switch (mRacePolicy) {
         case RRPChildWins:
             winner = "child";
             defer = mChild;
--- a/ipc/glue/RPCChannel.h
+++ b/ipc/glue/RPCChannel.h
@@ -109,17 +109,38 @@ public:
 
     // Override the SyncChannel handler so we can dispatch RPC
     // messages.  Called on the IO thread only.
     NS_OVERRIDE
     virtual void OnMessageReceived(const Message& msg);
     NS_OVERRIDE
     virtual void OnChannelError();
 
-private:
+#ifdef OS_WIN
+    static bool IsSpinLoopActive() {
+        return (sInnerEventLoopDepth > 0);
+    }
+
+protected:
+    void WaitForNotify();
+    bool IsMessagePending();
+    bool SpinInternalEventLoop();
+    static void EnterModalLoop() {
+        sInnerEventLoopDepth++;
+    }
+    static void ExitModalLoop() {
+        sInnerEventLoopDepth--;
+        NS_ASSERTION(sInnerEventLoopDepth >= 0,
+            "sInnerEventLoopDepth dropped below zero!");
+    }
+
+    static int sInnerEventLoopDepth;
+#endif
+
+  private:
     // Called on worker thread only
 
     void MaybeProcessDeferredIncall();
     void EnqueuePendingMessages();
 
     void OnMaybeDequeueOne();
     void Incall(const Message& call, size_t stackDepth);
     void DispatchIncall(const Message& call);
@@ -181,21 +202,22 @@ private:
 
     // 
     // Stack of all the RPC out-calls on which this RPCChannel is
     // awaiting a response.
     //
     std::stack<Message> mStack;
 
     //
-    // Stack of replies received "out of turn", because of RPC
+    // Map of replies received "out of turn", because of RPC
     // in-calls racing with replies to outstanding in-calls.  See
     // https://bugzilla.mozilla.org/show_bug.cgi?id=521929.
     //
-    std::stack<Message> mOutOfTurnReplies;
+    typedef std::map<size_t, Message> MessageMap;
+    MessageMap mOutOfTurnReplies;
 
     //
     // Stack of RPC in-calls that were deferred because of race
     // conditions.
     //
     std::stack<Message> mDeferred;
 
     //
--- a/ipc/glue/SyncChannel.cpp
+++ b/ipc/glue/SyncChannel.cpp
@@ -98,17 +98,17 @@ SyncChannel::Send(Message* msg, Message*
 
     // NB: this is a do-while loop instead of a single wait because if
     // there's a pending RPC out- or in-call below us, and the sync
     // message handler on the other side sends us an async message,
     // the IO thread will Notify() this thread of the async message.
     // See https://bugzilla.mozilla.org/show_bug.cgi?id=538239.
     do {
         // wait for the next sync message to arrive
-        WaitForNotify();
+        SyncChannel::WaitForNotify();
     } while(Connected() &&
             mPendingReply != mRecvd.type() && !mRecvd.is_reply_error());
 
     if (!Connected()) {
         ReportConnectionError("SyncChannel");
         return false;
     }
 
--- a/ipc/glue/WindowsMessageLoop.cpp
+++ b/ipc/glue/WindowsMessageLoop.cpp
@@ -17,42 +17,44 @@
  * The Original Code is Mozilla Plugin App.
  *
  * The Initial Developer of the Original Code is
  *   Ben Turner <bent.mozilla@gmail.com>
  * Portions created by the Initial Developer are Copyright (C) 2009
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
+ *   Jim Mathies <jmathies@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "WindowsMessageLoop.h"
-#include "SyncChannel.h"
+#include "RPCChannel.h"
 
 #include "nsAutoPtr.h"
 #include "nsServiceManagerUtils.h"
 #include "nsStringGlue.h"
 #include "nsIXULAppInfo.h"
 
 #include "mozilla/Mutex.h"
 
 using mozilla::ipc::SyncChannel;
+using mozilla::ipc::RPCChannel;
 using mozilla::MutexAutoUnlock;
 
 using namespace mozilla::ipc::windows;
 
 /**
  * The Windows-only code below exists to solve a general problem with deadlocks
  * that we experience when sending synchronous IPC messages to processes that
  * contain native windows (i.e. HWNDs). Windows (the OS) sends synchronous
@@ -80,28 +82,41 @@ using namespace mozilla::ipc::windows;
  * WS_EX_NOPARENTNOTIFY style on the child window) but the general problem is
  * persists. Once two HWNDs are parented we must not block their owning
  * threads when manipulating either HWND.
  *
  * Windows requires any application that hosts native HWNDs to always process
  * messages or risk deadlock. Given our architecture the only way to meet
  * Windows' requirement and allow for synchronous IPC messages is to pump a
  * miniature message loop during a sync IPC call. We avoid processing any
- * queued messages during the loop, but "nonqueued" messages (see
+ * queued messages during the loop (with one exception, see below), but 
+ * "nonqueued" messages (see 
  * http://msdn.microsoft.com/en-us/library/ms644927(VS.85).aspx under the
  * section "Nonqueued messages") cannot be avoided. Those messages are trapped
  * in a special window procedure where we can either ignore the message or
  * process it in some fashion.
+ *
+ * Queued and "non-queued" messages will be processed during RPC calls if
+ * modal UI related api calls block an RPC in-call in the child. To prevent
+ * windows from freezing, and to allow concurrent processing of critical
+ * events (such as painting), we spin a native event dispatch loop while
+ * these in-calls are blocked.
  */
 
 namespace {
 
 UINT gEventLoopMessage =
     RegisterWindowMessage(L"SyncChannel Windows Message Loop Message");
 
+UINT gOOPPSpinNativeLoopEvent =
+    RegisterWindowMessage(L"SyncChannel Spin Inner Loop Message");
+
+UINT gOOPPStopNativeLoopEvent =
+    RegisterWindowMessage(L"SyncChannel Stop Inner Loop Message");
+
 const wchar_t kOldWndProcProp[] = L"MozillaIPCOldWndProc";
 
 // This isn't defined before Windows XP.
 enum { WM_XP_THEMECHANGED = 0x031A };
 
 PRUnichar gAppMessageWindowName[256] = { 0 };
 PRInt32 gAppMessageWindowNameLength = 0;
 
@@ -109,17 +124,16 @@ nsTArray<HWND>* gNeuteredWindows = nsnul
 
 typedef nsTArray<nsAutoPtr<DeferredMessage> > DeferredMessageArray;
 DeferredMessageArray* gDeferredMessages = nsnull;
 
 HHOOK gDeferredGetMsgHook = NULL;
 HHOOK gDeferredCallWndProcHook = NULL;
 
 DWORD gUIThreadId = 0;
-int gEventLoopDepth = 0;
 
 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
@@ -152,16 +166,33 @@ DeferredMessageHook(int nCode,
       messages->ElementAt(index)->Run();
     }
   }
 
   // Always call the next hook.
   return CallNextHookEx(NULL, nCode, wParam, lParam);
 }
 
+void
+ScheduleDeferredMessageRun()
+{
+  if (gDeferredMessages &&
+      !(gDeferredGetMsgHook && gDeferredCallWndProcHook)) {
+    NS_ASSERTION(gDeferredMessages->Length(), "No deferred messages?!");
+
+    gDeferredGetMsgHook = ::SetWindowsHookEx(WH_GETMESSAGE, DeferredMessageHook,
+                                             NULL, gUIThreadId);
+    gDeferredCallWndProcHook = ::SetWindowsHookEx(WH_CALLWNDPROC,
+                                                  DeferredMessageHook, NULL,
+                                                  gUIThreadId);
+    NS_ASSERTION(gDeferredGetMsgHook && gDeferredCallWndProcHook,
+                 "Failed to set hooks!");
+  }
+}
+
 LRESULT
 ProcessOrDeferMessage(HWND hwnd,
                       UINT uMsg,
                       WPARAM wParam,
                       LPARAM lParam)
 {
   DeferredMessage* deferred = nsnull;
 
@@ -247,16 +278,17 @@ ProcessOrDeferMessage(HWND hwnd,
 
     case WM_COPYDATA: {
       deferred = new DeferredCopyDataMessage(hwnd, uMsg, wParam, lParam);
       res = TRUE;
       break;
     }
 
     // Messages that are safe to pass to DefWindowProc go here.
+    case WM_ENTERIDLE:
     case WM_GETICON:
     case WM_GETMINMAXINFO:
     case WM_GETTEXT:
     case WM_NCHITTEST:
     case WM_SETICON:
     case WM_STYLECHANGING:
     case WM_STYLECHANGED:
     case WM_SYNCPAINT: // Intentional fall-through.
@@ -454,53 +486,166 @@ AssertWindowIsNotNeutered(HWND hWnd)
 {
 #ifdef DEBUG
   // Make sure our neutered window hook isn't still in place.
   LONG_PTR wndproc = GetWindowLongPtr(hWnd, GWLP_WNDPROC);
   NS_ASSERTION(wndproc != (LONG_PTR)NeuteredWindowProc, "Window is neutered!");
 #endif
 }
 
+void
+UnhookNeuteredWindows()
+{
+  if (!gNeuteredWindows)
+    return;
+  PRUint32 count = gNeuteredWindows->Length();
+  for (PRUint32 index = 0; index < count; index++) {
+    RestoreWindowProcedure(gNeuteredWindows->ElementAt(index));
+  }
+  gNeuteredWindows->Clear();
+}
+
+void
+Init()
+{
+  // If we aren't setup before a call to NotifyWorkerThread, we'll hang
+  // on startup.
+  if (!gUIThreadId) {
+    gUIThreadId = GetCurrentThreadId();
+  }
+  NS_ASSERTION(gUIThreadId, "ThreadId should not be 0!");
+  NS_ASSERTION(gUIThreadId == GetCurrentThreadId(),
+               "Running on different threads!");
+
+  if (!gNeuteredWindows) {
+    gNeuteredWindows = new nsAutoTArray<HWND, 20>();
+  }
+  NS_ASSERTION(gNeuteredWindows, "Out of memory!");
+}
+
 } // anonymous namespace
 
+bool
+RPCChannel::SpinInternalEventLoop()
+{
+  // Nested windows event loop that's triggered when the child enters into modal
+  // event procedures.
+  do {
+    MSG msg = { 0 };
+
+    // Don't get wrapped up in here if the child connection dies.
+    {
+      MutexAutoLock lock(mMutex);
+      if (!Connected()) {
+        RPCChannel::ExitModalLoop();
+        return false;
+      }
+    }
+
+    if (!RPCChannel::IsSpinLoopActive()) {
+      return false;
+    }
+
+    // If a modal loop in the child has exited, we want to disable the spin
+    // loop. However, we must continue to wait for a response from the last
+    // rpc call. Returning false here will cause the thread to drop down
+    // into deferred message processing.
+    if (PeekMessageW(&msg, (HWND)-1, gOOPPStopNativeLoopEvent,
+                     gOOPPStopNativeLoopEvent, PM_REMOVE)) {
+      RPCChannel::ExitModalLoop();
+      return false;
+    }
+
+    // At whatever depth we currently sit, a reply to the rpc call we were
+    // waiting for has been received. Exit out of here and respond to it.
+    // Returning true here causes the WaitForNotify() to return.
+    if (PeekMessageW(&msg, (HWND)-1, gEventLoopMessage, gEventLoopMessage,
+                     PM_REMOVE)) {
+      return true;
+    }
+
+    // Retrieve window or thread messages
+    if (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
+      if (msg.message == gOOPPStopNativeLoopEvent) {
+        RPCChannel::ExitModalLoop();
+        return false;
+      }
+      else if (msg.message == gOOPPSpinNativeLoopEvent) {
+        // Keep the spin loop counter accurate, multiple plugins can show ui.
+        RPCChannel::EnterModalLoop();
+        continue;
+      }
+      else if (msg.message == gEventLoopMessage) {
+        return true;
+      }
+
+      // The child UI should have been destroyed before the app is closed, in
+      // which case, we should never get this here.
+      if (msg.message == WM_QUIT) {
+          NS_ERROR("WM_QUIT received in SpinInternalEventLoop!");
+      } else {
+          TranslateMessage(&msg);
+          DispatchMessageW(&msg);
+      }
+    } else {
+      // Block and wait for any posted application messages
+      WaitMessage();
+    }
+  } while (true);
+}
+
+bool
+RPCChannel::IsMessagePending()
+{
+  MutexAutoLock lock(mMutex);
+  if (mStack.empty())
+    return true;
+
+  MessageMap::iterator it = mOutOfTurnReplies.find(mStack.top().seqno());
+  if (!mOutOfTurnReplies.empty() && 
+      it != mOutOfTurnReplies.end() &&
+      it->first == mStack.top().seqno())
+    return true;
+
+  if (!mPending.empty() &&
+      mPending.front().seqno() == mStack.top().seqno())
+    return true;
+
+  return false;
+}
+
 void
 SyncChannel::WaitForNotify()
 {
   mMutex.AssertCurrentThreadOwns();
 
-  NS_ASSERTION(gEventLoopDepth >= 0, "Event loop depth mismatch!");
-
-  HHOOK windowHook = NULL;
+  MutexAutoUnlock unlock(mMutex);
 
-  nsAutoTArray<HWND, 20> neuteredWindows;
-
-  if (++gEventLoopDepth == 1) {
-    NS_ASSERTION(!SyncChannel::IsPumpingMessages(),
-                 "Shouldn't be pumping already!");
-    SyncChannel::SetIsPumpingMessages(true);
+  // Initialize global objects used in deferred messaging.
+  Init();
 
-    if (!gUIThreadId) {
-      gUIThreadId = GetCurrentThreadId();
-    }
-    NS_ASSERTION(gUIThreadId, "ThreadId should not be 0!");
-    NS_ASSERTION(gUIThreadId == GetCurrentThreadId(),
-                 "Running on different threads!");
-
-    NS_ASSERTION(!gNeuteredWindows, "Should only set this once!");
-    gNeuteredWindows = &neuteredWindows;
-
-    windowHook = SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook,
-                                  NULL, gUIThreadId);
-    NS_ASSERTION(windowHook, "Failed to set hook!");
-  }
+  // Setup deferred processing of native events while we wait for a response.
+  NS_ASSERTION(!SyncChannel::IsPumpingMessages(),
+               "Shouldn't be pumping already!");
+  SyncChannel::SetIsPumpingMessages(true);
+  HHOOK windowHook = SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook,
+                                      NULL, gUIThreadId);
+  NS_ASSERTION(windowHook, "Failed to set hook!");
 
   {
-    MutexAutoUnlock unlock(mMutex);
+    while (1) {
+      MSG msg = { 0 };
+      // Don't get wrapped up in here if the child connection dies.
+      {
+        MutexAutoLock lock(mMutex);
+        if (!Connected()) {
+          return;
+        }
+      }
 
-    while (1) {
       // Wait until we have a message in the queue. MSDN docs are a bit unclear
       // but it seems that windows from two different threads (and it should be
       // noted that a thread in another process counts as a "different thread")
       // will implicitly have their message queues attached if they are parented
       // to one another. This wait call, then, will return for a message
       // delivered to *either* thread.
       DWORD result = MsgWaitForMultipleObjects(0, NULL, FALSE, INFINITE,
                                                QS_ALLINPUT);
@@ -522,17 +667,16 @@ SyncChannel::WaitForNotify()
       // 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.
 
       // We check first to see if we should break out of the loop by looking for
       // the special message from the IO thread. We pull it out of the queue.
-      MSG msg = { 0 };
       if (PeekMessageW(&msg, (HWND)-1, gEventLoopMessage, gEventLoopMessage,
                        PM_REMOVE)) {
         break;
       }
 
       // 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.
@@ -541,50 +685,177 @@ SyncChannel::WaitForNotify()
       if (!PeekMessageW(&msg, NULL, 0, 0, PM_NOREMOVE) &&
           !haveSentMessagesPending) {
         // Message was for child, we should wait a bit.
         SwitchToThread();
       }
     }
   }
 
-  NS_ASSERTION(gEventLoopDepth > 0, "Event loop depth mismatch!");
+  // Unhook the neutered window procedure hook.
+  UnhookWindowsHookEx(windowHook);
+
+  // Unhook any neutered windows procedures so messages can be delivered
+  // normally.
+  UnhookNeuteredWindows();
 
-  if (--gEventLoopDepth == 0) {
-    if (windowHook) {
-      UnhookWindowsHookEx(windowHook);
-    }
+  // Before returning we need to set a hook to run any deferred messages that
+  // we received during the IPC call. The hook will unset itself as soon as
+  // someone else calls GetMessage, PeekMessage, or runs code that generates
+  // a "nonqueued" message.
+  ScheduleDeferredMessageRun();
+
+  SyncChannel::SetIsPumpingMessages(false);
+}
+
+void
+RPCChannel::WaitForNotify()
+{
+  mMutex.AssertCurrentThreadOwns();
 
-    NS_ASSERTION(gNeuteredWindows == &neuteredWindows, "Bad pointer!");
-    gNeuteredWindows = nsnull;
+  if (!StackDepth()) {
+    NS_ASSERTION(!StackDepth(),
+        "StackDepth() is 0 in a call to RPCChannel::WaitForNotify?");
+    return;
+  }
+
+  MutexAutoUnlock unlock(mMutex);
+
+  // Initialize global objects used in deferred messaging.
+  Init();
 
-    PRUint32 count = neuteredWindows.Length();
-    for (PRUint32 index = 0; index < count; index++) {
-      RestoreWindowProcedure(neuteredWindows[index]);
+  // IsSpinLoopActive indicates modal UI is being displayed in a plugin. Drop
+  // down into the spin loop until all modal loops end. If SpinInternalEventLoop
+  // returns true, the out-call response we were waiting on arrived, or we
+  // received an in-call request from child, so return from WaitForNotify.
+  // We'll step back down into the spin loop on the next WaitForNotify call.
+  // If the spin loop returns false, the child's modal loop has ended, so
+  // drop down into "normal" deferred processing until the next reply is
+  // received. Note, spin loop can cause reentrant race conditions, which
+  // is expected.
+  if (RPCChannel::IsSpinLoopActive()) {
+    if (SpinInternalEventLoop()) {
+      return;
     }
 
-    SyncChannel::SetIsPumpingMessages(false);
-
-    // Before returning we need to set a hook to run any deferred messages that
-    // we received during the IPC call. The hook will unset itself as soon as
-    // someone else calls GetMessage, PeekMessage, or runs code that generates a
-    // "nonqueued" message.
-    if (gDeferredMessages &&
-        !(gDeferredGetMsgHook && gDeferredCallWndProcHook)) {
-      NS_ASSERTION(gDeferredMessages->Length(), "No deferred messages?!");
-
-      gDeferredGetMsgHook = SetWindowsHookEx(WH_GETMESSAGE, DeferredMessageHook,
-                                             NULL, gUIThreadId);
-      gDeferredCallWndProcHook = SetWindowsHookEx(WH_CALLWNDPROC,
-                                                  DeferredMessageHook, NULL,
-                                                  gUIThreadId);
-      NS_ASSERTION(gDeferredGetMsgHook && gDeferredCallWndProcHook,
-                   "Failed to set hooks!");
+    // Coming out of spin loop after a modal loop ends, we can't drop into
+    // deferred processing on the assumption that MsgWaitForMultipleObjects
+    // will get signaled with gEventLoopMessage - messages may have already
+    // been received and stored in mPending & mOutOfTurnReplies. So we check
+    // various stacks to figure out if we should fall through or just return.
+    if (IsMessagePending()) {
+      return;
     }
   }
+  
+  // Setup deferred processing of native events while we wait for a response.
+  NS_ASSERTION(!SyncChannel::IsPumpingMessages(),
+               "Shouldn't be pumping already!");
+  SyncChannel::SetIsPumpingMessages(true);
+  HHOOK windowHook = SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook,
+                                      NULL, gUIThreadId);
+  NS_ASSERTION(windowHook, "Failed to set hook!");
+
+  {
+    while (1) {
+      MSG msg = { 0 };
+
+      // Don't get wrapped up in here if the child connection dies.
+      {
+        MutexAutoLock lock(mMutex);
+        if (!Connected())
+          break;
+      }
+
+      DWORD result = MsgWaitForMultipleObjects(0, NULL, FALSE, INFINITE,
+                                               QS_ALLINPUT);
+      if (result != WAIT_OBJECT_0) {
+        NS_ERROR("Wait failed!");
+        break;
+      }
+
+      // See SyncChannel's WaitForNotify for details.
+      bool haveSentMessagesPending =
+        (HIWORD(GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0;
+
+      // This message is received from PluginInstanceParent when the child is
+      // about to drop into a modal event loop. Deferred "nonqueued" events and
+      // backed up queued events must be delivered before this happens, and
+      // normal event processing must resume, otherwise UI lockups can result.
+      // Unhook deferred processing, purge deferred events, and drop down into
+      // our local event dispatch loop.
+      if (PeekMessageW(&msg, (HWND)-1, gOOPPSpinNativeLoopEvent,
+                       gOOPPSpinNativeLoopEvent, PM_REMOVE)) {
+        // Unhook the neutered window procedure hook.
+        UnhookWindowsHookEx(windowHook);
+        windowHook = NULL;
+
+        // Used by widget to assert on incoming native events.
+        SyncChannel::SetIsPumpingMessages(false);
+
+        // Unhook any neutered windows procedures so messages can be delivered
+        // normally.
+        UnhookNeuteredWindows();
+
+        // Send all deferred "nonqueued" messages to the intended receiver.
+        // We're dropping into SpinInternalEventLoop so we should be fairly
+        // certain these will get deliverd soon.
+        ScheduleDeferredMessageRun();
+        
+        // Spin the internal dispatch message loop during calls to WaitForNotify
+        // until the child process tells us the modal loop has closed. A return
+        // of true indicates gEventLoopMessage was received, exit out of
+        // WaitForNotify so we can deal with it in RPCChannel.
+        RPCChannel::EnterModalLoop();
+        if (SpinInternalEventLoop())
+          return;
+
+        // See comments above - never assume MsgWaitForMultipleObjects will
+        // get signaled.
+        if (IsMessagePending())
+          return;
+
+        // We drop out of our inner event loop after the plugin has relinquished
+        // control back to the shim, but the ipdl call has yet to return, so reset
+        // our hook in case and continue waiting on gEventLoopMessage.
+        windowHook = SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook,
+                                      NULL, gUIThreadId);
+        NS_ASSERTION(windowHook, "Failed to set proc hook on the rebound!");
+
+        SyncChannel::SetIsPumpingMessages(true);
+        continue;
+      }
+
+      if (PeekMessageW(&msg, (HWND)-1, gEventLoopMessage, gEventLoopMessage,
+                       PM_REMOVE)) {
+        break;
+      }
+
+      if (!PeekMessageW(&msg, NULL, 0, 0, PM_NOREMOVE) &&
+          !haveSentMessagesPending) {
+        // Message was for child, we should wait a bit.
+        SwitchToThread();
+      }
+    }
+  }
+
+  // Unhook the neutered window procedure hook.
+  UnhookWindowsHookEx(windowHook);
+
+  // Unhook any neutered windows procedures so messages can be delivered
+  // normally.
+  UnhookNeuteredWindows();
+
+  // Before returning we need to set a hook to run any deferred messages that
+  // we received during the IPC call. The hook will unset itself as soon as
+  // someone else calls GetMessage, PeekMessage, or runs code that generates
+  // a "nonqueued" message.
+  ScheduleDeferredMessageRun();
+
+  SyncChannel::SetIsPumpingMessages(false);
 }
 
 void
 SyncChannel::NotifyWorkerThread()
 {
   mMutex.AssertCurrentThreadOwns();
   NS_ASSERTION(gUIThreadId, "This should have been set already!");
   if (!PostThreadMessage(gUIThreadId, gEventLoopMessage, 0, 0)) {
--- a/widget/src/windows/nsWindow.cpp
+++ b/widget/src/windows/nsWindow.cpp
@@ -100,17 +100,17 @@
  ** BLOCK: Includes
  **
  ** Include headers.
  **
  **************************************************************
  **************************************************************/
 
 #ifdef MOZ_IPC
-#include "mozilla/ipc/SyncChannel.h"
+#include "mozilla/ipc/RPCChannel.h"
 #endif
 
 #include "nsWindow.h"
 
 #include <windows.h>
 #include <process.h>
 #include <commctrl.h>
 #include <unknwn.h>
@@ -3603,16 +3603,81 @@ void nsWindow::SuppressBlurEvents(PRBool
 }
 
 PRBool nsWindow::ConvertStatus(nsEventStatus aStatus)
 {
   return aStatus == nsEventStatus_eConsumeNoDefault;
 }
 
 /**************************************************************
+ *
+ * SECTION: IPC
+ *
+ * IPC related helpers.
+ *
+ **************************************************************/
+
+#ifdef MOZ_IPC
+
+// static
+bool
+nsWindow::IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult)
+{
+  switch(aMsg) {
+    case WM_SETFOCUS:
+    case WM_KILLFOCUS:
+    case WM_ENABLE:
+    case WM_WINDOWPOSCHANGING:
+    case WM_WINDOWPOSCHANGED:
+    case WM_PARENTNOTIFY:
+    case WM_ACTIVATEAPP:
+    case WM_NCACTIVATE:
+    case WM_ACTIVATE:
+    case WM_CHILDACTIVATE:
+    case WM_IME_SETCONTEXT:
+    case WM_IME_NOTIFY:
+    case WM_SHOWWINDOW:
+    case WM_CANCELMODE:
+    case WM_MOUSEACTIVATE:
+      aResult = 0;
+    return true;
+
+    case WM_SETTINGCHANGE:
+    case WM_SETCURSOR:
+    return false;
+  }
+
+#ifdef DEBUG
+  char szBuf[200];
+  sprintf(szBuf,
+    "An unhandled ISMEX_SEND message was received during spin loop! (%X)", aMsg);
+  NS_WARNING(szBuf);
+#endif
+
+  return false;
+}
+
+// static
+void
+nsWindow::IPCWindowProcHandler(HWND& hWnd, UINT& msg, WPARAM& wParam, LPARAM& lParam)
+{
+  NS_ASSERTION(!mozilla::ipc::SyncChannel::IsPumpingMessages(),
+               "Failed to prevent a nonqueued message from running!");
+  if (mozilla::ipc::RPCChannel::IsSpinLoopActive() &&
+      (::InSendMessageEx(NULL)&(ISMEX_REPLIED|ISMEX_SEND)) == ISMEX_SEND) {
+    LRESULT res;
+    if (IsAsyncResponseEvent(msg, res)) {
+      ::ReplyMessage(res);
+    }
+  }
+}
+
+#endif // MOZ_IPC
+
+/**************************************************************
  **************************************************************
  **
  ** BLOCK: Native events
  **
  ** Main Windows message handlers and OnXXX handlers for
  ** Windows event handling.
  **
  **************************************************************
@@ -3626,18 +3691,17 @@ PRBool nsWindow::ConvertStatus(nsEventSt
  * message processing methods.
  *
  **************************************************************/
 
 // The WndProc procedure for all nsWindows in this toolkit
 LRESULT CALLBACK nsWindow::WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
 {
 #ifdef MOZ_IPC
-  NS_ASSERTION(!mozilla::ipc::SyncChannel::IsPumpingMessages(),
-               "Failed to prevent a nonqueued message from running!");
+  IPCWindowProcHandler(hWnd, msg, wParam, lParam);
 #endif
 
   // create this here so that we store the last rolled up popup until after
   // the event has been processed.
   nsAutoRollup autoRollup;
 
   LRESULT popupHandlingResult;
   if ( DealWithPopups(hWnd, msg, wParam, lParam, &popupHandlingResult) )
@@ -3761,16 +3825,22 @@ nsWindow::ProcessMessageForPlugin(const 
 // The main windows message processing method.
 PRBool nsWindow::ProcessMessage(UINT msg, WPARAM &wParam, LPARAM &lParam,
                                 LRESULT *aRetValue)
 {
   // (Large blocks of code should be broken out into OnEvent handlers.)
   if (mWindowHook.Notify(mWnd, msg, wParam, lParam, aRetValue))
     return PR_TRUE;
   
+#if defined(EVENT_DEBUG_OUTPUT)
+  // First param shows all events, second param indicates whether
+  // to show mouse move events. See nsWindowDbg for details.
+  PrintEvent(msg, SHOW_REPEAT_EVENTS, SHOW_MOUSEMOVE_EVENTS);
+#endif
+
   PRBool eatMessage;
   if (nsIMM32Handler::ProcessMessage(this, msg, wParam, lParam, aRetValue,
                                      eatMessage)) {
     return mWnd ? eatMessage : PR_TRUE;
   }
 
   if (PluginHasFocus()) {
     PRBool callDefaultWndProc;
@@ -3781,22 +3851,16 @@ PRBool nsWindow::ProcessMessage(UINT msg
   }
 
   static UINT vkKeyCached = 0; // caches VK code fon WM_KEYDOWN
   PRBool result = PR_FALSE;    // call the default nsWindow proc
   *aRetValue = 0;
 
   static PRBool getWheelInfo = PR_TRUE;
 
-#if defined(EVENT_DEBUG_OUTPUT)
-  // First param shows all events, second param indicates whether
-  // to show mouse move events. See nsWindowDbg for details.
-  PrintEvent(msg, SHOW_REPEAT_EVENTS, SHOW_MOUSEMOVE_EVENTS);
-#endif
-
   switch (msg) {
     case WM_COMMAND:
     {
       WORD wNotifyCode = HIWORD(wParam); // notification code
       if ((CBN_SELENDOK == wNotifyCode) || (CBN_SELENDCANCEL == wNotifyCode)) { // Combo box change
         nsGUIEvent event(PR_TRUE, NS_CONTROL_CHANGE, this);
         nsIntPoint point(0,0);
         InitEvent(event, &point); // this add ref's event.widget
--- a/widget/src/windows/nsWindow.h
+++ b/widget/src/windows/nsWindow.h
@@ -380,16 +380,21 @@ private:
   void                    SetWindowTranslucencyInner(nsTransparencyMode aMode);
   nsTransparencyMode      GetWindowTranslucencyInner() const { return mTransparencyMode; }
   void                    ResizeTranslucentWindow(PRInt32 aNewWidth, PRInt32 aNewHeight, PRBool force = PR_FALSE);
   nsresult                UpdateTranslucentWindow();
   void                    SetupTranslucentWindowMemoryBitmap(nsTransparencyMode aMode);
 protected:
 #endif // MOZ_XUL
 
+#ifdef MOZ_IPC
+  static bool             IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult);
+  static void             IPCWindowProcHandler(HWND& hWnd, UINT& msg, WPARAM& wParam, LPARAM& lParam);
+#endif // MOZ_IPC
+
   /**
    * Misc.
    */
   UINT                    MapFromNativeToDOM(UINT aNativeKeyCode);
   void                    StopFlashing();
   static PRBool           IsTopLevelMouseExit(HWND aWnd);
   static void             SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray, PRUint32 aModifiers);
   nsresult                SetWindowClipRegion(const nsTArray<nsIntRect>& aRects,
--- a/widget/src/windows/nsWindowGfx.cpp
+++ b/widget/src/windows/nsWindowGfx.cpp
@@ -350,16 +350,25 @@ PRBool nsWindow::OnPaint(HDC aDC)
     if (instance) {
       instance->CallUpdateWindow();
       ValidateRect(mWnd, NULL);
       return PR_TRUE;
     }
   }
 #endif
 
+#ifdef MOZ_IPC
+  // We never have reentrant paint events, except when we're running our RPC
+  // windows event spin loop. If we don't trap for this, we'll try to paint,
+  // but view manager will refuse to paint the surface, resulting is black
+  // flashes on the plugin rendering surface.
+  if (mozilla::ipc::RPCChannel::IsSpinLoopActive() && mPainting)
+    return PR_FALSE;
+#endif
+
   nsPaintEvent willPaintEvent(PR_TRUE, NS_WILL_PAINT, this);
   DispatchWindowEvent(&willPaintEvent);
 
 #ifdef CAIRO_HAS_DDRAW_SURFACE
   if (IsRenderMode(gfxWindowsPlatform::RENDER_IMAGE_DDRAW16)) {
     return OnPaintImageDDraw16();
   }
 #endif