Bug 545149 - Fix various issues with winless plugin context menus. r=bent.
authorJim Mathies <jmathies@mozilla.com>
Mon, 22 Mar 2010 22:06:35 -0500
changeset 39729 60f50bdbc54495b1631f5fc17d905a59807d3822
parent 39728 9ca2eda683074f5bd033c98c99586d17d5f43b99
child 39730 1ef04d9006bfec50b86537cff8f5d68ce1a1a5ed
push idunknown
push userunknown
push dateunknown
reviewersbent
bugs545149
milestone1.9.3a4pre
Bug 545149 - Fix various issues with winless plugin context menus. r=bent.
dom/plugins/PluginInstanceChild.cpp
dom/plugins/PluginInstanceChild.h
toolkit/xre/Makefile.in
toolkit/xre/nsWindowsDllInterceptor.h
widget/src/windows/nsWindow.cpp
--- a/dom/plugins/PluginInstanceChild.cpp
+++ b/dom/plugins/PluginInstanceChild.cpp
@@ -53,48 +53,53 @@ using namespace mozilla::plugins;
 #include <gdk/gdkx.h>
 #include <gdk/gdk.h>
 #include "gtk2xtbin.h"
 
 #elif defined(MOZ_WIDGET_QT)
 #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>
 #include <windowsx.h>
 
 #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,
                                          const nsCString& aMimeType)
     : mPluginIface(aPluginIface)
     , mQuirks(0)
     , mCachedWindowActor(nsnull)
     , mCachedElementActor(nsnull)
 #if defined(OS_WIN)
     , mPluginWindowHWND(0)
     , 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));
     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
@@ -103,34 +108,43 @@ PluginInstanceChild::PluginInstanceChild
     mWsInfo.display = QX11Info::display();
 #endif // MOZ_WIDGET_GTK2
 #endif // MOZ_X11 && XP_UNIX && !XP_MACOSX
 #if defined(OS_WIN)
     memset(&mAlphaExtract, 0, sizeof(mAlphaExtract));
     mAlphaExtract.doublePassEvent = ::RegisterWindowMessage(NS_OOPP_DOUBLEPASS_MSGID);
 #endif // OS_WIN
     InitQuirksModes(aMimeType);
+#if defined(OS_WIN)
+    InitPopupMenuHook();
+#endif // OS_WIN
 }
 
 PluginInstanceChild::~PluginInstanceChild()
 {
 #if defined(OS_WIN)
   DestroyPluginWindow();
 #endif
 }
 
 void
 PluginInstanceChild::InitQuirksModes(const nsCString& aMimeType)
 {
 #ifdef OS_WIN
     // 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
 }
 
 NPError
 PluginInstanceChild::InternalGetNPObjectForValue(NPNVariable aValue,
                                                  NPObject** aObject)
 {
@@ -611,16 +625,18 @@ PluginInstanceChild::AnswerNPP_SetWindow
                       SetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC,
                                        reinterpret_cast<LONG>(PluginWindowProc)));
               }
           }
       }
       break;
 
       case NPWindowTypeDrawable:
+          if (mQuirks & QUIRK_WINLESS_TRACKPOPUP_HOOK)
+              CreateWinlessPopupSurrogate();
           return SharedSurfaceSetWindow(aWindow);
       break;
 
       default:
           NS_NOTREACHED("Bad plugin window type.");
           return false;
       break;
     }
@@ -831,36 +847,16 @@ PluginInstanceChild::PluginWindowProc(HW
     if (message == WM_NCDESTROY)
         RemoveProp(hWnd, kPluginInstanceChildProperty);
 
     return res;
 }
 
 /* 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
 // spin loop until InternalCallSetNestedEventState is set, at which point,
 // gTempChildPointer is no longer needed.
 static PluginInstanceChild* gTempChildPointer;
 
 LRESULT CALLBACK
@@ -870,54 +866,24 @@ PluginInstanceChild::NestedInputEventHoo
 {
     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
@@ -946,16 +912,127 @@ PluginInstanceChild::InternalCallSetNest
         PLUGIN_LOG_DEBUG(
             ("PluginInstanceChild::InternalCallSetNestedEventState(%i)",
             (int)aState));
         mNestedEventState = aState;
         SendSetNestedEventState(mNestedEventState);
     }
 }
 
+/* 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
 NeedsNestedEventCoverage(UINT msg)
 {
     // Events we assume some sort of modal ui *might* be generated.
     switch (msg) {
         case WM_LBUTTONUP:
@@ -1027,30 +1104,39 @@ PluginInstanceChild::WinlessHandleEvent(
 
     // On the first, non-reentrant call, setup our modal ui detection hook.
     if (mNestedEventLevelDepth == 1) {
         NS_ASSERTION(!gTempChildPointer, "valid gTempChildPointer here?");
         gTempChildPointer = this;
         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));
 
     NS_ASSERTION(!(mNestedEventLevelDepth < 0), "mNestedEventLevelDepth < 0?");
     if (mNestedEventLevelDepth <= 0) {
         ResetNestedEventHook();
-        ResetPumpHooks();
         InternalCallSetNestedEventState(false);
     }
     return handled;
 }
 
 /* windowless drawing helpers */
 
 bool
@@ -1591,13 +1677,13 @@ PluginInstanceChild::AnswerNPP_Destroy(N
     // Null out our cached actors as they should have been killed in the
     // PluginInstanceDestroyed call above.
     mCachedWindowActor = nsnull;
     mCachedElementActor = nsnull;
 
 #if defined(OS_WIN)
     SharedSurfaceRelease();
     ResetNestedEventHook();
-    ResetPumpHooks();
+    DestroyWinlessPopupSurrogate();
 #endif
 
     return true;
 }
--- a/dom/plugins/PluginInstanceChild.h
+++ b/dom/plugins/PluginInstanceChild.h
@@ -192,17 +192,24 @@ public:
     uint32_t ScheduleTimer(uint32_t interval, bool repeat, TimerFunc func);
     void UnscheduleTimer(uint32_t id);
 
 private:
     friend class PluginModuleChild;
 
     // 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);
 
     NPError
     InternalGetNPObjectForValue(NPNVariable aValue,
                                 NPObject** aObject);
 
@@ -212,16 +219,19 @@ private:
     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 CreateWinlessPopupSurrogate();
+    void DestroyWinlessPopupSurrogate();
+    void InitPopupMenuHook();
     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,
@@ -231,16 +241,23 @@ private:
                                        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);
+    static BOOL WINAPI TrackPopupHookProc(HMENU hMenu,
+                                          UINT uFlags,
+                                          int x,
+                                          int y,
+                                          int nReserved,
+                                          HWND hWnd,
+                                          CONST RECT *prcRect);
 #endif
 
     const NPPluginFuncs* mPluginIface;
     NPP_t mData;
     NPWindow mWindow;
     int mQuirks;
 
     // Cached scriptable actors to avoid IPC churn
@@ -249,21 +266,20 @@ private:
 
 #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;
+    HWND mWinlessPopupSurrogateHWND;
     nsIntPoint mPluginSize;
     nsIntPoint mPluginOffset;
 #endif
 
     friend class ChildAsyncCall;
     nsTArray<ChildAsyncCall*> mPendingAsyncCalls;
     nsTArray<nsAutoPtr<ChildTimer> > mTimers;
 
--- a/toolkit/xre/Makefile.in
+++ b/toolkit/xre/Makefile.in
@@ -87,16 +87,17 @@ endif
 ifdef MOZ_UPDATER
 CPPSRCS += nsUpdateDriver.cpp
 DEFINES += -DMOZ_UPDATER
 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
 else
 ifeq ($(MOZ_WIDGET_TOOLKIT),os2)
 CPPSRCS += nsNativeAppSupportOS2.cpp
 else
 ifeq ($(MOZ_WIDGET_TOOLKIT),beos)
--- a/toolkit/xre/nsWindowsDllInterceptor.h
+++ b/toolkit/xre/nsWindowsDllInterceptor.h
@@ -71,17 +71,17 @@ public:
   WindowsDllInterceptor(const char *modulename, int nhooks = 0) {
     Init(modulename, nhooks);
   }
 
   void Init(const char *modulename, int nhooks = 0) {
     if (mModule)
       return;
 
-    mModule = LoadLibraryEx(modulename, NULL, 0);
+    mModule = LoadLibraryExA(modulename, NULL, 0);
     if (!mModule) {
       //printf("LoadLibraryEx for '%s' failed\n", modulename);
       return;
     }
 
     int hooksPerPage = 4096 / kHookSize;
     if (nhooks == 0)
       nhooks = hooksPerPage;
--- a/widget/src/windows/nsWindow.cpp
+++ b/widget/src/windows/nsWindow.cpp
@@ -3728,16 +3728,17 @@ nsWindow::IsAsyncResponseEvent(UINT aMsg
     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:
+    case WM_CONTEXTMENU:
       aResult = 0;
     return true;
 
     case WM_SETTINGCHANGE:
     case WM_SETCURSOR:
     return false;
   }