Bug 545149 - Fix various issues with winless plugin context menus. r=bent.

This commit is contained in:
Jim Mathies 2010-03-22 22:06:35 -05:00
parent 5de9282f0d
commit a149cb14ad
5 changed files with 169 additions and 65 deletions

View File

@ -58,6 +58,19 @@ using namespace mozilla::plugins;
#include <QX11Info>
#elif defined(OS_WIN)
#include "nsWindowsDllInterceptor.h"
typedef BOOL (WINAPI *User32TrackPopupMenu)(HMENU hMenu,
UINT uFlags,
int x,
int y,
int nReserved,
HWND hWnd,
CONST RECT *prcRect);
static WindowsDllInterceptor sUser32Intercept;
static HWND sWinlessPopupSurrogateHWND = NULL;
static User32TrackPopupMenu sUser32TrackPopupMenuStub = NULL;
using mozilla::gfx::SharedDIB;
#include <windows.h>
@ -65,13 +78,6 @@ using mozilla::gfx::SharedDIB;
#define NS_OOPP_DOUBLEPASS_MSGID TEXT("MozDoublePassMsg")
// 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,
@ -85,11 +91,10 @@ PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface,
, mPluginWndProc(0)
, mPluginParentHWND(0)
, mNestedEventHook(0)
, mNestedPumpHook(0)
, mNestedEventLevelDepth(0)
, mNestedEventState(false)
, mCachedWinlessPluginHWND(0)
, mEventPumpTimer(0)
, mWinlessPopupSurrogateHWND(0)
#endif // OS_WIN
{
memset(&mWindow, 0, sizeof(mWindow));
@ -108,6 +113,9 @@ PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface,
mAlphaExtract.doublePassEvent = ::RegisterWindowMessage(NS_OOPP_DOUBLEPASS_MSGID);
#endif // OS_WIN
InitQuirksModes(aMimeType);
#if defined(OS_WIN)
InitPopupMenuHook();
#endif // OS_WIN
}
PluginInstanceChild::~PluginInstanceChild()
@ -124,8 +132,14 @@ PluginInstanceChild::InitQuirksModes(const nsCString& aMimeType)
// application/x-silverlight
// application/x-silverlight-2
NS_NAMED_LITERAL_CSTRING(silverlight, "application/x-silverlight");
// application/x-shockwave-flash
NS_NAMED_LITERAL_CSTRING(flash, "application/x-shockwave-flash");
if (FindInReadable(silverlight, aMimeType)) {
mQuirks |= QUIRK_SILVERLIGHT_WINLESS_INPUT_TRANSLATION;
mQuirks |= QUIRK_WINLESS_TRACKPOPUP_HOOK;
}
else if (FindInReadable(flash, aMimeType)) {
mQuirks |= QUIRK_WINLESS_TRACKPOPUP_HOOK;
}
#endif
}
@ -616,6 +630,8 @@ PluginInstanceChild::AnswerNPP_SetWindow(const NPRemoteWindow& aWindow)
break;
case NPWindowTypeDrawable:
if (mQuirks & QUIRK_WINLESS_TRACKPOPUP_HOOK)
CreateWinlessPopupSurrogate();
return SharedSurfaceSetWindow(aWindow);
break;
@ -836,26 +852,6 @@ PluginInstanceChild::PluginWindowProc(HWND hWnd,
/* winless modal ui loop logic */
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
@ -875,7 +871,6 @@ PluginInstanceChild::NestedInputEventHook(int nCode,
if (nCode >= 0) {
NS_ASSERTION(gTempChildPointer, "Never should be null here!");
gTempChildPointer->ResetNestedEventHook();
gTempChildPointer->SetNestedInputPumpHook();
gTempChildPointer->InternalCallSetNestedEventState(true);
gTempChildPointer = NULL;
@ -883,35 +878,6 @@ PluginInstanceChild::NestedInputEventHook(int nCode,
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()
{
@ -951,6 +917,117 @@ PluginInstanceChild::InternalCallSetNestedEventState(bool aState)
}
}
/* windowless track popup menu helpers */
BOOL
WINAPI
PluginInstanceChild::TrackPopupHookProc(HMENU hMenu,
UINT uFlags,
int x,
int y,
int nReserved,
HWND hWnd,
CONST RECT *prcRect)
{
if (!sUser32TrackPopupMenuStub) {
NS_ERROR("TrackPopupMenu stub isn't set! Badness!");
return 0;
}
// Only change the parent when we know this is a context on the plugin
// surface within the browser. Prevents resetting the parent on child ui
// displayed by plugins that have working parent-child relationships.
PRUnichar szClass[21];
bool haveClass = GetClassNameW(hWnd, szClass, NS_ARRAY_LENGTH(szClass));
if (!haveClass ||
(wcscmp(szClass, L"MozillaWindowClass") &&
wcscmp(szClass, L"SWFlash_Placeholder"))) {
// Unrecognized parent
return sUser32TrackPopupMenuStub(hMenu, uFlags, x, y, nReserved,
hWnd, prcRect);
}
// Called on an unexpected event, warn.
if (!sWinlessPopupSurrogateHWND) {
NS_WARNING(
"Untraced TrackPopupHookProc call! Menu might not work right!");
return sUser32TrackPopupMenuStub(hMenu, uFlags, x, y, nReserved,
hWnd, prcRect);
}
HWND surrogateHwnd = sWinlessPopupSurrogateHWND;
sWinlessPopupSurrogateHWND = NULL;
// Popups that don't use TPM_RETURNCMD expect a final command message
// when an item is selected and the context closes. Since we replace
// the parent, we need to forward this back to the real parent so it
// can act on the menu item selected.
bool isRetCmdCall = (uFlags & TPM_RETURNCMD);
// A little trick scrounged from chromium's code - set the focus
// to our surrogate parent so keyboard nav events go to the menu.
HWND focusHwnd = SetFocus(surrogateHwnd);
DWORD res = sUser32TrackPopupMenuStub(hMenu, uFlags|TPM_RETURNCMD, x, y,
nReserved, surrogateHwnd, prcRect);
if (IsWindow(focusHwnd)) {
SetFocus(focusHwnd);
}
if (!isRetCmdCall && res) {
SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(res, 0), 0);
}
return res;
}
void
PluginInstanceChild::InitPopupMenuHook()
{
if (!(mQuirks & QUIRK_WINLESS_TRACKPOPUP_HOOK) ||
sUser32TrackPopupMenuStub)
return;
// Note, once WindowsDllInterceptor is initialized for a module,
// it remains initialized for that particular module for it's
// lifetime. Additional instances are needed if other modules need
// to be hooked.
sUser32Intercept.Init("user32.dll");
sUser32Intercept.AddHook("TrackPopupMenu", TrackPopupHookProc,
(void**) &sUser32TrackPopupMenuStub);
}
void
PluginInstanceChild::CreateWinlessPopupSurrogate()
{
// already initialized
if (mWinlessPopupSurrogateHWND)
return;
HWND hwnd = NULL;
NPError result;
if (!CallNPN_GetValue_NPNVnetscapeWindow(&hwnd, &result)) {
NS_ERROR("CallNPN_GetValue_NPNVnetscapeWindow failed.");
return;
}
mWinlessPopupSurrogateHWND =
CreateWindowEx(WS_EX_NOPARENTNOTIFY, L"Static", NULL, WS_CHILD, 0, 0,
0, 0, hwnd, 0, GetModuleHandle(NULL), 0);
if (!mWinlessPopupSurrogateHWND) {
NS_ERROR("CreateWindowEx failed for winless placeholder!");
return;
}
return;
}
void
PluginInstanceChild::DestroyWinlessPopupSurrogate()
{
if (mWinlessPopupSurrogateHWND)
DestroyWindow(mWinlessPopupSurrogateHWND);
mWinlessPopupSurrogateHWND = NULL;
}
/* windowless handle event helpers */
static bool
@ -1032,12 +1109,22 @@ PluginInstanceChild::WinlessHandleEvent(NPEvent& event)
SetNestedInputEventHook();
}
// TrackPopupMenu will fail if the parent window is not associated with
// our ui thread. So we hook TrackPopupMenu so we can hand in a surrogate
// parent created in the child process.
if ((mQuirks & QUIRK_WINLESS_TRACKPOPUP_HOOK) && // XXX turn on by default?
(event.event == WM_RBUTTONDOWN || // flash
event.event == WM_RBUTTONUP)) { // silverlight
sWinlessPopupSurrogateHWND = mWinlessPopupSurrogateHWND;
}
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;
sWinlessPopupSurrogateHWND = NULL;
mNestedEventLevelDepth--;
PLUGIN_LOG_DEBUG(("WinlessHandleEvent end depth: %i", mNestedEventLevelDepth));
@ -1045,7 +1132,6 @@ PluginInstanceChild::WinlessHandleEvent(NPEvent& event)
NS_ASSERTION(!(mNestedEventLevelDepth < 0), "mNestedEventLevelDepth < 0?");
if (mNestedEventLevelDepth <= 0) {
ResetNestedEventHook();
ResetPumpHooks();
InternalCallSetNestedEventState(false);
}
return handled;
@ -1596,7 +1682,7 @@ PluginInstanceChild::AnswerNPP_Destroy(NPError* aResult)
#if defined(OS_WIN)
SharedSurfaceRelease();
ResetNestedEventHook();
ResetPumpHooks();
DestroyWinlessPopupSurrogate();
#endif
return true;

View File

@ -197,7 +197,14 @@ private:
// Quirks mode support for various plugin mime types
enum PluginQuirks {
QUIRK_SILVERLIGHT_WINLESS_INPUT_TRANSLATION = 1, // Win32
// Win32: Translate mouse input based on WM_WINDOWPOSCHANGED
// windowing events due to winless shared dib rendering. See
// WinlessHandleEvent for details.
QUIRK_SILVERLIGHT_WINLESS_INPUT_TRANSLATION = 1,
// Win32: Hook TrackPopupMenu api so that we can swap out parent
// hwnds. The api will fail with parents not associated with our
// child ui thread. See WinlessHandleEvent for details.
QUIRK_WINLESS_TRACKPOPUP_HOOK = 2,
};
void InitQuirksModes(const nsCString& aMimeType);
@ -217,6 +224,9 @@ private:
void ResetNestedEventHook();
void SetNestedInputPumpHook();
void ResetPumpHooks();
void CreateWinlessPopupSurrogate();
void DestroyWinlessPopupSurrogate();
void InitPopupMenuHook();
void InternalCallSetNestedEventState(bool aState);
static LRESULT CALLBACK DummyWindowProc(HWND hWnd,
UINT message,
@ -236,6 +246,13 @@ private:
static LRESULT CALLBACK NestedInputPumpHook(int code,
WPARAM wParam,
LPARAM lParam);
static BOOL WINAPI TrackPopupHookProc(HMENU hMenu,
UINT uFlags,
int x,
int y,
int nReserved,
HWND hWnd,
CONST RECT *prcRect);
#endif
const NPPluginFuncs* mPluginIface;
@ -254,11 +271,10 @@ private:
WNDPROC mPluginWndProc;
HWND mPluginParentHWND;
HHOOK mNestedEventHook;
HHOOK mNestedPumpHook;
int mNestedEventLevelDepth;
bool mNestedEventState;
HWND mCachedWinlessPluginHWND;
UINT_PTR mEventPumpTimer;
HWND mWinlessPopupSurrogateHWND;
nsIntPoint mPluginSize;
nsIntPoint mPluginOffset;
#endif

View File

@ -92,6 +92,7 @@ endif
ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
CPPSRCS += nsNativeAppSupportWin.cpp
DEFINES += -DWIN32_LEAN_AND_MEAN -DUNICODE -D_UNICODE
EXPORTS = nsWindowsDllInterceptor.h
else
ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
CMMSRCS = nsNativeAppSupportCocoa.mm

View File

@ -76,7 +76,7 @@ public:
if (mModule)
return;
mModule = LoadLibraryEx(modulename, NULL, 0);
mModule = LoadLibraryExA(modulename, NULL, 0);
if (!mModule) {
//printf("LoadLibraryEx for '%s' failed\n", modulename);
return;

View File

@ -3733,6 +3733,7 @@ nsWindow::IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult)
case WM_SHOWWINDOW:
case WM_CANCELMODE:
case WM_MOUSEACTIVATE:
case WM_CONTEXTMENU:
aResult = 0;
return true;