--- 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