Changeset 84638 in webkit


Ignore:
Timestamp:
Apr 22, 2011 9:48:35 AM (13 years ago)
Author:
Adam Roben
Message:

Give windowless plugins' context menus an owner window in the same thread as the plugin

In some cases, plugins pass the WKView's HWND to the ::TrackPopupMenu API. ::TrackPopupMenu
fails in this case because the WKView's HWND is owned by another process/thread. We work
around this by hooking the ::TrackPopupMenu API and providing our own window that's in the
same thread as the plugin instead.

I couldn't figure out how to write a test for this. :-(

Fixes <http://webkit.org/b/51063> <rdar://problem/8769281> REGRESSION (WebKit2): No context
menu appears when right-clicking on windowless Flash plugin

Reviewed by Brian Weinstein, and looked at suspiciously by Jeff Miller.

  • Platform/Module.h: Added installIATHook on Windows.
  • Platform/win/ModuleWin.cpp:

(WebKit::overwriteReadOnlyMemory): New helper function. Basically a wrapper around memcpy,
but uses ::VirtualProtect to allow writing to otherwise-read-only memory.
(WebKit::findFunctionPointerAddress): New helper function. The first overload iterates over
the imported modules and functions looking for the given one. If found, returns the address
of the function pointer that is used when calling that function. The second overload calls
the first, first passing it an enumerator for the image's imported modules, then passing it
an iterator for the image's delay-loaded modules.
(WebKit::Module::installIATHook): Added. Finds the address of the function pointer that's
used when code in this module calls the given function, then overwrites the function pointer
with the passed-in one. Future calls to the given function made by code in this module
should then end up calling to the passed-in function instead of the original one.

  • Shared/Plugins/Netscape/NetscapePluginModule.h:

(WebKit::NetscapePluginModule::module): Added this simple getter.

  • WebProcess/Plugins/Netscape/NetscapePlugin.h: Added some new Windows-only members.
  • WebProcess/Plugins/Netscape/win/NetscapePluginWin.cpp:

(WebKit::CurrentPluginSetter::CurrentPluginSetter):
(WebKit::CurrentPluginSetter::~CurrentPluginSetter):
This RAII class sets/unsets the currentPlugin global.

(WebKit::registerPluginView): Changed to use instanceHandle() instead of the mysterious
gInstance.
(WebKit::NetscapePlugin::platformPostInitialize): For windowless plugins, hook the
::TrackPopupMenu API and create a window to be used as the owner for context menus created
by this plugin. (Also changed the ::CreateWindowExW call for windowed plugins to pass an
instance handle so that the window will be associated with WebKit.dll instead of
WebKit2WebProcess.exe. This should have no visible effect, but is more correct.)
(WebKit::NetscapePlugin::platformDestroy): For windowless plugins, destroy the context menu
owner window we created above.

(WebKit::NetscapePlugin::platformPaint):
(WebKit::NetscapePlugin::platformHandleMouseEvent):
(WebKit::NetscapePlugin::platformHandleWheelEvent):
(WebKit::NetscapePlugin::platformSetFocus):
(WebKit::NetscapePlugin::platformHandleMouseEnterEvent):
(WebKit::NetscapePlugin::platformHandleMouseLeaveEvent):
(WebKit::NetscapePlugin::platformHandleKeyboardEvent):
Set ourselves as the current plugin.

(WebKit::NetscapePlugin::hookedTrackPopupMenu): Added. This is the function that actually
gets called when windowless plugins call ::TrackPopupMenu. If the passed-in owner window is
owned by the current thread, we have nothing to do; ::TrackPopupMenu should work just fine.
Otherwise, we use the current plugin's context menu owner window as the context menu's
owner. We also set focus to the new owner window because otherwise weird behavior results
(e.g., it's possible to scroll the WKView while the context menu is up).

Location:
trunk/Source/WebKit2
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebKit2/ChangeLog

    r84635 r84638  
     12011-04-21  Adam Roben  <aroben@apple.com>
     2
     3        Give windowless plugins' context menus an owner window in the same thread as the plugin
     4
     5        In some cases, plugins pass the WKView's HWND to the ::TrackPopupMenu API. ::TrackPopupMenu
     6        fails in this case because the WKView's HWND is owned by another process/thread. We work
     7        around this by hooking the ::TrackPopupMenu API and providing our own window that's in the
     8        same thread as the plugin instead.
     9
     10        I couldn't figure out how to write a test for this. :-(
     11
     12        Fixes <http://webkit.org/b/51063> <rdar://problem/8769281> REGRESSION (WebKit2): No context
     13        menu appears when right-clicking on windowless Flash plugin
     14
     15        Reviewed by Brian Weinstein, and looked at suspiciously by Jeff Miller.
     16
     17        * Platform/Module.h: Added installIATHook on Windows.
     18
     19        * Platform/win/ModuleWin.cpp:
     20        (WebKit::overwriteReadOnlyMemory): New helper function. Basically a wrapper around memcpy,
     21        but uses ::VirtualProtect to allow writing to otherwise-read-only memory.
     22        (WebKit::findFunctionPointerAddress): New helper function. The first overload iterates over
     23        the imported modules and functions looking for the given one. If found, returns the address
     24        of the function pointer that is used when calling that function. The second overload calls
     25        the first, first passing it an enumerator for the image's imported modules, then passing it
     26        an iterator for the image's delay-loaded modules.
     27        (WebKit::Module::installIATHook): Added. Finds the address of the function pointer that's
     28        used when code in this module calls the given function, then overwrites the function pointer
     29        with the passed-in one. Future calls to the given function made by code in this module
     30        should then end up calling to the passed-in function instead of the original one.
     31
     32        * Shared/Plugins/Netscape/NetscapePluginModule.h:
     33        (WebKit::NetscapePluginModule::module): Added this simple getter.
     34
     35        * WebProcess/Plugins/Netscape/NetscapePlugin.h: Added some new Windows-only members.
     36
     37        * WebProcess/Plugins/Netscape/win/NetscapePluginWin.cpp:
     38        (WebKit::CurrentPluginSetter::CurrentPluginSetter):
     39        (WebKit::CurrentPluginSetter::~CurrentPluginSetter):
     40        This RAII class sets/unsets the currentPlugin global.
     41
     42        (WebKit::registerPluginView): Changed to use instanceHandle() instead of the mysterious
     43        gInstance.
     44        (WebKit::NetscapePlugin::platformPostInitialize): For windowless plugins, hook the
     45        ::TrackPopupMenu API and create a window to be used as the owner for context menus created
     46        by this plugin. (Also changed the ::CreateWindowExW call for windowed plugins to pass an
     47        instance handle so that the window will be associated with WebKit.dll instead of
     48        WebKit2WebProcess.exe. This should have no visible effect, but is more correct.)
     49        (WebKit::NetscapePlugin::platformDestroy): For windowless plugins, destroy the context menu
     50        owner window we created above.
     51
     52        (WebKit::NetscapePlugin::platformPaint):
     53        (WebKit::NetscapePlugin::platformHandleMouseEvent):
     54        (WebKit::NetscapePlugin::platformHandleWheelEvent):
     55        (WebKit::NetscapePlugin::platformSetFocus):
     56        (WebKit::NetscapePlugin::platformHandleMouseEnterEvent):
     57        (WebKit::NetscapePlugin::platformHandleMouseLeaveEvent):
     58        (WebKit::NetscapePlugin::platformHandleKeyboardEvent):
     59        Set ourselves as the current plugin.
     60
     61        (WebKit::NetscapePlugin::hookedTrackPopupMenu): Added. This is the function that actually
     62        gets called when windowless plugins call ::TrackPopupMenu. If the passed-in owner window is
     63        owned by the current thread, we have nothing to do; ::TrackPopupMenu should work just fine.
     64        Otherwise, we use the current plugin's context menu owner window as the context menu's
     65        owner. We also set focus to the new owner window because otherwise weird behavior results
     66        (e.g., it's possible to scroll the WKView while the context menu is up).
     67
    1682011-04-21  Adam Roben  <aroben@apple.com>
    269
  • trunk/Source/WebKit2/Platform/Module.h

    r81617 r84638  
    6161#endif
    6262
     63#if PLATFORM(WIN)
     64    void installIATHook(const char* importDLLName, const char* importFunctionName, const void* hookFunction);
     65#endif
     66
    6367private:
    6468    void* platformFunctionPointer(const char* functionName) const;
  • trunk/Source/WebKit2/Platform/win/ModuleWin.cpp

    r76916 r84638  
    2727#include "Module.h"
    2828
     29#include <WebCore/DelayLoadedModulesEnumerator.h>
     30#include <WebCore/ImportedFunctionsEnumerator.h>
     31#include <WebCore/ImportedModulesEnumerator.h>
    2932#include <shlwapi.h>
     33
     34using namespace WebCore;
    3035
    3136namespace WebKit {
     
    4651}
    4752
     53static void memcpyToReadOnlyMemory(void* destination, const void* source, size_t size)
     54{
     55    DWORD originalProtection;
     56    if (!::VirtualProtect(destination, size, PAGE_READWRITE, &originalProtection))
     57        return;
     58
     59    memcpy(destination, source, size);
     60
     61    ::VirtualProtect(destination, size, originalProtection, &originalProtection);
     62}
     63
     64static const void* const* findFunctionPointerAddress(ImportedModulesEnumeratorBase& modules, const char* importDLLName, const char* importFunctionName)
     65{
     66    for (; !modules.isAtEnd(); modules.next()) {
     67        if (_stricmp(importDLLName, modules.currentModuleName()))
     68            continue;
     69
     70        for (ImportedFunctionsEnumerator functions = modules.functionsEnumerator(); !functions.isAtEnd(); functions.next()) {
     71            const char* currentFunctionName = functions.currentFunctionName();
     72            if (!currentFunctionName || strcmp(importFunctionName, currentFunctionName))
     73                continue;
     74
     75            return functions.addressOfCurrentFunctionPointer();
     76        }
     77
     78        break;
     79    }
     80
     81    return 0;
     82}
     83
     84static const void* const* findFunctionPointerAddress(HMODULE module, const char* importDLLName, const char* importFunctionName)
     85{
     86    PEImage image(module);
     87
     88    ImportedModulesEnumerator importedModules(image);
     89    if (const void* const* functionPointerAddress = findFunctionPointerAddress(importedModules, importDLLName, importFunctionName))
     90        return functionPointerAddress;
     91
     92    DelayLoadedModulesEnumerator delayLoadedModules(image);
     93    return findFunctionPointerAddress(delayLoadedModules, importDLLName, importFunctionName);
     94}
     95
     96void Module::installIATHook(const char* importDLLName, const char* importFunctionName, const void* hookFunction)
     97{
     98    if (!m_module)
     99        return;
     100
     101    // The Import Address Table (IAT) contains one function pointer for each function imported by
     102    // this module. When code in this module calls that function, the function pointer from the IAT
     103    // is used. We find that function pointer and overwrite it with hookFunction so that
     104    // hookFunction will be called instead.
     105
     106    const void* const* functionPointerAddress = findFunctionPointerAddress(m_module, importDLLName, importFunctionName);
     107    if (!functionPointerAddress || *functionPointerAddress == hookFunction)
     108        return;
     109
     110    memcpyToReadOnlyMemory(const_cast<const void**>(functionPointerAddress), &hookFunction, sizeof(hookFunction));
     111}
     112
    48113void* Module::platformFunctionPointer(const char* functionName) const
    49114{
  • trunk/Source/WebKit2/Shared/Plugins/Netscape/NetscapePluginModule.h

    r81157 r84638  
    6060    bool clearSiteData(const String& site, uint64_t flags, uint64_t maxAge);
    6161
     62    Module* module() const { return m_module.get(); }
     63
    6264private:
    6365    explicit NetscapePluginModule(const String& pluginPath);
  • trunk/Source/WebKit2/WebProcess/Plugins/Netscape/NetscapePlugin.h

    r81705 r84638  
    186186    virtual PluginController* controller();
    187187
     188#if PLUGIN_ARCHITECTURE(WIN)
     189    static BOOL WINAPI hookedTrackPopupMenu(HMENU, UINT uFlags, int x, int y, int nReserved, HWND, const RECT*);
     190#endif
     191
    188192    PluginController* m_pluginController;
    189193    uint64_t m_nextRequestID;
     
    235239#elif PLUGIN_ARCHITECTURE(WIN)
    236240    HWND m_window;
     241    HWND m_contextMenuOwnerWindow;
    237242#elif PLUGIN_ARCHITECTURE(X11)
    238243    Pixmap m_drawable;
  • trunk/Source/WebKit2/WebProcess/Plugins/Netscape/win/NetscapePluginWin.cpp

    r79335 r84638  
    2929#include "PluginController.h"
    3030#include "WebEvent.h"
     31#include <WebCore/DefWndProcWindowClass.h>
    3132#include <WebCore/GraphicsContext.h>
    3233#include <WebCore/LocalWindowsContext.h>
    3334#include <WebCore/NotImplemented.h>
     35#include <WebCore/WebCoreInstanceHandle.h>
    3436
    3537using namespace WebCore;
    3638
    37 extern "C" HINSTANCE gInstance;
    38 
    3939namespace WebKit {
     40
     41static NetscapePlugin* currentPlugin;
     42
     43class CurrentPluginSetter {
     44    WTF_MAKE_NONCOPYABLE(CurrentPluginSetter);
     45public:
     46    explicit CurrentPluginSetter(NetscapePlugin* plugin)
     47        : m_plugin(plugin)
     48        , m_formerCurrentPlugin(currentPlugin)
     49    {
     50        currentPlugin = m_plugin;
     51    }
     52
     53    ~CurrentPluginSetter()
     54    {
     55        ASSERT(currentPlugin == m_plugin);
     56        currentPlugin = m_formerCurrentPlugin;
     57    }
     58
     59private:
     60    NetscapePlugin* m_plugin;
     61    NetscapePlugin* m_formerCurrentPlugin;
     62};
    4063
    4164static LPCWSTR windowClassName = L"org.webkit.NetscapePluginWindow";
     
    5174    windowClass.style = CS_DBLCLKS;
    5275    windowClass.lpfnWndProc = ::DefWindowProcW;
    53     windowClass.hInstance = gInstance;
     76    windowClass.hInstance = instanceHandle();
    5477    windowClass.hCursor = ::LoadCursorW(0, IDC_ARROW);
    5578    windowClass.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
     
    6891    if (!m_isWindowed) {
    6992        m_window = 0;
     93
     94        // Windowless plugins need a little help showing context menus since our containingWindow()
     95        // is in a different process. See <http://webkit.org/b/51063>.
     96        m_pluginModule->module()->installIATHook("user32.dll", "TrackPopupMenu", hookedTrackPopupMenu);
     97        m_contextMenuOwnerWindow = ::CreateWindowExW(0, defWndProcWindowClassName(), 0, WS_CHILD, 0, 0, 0, 0, containingWindow(), 0, instanceHandle(), 0);
     98
    7099        return true;
    71100    }
     
    73102    registerPluginView();
    74103
    75     m_window = ::CreateWindowExW(0, windowClassName, 0, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, containingWindow(), 0, 0, 0);
     104    m_window = ::CreateWindowExW(0, windowClassName, 0, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, containingWindow(), 0, instanceHandle(), 0);
    76105    if (!m_window)
    77106        return false;
     
    89118{
    90119    if (!m_isWindowed) {
     120        ASSERT(m_contextMenuOwnerWindow);
     121        ::DestroyWindow(m_contextMenuOwnerWindow);
    91122        ASSERT(!m_window);
    92123        return;
     
    138169void NetscapePlugin::platformPaint(GraphicsContext* context, const IntRect& dirtyRect, bool)
    139170{
     171    CurrentPluginSetter setCurrentPlugin(this);
     172
    140173    // FIXME: Call SetWindow here if we haven't called it yet (see r59904).
    141174
     
    247280bool NetscapePlugin::platformHandleMouseEvent(const WebMouseEvent& event)
    248281{
     282    CurrentPluginSetter setCurrentPlugin(this);
     283
    249284    if (m_isWindowed)
    250285        return false;
     
    257292bool NetscapePlugin::platformHandleWheelEvent(const WebWheelEvent&)
    258293{
     294    CurrentPluginSetter setCurrentPlugin(this);
     295
    259296    notImplemented();
    260297    return false;
     
    263300void NetscapePlugin::platformSetFocus(bool)
    264301{
     302    CurrentPluginSetter setCurrentPlugin(this);
     303
    265304    notImplemented();
    266305}
     
    268307bool NetscapePlugin::platformHandleMouseEnterEvent(const WebMouseEvent& event)
    269308{
     309    CurrentPluginSetter setCurrentPlugin(this);
     310
    270311    if (m_isWindowed)
    271312        return false;
     
    278319bool NetscapePlugin::platformHandleMouseLeaveEvent(const WebMouseEvent& event)
    279320{
     321    CurrentPluginSetter setCurrentPlugin(this);
     322
    280323    if (m_isWindowed)
    281324        return false;
     
    288331bool NetscapePlugin::platformHandleKeyboardEvent(const WebKeyboardEvent&)
    289332{
     333    CurrentPluginSetter setCurrentPlugin(this);
     334
    290335    notImplemented();
    291336    return false;
    292337}
    293338
     339BOOL NetscapePlugin::hookedTrackPopupMenu(HMENU hMenu, UINT uFlags, int x, int y, int nReserved, HWND hWnd, const RECT* prcRect)
     340{
     341    // ::TrackPopupMenu fails when it is passed a window that is owned by another thread. If this
     342    // happens, we substitute a dummy window that is owned by this thread.
     343
     344    if (::GetWindowThreadProcessId(hWnd, 0) == ::GetCurrentThreadId())
     345        return ::TrackPopupMenu(hMenu, uFlags, x, y, nReserved, hWnd, prcRect);
     346
     347    HWND originalFocusWindow = 0;
     348
     349    ASSERT(currentPlugin);
     350    if (currentPlugin) {
     351        ASSERT(!currentPlugin->m_isWindowed);
     352        ASSERT(currentPlugin->m_contextMenuOwnerWindow);
     353        ASSERT(::GetWindowThreadProcessId(currentPlugin->m_contextMenuOwnerWindow, 0) == ::GetCurrentThreadId());
     354        hWnd = currentPlugin->m_contextMenuOwnerWindow;
     355
     356        // If we don't focus the dummy window, the user will be able to scroll the page while the
     357        // context menu is up, e.g.
     358        originalFocusWindow = ::SetFocus(hWnd);
     359    }
     360
     361    BOOL result = ::TrackPopupMenu(hMenu, uFlags, x, y, nReserved, hWnd, prcRect);
     362
     363    if (originalFocusWindow)
     364        ::SetFocus(originalFocusWindow);
     365
     366    return result;
     367}
     368
    294369} // namespace WebKit
Note: See TracChangeset for help on using the changeset viewer.