blob: 07102c1d32c7eb8dfb100176b4ca2a633d5ba9b4 [file] [log] [blame]
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "webkit/plugins/npapi/webplugin_delegate_impl.h"
#include <map>
#include <string>
#include <vector>
#include "base/file_util.h"
#include "base/lazy_instance.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "base/metrics/stats_counters.h"
#include "base/string_number_conversions.h"
#include "base/string_split.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/version.h"
#include "base/win/iat_patch_function.h"
#include "base/win/registry.h"
#include "base/win/windows_version.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h"
#include "webkit/glue/webkit_glue.h"
#include "webkit/plugins/npapi/default_plugin_shared.h"
#include "webkit/plugins/npapi/plugin_constants_win.h"
#include "webkit/plugins/npapi/plugin_group.h"
#include "webkit/plugins/npapi/plugin_instance.h"
#include "webkit/plugins/npapi/plugin_lib.h"
#include "webkit/plugins/npapi/plugin_list.h"
#include "webkit/plugins/npapi/plugin_stream_url.h"
#include "webkit/plugins/npapi/webplugin.h"
using WebKit::WebCursorInfo;
using WebKit::WebKeyboardEvent;
using WebKit::WebInputEvent;
using WebKit::WebMouseEvent;
namespace webkit {
namespace npapi {
namespace {
const wchar_t kWebPluginDelegateProperty[] = L"WebPluginDelegateProperty";
const wchar_t kPluginNameAtomProperty[] = L"PluginNameAtom";
const wchar_t kDummyActivationWindowName[] = L"DummyWindowForActivation";
const wchar_t kPluginFlashThrottle[] = L"FlashThrottle";
// The fastest we are willing to process WM_USER+1 events for Flash.
// Flash can easily exceed the limits of our CPU if we don't throttle it.
// The throttle has been chosen by testing various delays and compromising
// on acceptable Flash performance and reasonable CPU consumption.
//
// I'd like to make the throttle delay variable, based on the amount of
// time currently required to paint Flash plugins. There isn't a good
// way to count the time spent in aggregate plugin painting, however, so
// this seems to work well enough.
const int kFlashWMUSERMessageThrottleDelayMs = 5;
// Flash displays popups in response to user clicks by posting a WM_USER
// message to the plugin window. The handler for this message displays
// the popup. To ensure that the popups allowed state is sent correctly
// to the renderer we reset the popups allowed state in a timer.
const int kWindowedPluginPopupTimerMs = 50;
// The current instance of the plugin which entered the modal loop.
WebPluginDelegateImpl* g_current_plugin_instance = NULL;
typedef std::deque<MSG> ThrottleQueue;
base::LazyInstance<ThrottleQueue> g_throttle_queue(base::LINKER_INITIALIZED);
base::LazyInstance<std::map<HWND, WNDPROC> > g_window_handle_proc_map(
base::LINKER_INITIALIZED);
// Helper object for patching the TrackPopupMenu API.
base::LazyInstance<base::win::IATPatchFunction> g_iat_patch_track_popup_menu(
base::LINKER_INITIALIZED);
// Helper object for patching the SetCursor API.
base::LazyInstance<base::win::IATPatchFunction> g_iat_patch_set_cursor(
base::LINKER_INITIALIZED);
// Helper object for patching the RegEnumKeyExW API.
base::LazyInstance<base::win::IATPatchFunction> g_iat_patch_reg_enum_key_ex_w(
base::LINKER_INITIALIZED);
// Helper object for patching the GetKeyState API.
base::LazyInstance<base::win::IATPatchFunction> g_iat_patch_get_key_state(
base::LINKER_INITIALIZED);
// Saved key state globals and helper access functions.
SHORT (WINAPI *g_iat_orig_get_key_state)(int vkey);
typedef size_t SavedStateType;
const size_t kBitsPerType = sizeof(SavedStateType) * 8;
// Bit array of key state corresponding to virtual key index (0=up, 1=down).
SavedStateType g_saved_key_state[256 / kBitsPerType];
bool GetSavedKeyState(WPARAM vkey) {
CHECK_LT(vkey, kBitsPerType * sizeof(g_saved_key_state));
if (g_saved_key_state[vkey / kBitsPerType] & 1 << (vkey % kBitsPerType))
return true;
return false;
}
void SetSavedKeyState(WPARAM vkey) {
CHECK_LT(vkey, kBitsPerType * sizeof(g_saved_key_state));
// Cache the key state only for keys blocked by UIPI.
if (g_iat_orig_get_key_state(vkey) == 0)
g_saved_key_state[vkey / kBitsPerType] |= 1 << (vkey % kBitsPerType);
}
void UnsetSavedKeyState(WPARAM vkey) {
CHECK_LT(vkey, kBitsPerType * sizeof(g_saved_key_state));
g_saved_key_state[vkey / kBitsPerType] &= ~(1 << (vkey % kBitsPerType));
}
void ClearSavedKeyState() {
memset(g_saved_key_state, 0, sizeof(g_saved_key_state));
}
// http://crbug.com/16114
// Enforces providing a valid device context in NPWindow, so that NPP_SetWindow
// is never called with NPNWindoTypeDrawable and NPWindow set to NULL.
// Doing so allows removing NPP_SetWindow call during painting a windowless
// plugin, which otherwise could trigger layout change while painting by
// invoking NPN_Evaluate. Which would cause bad, bad crashes. Bad crashes.
// TODO(dglazkov): If this approach doesn't produce regressions, move class to
// webplugin_delegate_impl.h and implement for other platforms.
class DrawableContextEnforcer {
public:
explicit DrawableContextEnforcer(NPWindow* window)
: window_(window),
disposable_dc_(window && !window->window) {
// If NPWindow is NULL, create a device context with monochrome 1x1 surface
// and stuff it to NPWindow.
if (disposable_dc_)
window_->window = CreateCompatibleDC(NULL);
}
~DrawableContextEnforcer() {
if (!disposable_dc_)
return;
DeleteDC(static_cast<HDC>(window_->window));
window_->window = NULL;
}
private:
NPWindow* window_;
bool disposable_dc_;
};
// These are from ntddk.h
typedef LONG NTSTATUS;
#ifndef STATUS_SUCCESS
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#endif
#ifndef STATUS_BUFFER_TOO_SMALL
#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
#endif
typedef enum _KEY_INFORMATION_CLASS {
KeyBasicInformation,
KeyNodeInformation,
KeyFullInformation,
KeyNameInformation,
KeyCachedInformation,
KeyVirtualizationInformation
} KEY_INFORMATION_CLASS;
typedef struct _KEY_NAME_INFORMATION {
ULONG NameLength;
WCHAR Name[1];
} KEY_NAME_INFORMATION, *PKEY_NAME_INFORMATION;
typedef DWORD (__stdcall *ZwQueryKeyType)(
HANDLE key_handle,
int key_information_class,
PVOID key_information,
ULONG length,
PULONG result_length);
// Returns a key's full path.
std::wstring GetKeyPath(HKEY key) {
if (key == NULL)
return L"";
HMODULE dll = GetModuleHandle(L"ntdll.dll");
if (dll == NULL)
return L"";
ZwQueryKeyType func = reinterpret_cast<ZwQueryKeyType>(
::GetProcAddress(dll, "ZwQueryKey"));
if (func == NULL)
return L"";
DWORD size = 0;
DWORD result = 0;
result = func(key, KeyNameInformation, 0, 0, &size);
if (result != STATUS_BUFFER_TOO_SMALL)
return L"";
scoped_array<char> buffer(new char[size]);
if (buffer.get() == NULL)
return L"";
result = func(key, KeyNameInformation, buffer.get(), size, &size);
if (result != STATUS_SUCCESS)
return L"";
KEY_NAME_INFORMATION* info =
reinterpret_cast<KEY_NAME_INFORMATION*>(buffer.get());
return std::wstring(info->Name, info->NameLength / sizeof(wchar_t));
}
int GetPluginMajorVersion(const WebPluginInfo& plugin_info) {
scoped_ptr<Version> plugin_version(PluginGroup::CreateVersionFromString(
plugin_info.version));
int major_version = 0;
if (plugin_version.get()) {
major_version = plugin_version->components()[0];
}
return major_version;
}
} // namespace
bool WebPluginDelegateImpl::IsPluginDelegateWindow(HWND window) {
static const int kBufLen = 64;
wchar_t class_name[kBufLen];
if (!GetClassNameW(window, class_name, kBufLen))
return false;
return wcscmp(class_name, kNativeWindowClassName) == 0;
}
// static
bool WebPluginDelegateImpl::GetPluginNameFromWindow(
HWND window, string16* plugin_name) {
if (NULL == plugin_name) {
return false;
}
if (!IsPluginDelegateWindow(window)) {
return false;
}
ATOM plugin_name_atom = reinterpret_cast<ATOM>(
GetPropW(window, kPluginNameAtomProperty));
if (plugin_name_atom != 0) {
WCHAR plugin_name_local[MAX_PATH] = {0};
GlobalGetAtomNameW(plugin_name_atom,
plugin_name_local,
ARRAYSIZE(plugin_name_local));
*plugin_name = plugin_name_local;
return true;
}
return false;
}
bool WebPluginDelegateImpl::IsDummyActivationWindow(HWND window) {
if (!IsWindow(window))
return false;
wchar_t window_title[MAX_PATH + 1] = {0};
if (GetWindowText(window, window_title, arraysize(window_title))) {
return (0 == lstrcmpiW(window_title, kDummyActivationWindowName));
}
return false;
}
LRESULT CALLBACK WebPluginDelegateImpl::HandleEventMessageFilterHook(
int code, WPARAM wParam, LPARAM lParam) {
if (g_current_plugin_instance) {
g_current_plugin_instance->OnModalLoopEntered();
} else {
NOTREACHED();
}
return CallNextHookEx(NULL, code, wParam, lParam);
}
LRESULT CALLBACK WebPluginDelegateImpl::MouseHookProc(
int code, WPARAM wParam, LPARAM lParam) {
if (code == HC_ACTION) {
MOUSEHOOKSTRUCT* hook_struct = reinterpret_cast<MOUSEHOOKSTRUCT*>(lParam);
if (hook_struct)
HandleCaptureForMessage(hook_struct->hwnd, wParam);
}
return CallNextHookEx(NULL, code, wParam, lParam);
}
// In addition to the key state we maintain, we also mask in the original
// return value. This is done because system keys (e.g. tab, enter, shift)
// and toggles (e.g. capslock, numlock) don't ever seem to be blocked.
SHORT WINAPI WebPluginDelegateImpl::GetKeyStatePatch(int vkey) {
if (GetSavedKeyState(vkey))
return g_iat_orig_get_key_state(vkey) | 0x8000;
return g_iat_orig_get_key_state(vkey);
}
WebPluginDelegateImpl::WebPluginDelegateImpl(
gfx::PluginWindowHandle containing_view,
PluginInstance *instance)
: parent_(containing_view),
instance_(instance),
quirks_(0),
plugin_(NULL),
windowless_(false),
windowed_handle_(NULL),
windowed_did_set_window_(false),
plugin_wnd_proc_(NULL),
last_message_(0),
is_calling_wndproc(false),
dummy_window_for_activation_(NULL),
parent_proxy_window_(NULL),
handle_event_message_filter_hook_(NULL),
handle_event_pump_messages_event_(NULL),
user_gesture_message_posted_(false),
#pragma warning(suppress: 4355) // can use this
user_gesture_msg_factory_(this),
handle_event_depth_(0),
mouse_hook_(NULL),
first_set_window_call_(true),
plugin_has_focus_(false),
has_webkit_focus_(false),
containing_view_has_focus_(true),
creation_succeeded_(false) {
memset(&window_, 0, sizeof(window_));
const WebPluginInfo& plugin_info = instance_->plugin_lib()->plugin_info();
std::wstring filename =
StringToLowerASCII(plugin_info.path.BaseName().value());
if (instance_->mime_type() == "application/x-shockwave-flash" ||
filename == kFlashPlugin) {
// Flash only requests windowless plugins if we return a Mozilla user
// agent.
instance_->set_use_mozilla_user_agent();
quirks_ |= PLUGIN_QUIRK_THROTTLE_WM_USER_PLUS_ONE;
quirks_ |= PLUGIN_QUIRK_PATCH_SETCURSOR;
quirks_ |= PLUGIN_QUIRK_ALWAYS_NOTIFY_SUCCESS;
quirks_ |= PLUGIN_QUIRK_HANDLE_MOUSE_CAPTURE;
if (filename == kBuiltinFlashPlugin &&
base::win::GetVersion() >= base::win::VERSION_VISTA) {
quirks_ |= PLUGIN_QUIRK_REPARENT_IN_BROWSER |
PLUGIN_QUIRK_PATCH_GETKEYSTATE;
}
} else if (filename == kAcrobatReaderPlugin) {
// Check for the version number above or equal 9.
int major_version = GetPluginMajorVersion(plugin_info);
if (major_version >= 9) {
quirks_ |= PLUGIN_QUIRK_DIE_AFTER_UNLOAD;
// 9.2 needs this.
quirks_ |= PLUGIN_QUIRK_SETWINDOW_TWICE;
}
quirks_ |= PLUGIN_QUIRK_BLOCK_NONSTANDARD_GETURL_REQUESTS;
} else if (plugin_info.name.find(L"Windows Media Player") !=
std::wstring::npos) {
// Windows Media Player needs two NPP_SetWindow calls.
quirks_ |= PLUGIN_QUIRK_SETWINDOW_TWICE;
// Windowless mode doesn't work in the WMP NPAPI plugin.
quirks_ |= PLUGIN_QUIRK_NO_WINDOWLESS;
// The media player plugin sets its size on the first NPP_SetWindow call
// and never updates its size. We should call the underlying NPP_SetWindow
// only when we have the correct size.
quirks_ |= PLUGIN_QUIRK_IGNORE_FIRST_SETWINDOW_CALL;
if (filename == kOldWMPPlugin) {
// Non-admin users on XP couldn't modify the key to force the new UI.
quirks_ |= PLUGIN_QUIRK_PATCH_REGENUMKEYEXW;
}
} else if (instance_->mime_type() == "audio/x-pn-realaudio-plugin" ||
filename == kRealPlayerPlugin) {
quirks_ |= PLUGIN_QUIRK_DONT_CALL_WND_PROC_RECURSIVELY;
} else if (plugin_info.name.find(L"VLC Multimedia Plugin") !=
std::wstring::npos ||
plugin_info.name.find(L"VLC Multimedia Plug-in") !=
std::wstring::npos) {
// VLC hangs on NPP_Destroy if we call NPP_SetWindow with a null window
// handle
quirks_ |= PLUGIN_QUIRK_DONT_SET_NULL_WINDOW_HANDLE_ON_DESTROY;
int major_version = GetPluginMajorVersion(plugin_info);
if (major_version == 0) {
// VLC 0.8.6d and 0.8.6e crash if multiple instances are created.
quirks_ |= PLUGIN_QUIRK_DONT_ALLOW_MULTIPLE_INSTANCES;
}
} else if (filename == kSilverlightPlugin) {
// Explanation for this quirk can be found in
// WebPluginDelegateImpl::Initialize.
quirks_ |= PLUGIN_QUIRK_PATCH_SETCURSOR;
} else if (plugin_info.name.find(L"DivX Web Player") !=
std::wstring::npos) {
// The divx plugin sets its size on the first NPP_SetWindow call and never
// updates its size. We should call the underlying NPP_SetWindow only when
// we have the correct size.
quirks_ |= PLUGIN_QUIRK_IGNORE_FIRST_SETWINDOW_CALL;
}
}
WebPluginDelegateImpl::~WebPluginDelegateImpl() {
if (::IsWindow(dummy_window_for_activation_)) {
// Sandboxed Flash stacks two dummy windows to prevent UIPI failures
if (::IsWindow(parent_proxy_window_))
::DestroyWindow(parent_proxy_window_);
else
::DestroyWindow(dummy_window_for_activation_);
}
DestroyInstance();
if (!windowless_)
WindowedDestroyWindow();
if (handle_event_pump_messages_event_) {
CloseHandle(handle_event_pump_messages_event_);
}
}
bool WebPluginDelegateImpl::PlatformInitialize() {
plugin_->SetWindow(windowed_handle_);
if (windowless_ && !instance_->plugin_lib()->internal()) {
CreateDummyWindowForActivation();
handle_event_pump_messages_event_ = CreateEvent(NULL, TRUE, FALSE, NULL);
plugin_->SetWindowlessPumpEvent(handle_event_pump_messages_event_);
}
// We cannot patch internal plugins as they are not shared libraries.
if (!instance_->plugin_lib()->internal()) {
// Windowless plugins call the WindowFromPoint API and passes the result of
// that to the TrackPopupMenu API call as the owner window. This causes the
// API to fail as the API expects the window handle to live on the same
// thread as the caller. It works in the other browsers as the plugin lives
// on the browser thread. Our workaround is to intercept the TrackPopupMenu
// API and replace the window handle with the dummy activation window.
if (windowless_ && !g_iat_patch_track_popup_menu.Pointer()->is_patched()) {
g_iat_patch_track_popup_menu.Pointer()->Patch(
GetPluginPath().value().c_str(), "user32.dll", "TrackPopupMenu",
WebPluginDelegateImpl::TrackPopupMenuPatch);
}
// Windowless plugins can set cursors by calling the SetCursor API. This
// works because the thread inputs of the browser UI thread and the plugin
// thread are attached. We intercept the SetCursor API for windowless
// plugins and remember the cursor being set. This is shipped over to the
// browser in the HandleEvent call, which ensures that the cursor does not
// change when a windowless plugin instance changes the cursor
// in a background tab.
if (windowless_ && !g_iat_patch_set_cursor.Pointer()->is_patched() &&
(quirks_ & PLUGIN_QUIRK_PATCH_SETCURSOR)) {
g_iat_patch_set_cursor.Pointer()->Patch(
GetPluginPath().value().c_str(), "user32.dll", "SetCursor",
WebPluginDelegateImpl::SetCursorPatch);
}
// The windowed flash plugin has a bug which occurs when the plugin enters
// fullscreen mode. It basically captures the mouse on WM_LBUTTONDOWN and
// does not release capture correctly causing it to stop receiving
// subsequent mouse events. This problem is also seen in Safari where there
// is code to handle this in the wndproc. However the plugin subclasses the
// window again in WM_LBUTTONDOWN before entering full screen. As a result
// Safari does not receive the WM_LBUTTONUP message. To workaround this
// issue we use a per thread mouse hook. This bug does not occur in Firefox
// and opera. Firefox has code similar to Safari. It could well be a bug in
// the flash plugin, which only occurs in webkit based browsers.
if (quirks_ & PLUGIN_QUIRK_HANDLE_MOUSE_CAPTURE) {
mouse_hook_ = SetWindowsHookEx(WH_MOUSE, MouseHookProc, NULL,
GetCurrentThreadId());
}
}
// On XP, WMP will use its old UI unless a registry key under HKLM has the
// name of the current process. We do it in the installer for admin users,
// for the rest patch this function.
if ((quirks_ & PLUGIN_QUIRK_PATCH_REGENUMKEYEXW) &&
base::win::GetVersion() == base::win::VERSION_XP &&
(base::win::RegKey().Open(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Microsoft\\MediaPlayer\\ShimInclusionList\\chrome.exe",
KEY_READ) != ERROR_SUCCESS) &&
!g_iat_patch_reg_enum_key_ex_w.Pointer()->is_patched()) {
g_iat_patch_reg_enum_key_ex_w.Pointer()->Patch(
L"wmpdxm.dll", "advapi32.dll", "RegEnumKeyExW",
WebPluginDelegateImpl::RegEnumKeyExWPatch);
}
// Under UIPI the key state does not get forwarded properly to the child
// plugin window. So, instead we track the key state manually and intercept
// GetKeyState.
if ((quirks_ & PLUGIN_QUIRK_PATCH_GETKEYSTATE) &&
!g_iat_patch_get_key_state.Pointer()->is_patched()) {
g_iat_orig_get_key_state = ::GetKeyState;
g_iat_patch_get_key_state.Pointer()->Patch(
L"gcswf32.dll", "user32.dll", "GetKeyState",
WebPluginDelegateImpl::GetKeyStatePatch);
}
return true;
}
void WebPluginDelegateImpl::PlatformDestroyInstance() {
if (!instance_->plugin_lib())
return;
// Unpatch if this is the last plugin instance.
if (instance_->plugin_lib()->instance_count() != 1)
return;
if (g_iat_patch_set_cursor.Pointer()->is_patched())
g_iat_patch_set_cursor.Pointer()->Unpatch();
if (g_iat_patch_track_popup_menu.Pointer()->is_patched())
g_iat_patch_track_popup_menu.Pointer()->Unpatch();
if (g_iat_patch_reg_enum_key_ex_w.Pointer()->is_patched())
g_iat_patch_reg_enum_key_ex_w.Pointer()->Unpatch();
if (mouse_hook_) {
UnhookWindowsHookEx(mouse_hook_);
mouse_hook_ = NULL;
}
}
void WebPluginDelegateImpl::Paint(WebKit::WebCanvas* canvas,
const gfx::Rect& rect) {
if (windowless_) {
skia::ScopedPlatformPaint scoped_platform_paint(canvas);
HDC hdc = scoped_platform_paint.GetPlatformSurface();
WindowlessPaint(hdc, rect);
}
}
bool WebPluginDelegateImpl::WindowedCreatePlugin() {
DCHECK(!windowed_handle_);
RegisterNativeWindowClass();
// UIPI requires reparenting in the (medium-integrity) browser process.
bool reparent_in_browser = (quirks_ & PLUGIN_QUIRK_REPARENT_IN_BROWSER) != 0;
// The window will be sized and shown later.
windowed_handle_ = CreateWindowEx(
WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR,
kNativeWindowClassName,
0,
WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
0,
0,
0,
0,
reparent_in_browser ? NULL : parent_,
0,
GetModuleHandle(NULL),
0);
if (windowed_handle_ == 0)
return false;
if (reparent_in_browser) {
plugin_->ReparentPluginWindow(windowed_handle_, parent_);
} else if (IsWindow(parent_)) {
// This is a tricky workaround for Issue 2673 in chromium "Flash: IME not
// available". To use IMEs in this window, we have to make Windows attach
// IMEs to this window (i.e. load IME DLLs, attach them to this process,
// and add their message hooks to this window). Windows attaches IMEs while
// this process creates a top-level window. On the other hand, to layout
// this window correctly in the given parent window (RenderWidgetHostHWND),
// this window should be a child window of the parent window.
// To satisfy both of the above conditions, this code once creates a
// top-level window and change it to a child window of the parent window.
SetWindowLongPtr(windowed_handle_, GWL_STYLE,
WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
SetParent(windowed_handle_, parent_);
}
BOOL result = SetProp(windowed_handle_, kWebPluginDelegateProperty, this);
DCHECK(result == TRUE) << "SetProp failed, last error = " << GetLastError();
// Get the name of the plugin, create an atom and set that in a window
// property. Use an atom so that other processes can access the name of
// the plugin that this window is hosting
if (instance_ != NULL) {
PluginLib* plugin_lib = instance()->plugin_lib();
if (plugin_lib != NULL) {
std::wstring plugin_name = plugin_lib->plugin_info().name;
if (!plugin_name.empty()) {
ATOM plugin_name_atom = GlobalAddAtomW(plugin_name.c_str());
DCHECK(0 != plugin_name_atom);
result = SetProp(windowed_handle_,
kPluginNameAtomProperty,
reinterpret_cast<HANDLE>(plugin_name_atom));
DCHECK(result == TRUE) << "SetProp failed, last error = " <<
GetLastError();
}
}
}
// Calling SetWindowLongPtrA here makes the window proc ASCII, which is
// required by at least the Shockwave Director plug-in.
SetWindowLongPtrA(
windowed_handle_, GWL_WNDPROC, reinterpret_cast<LONG>(DefWindowProcA));
return true;
}
void WebPluginDelegateImpl::WindowedDestroyWindow() {
if (windowed_handle_ != NULL) {
// Unsubclass the window.
WNDPROC current_wnd_proc = reinterpret_cast<WNDPROC>(
GetWindowLongPtr(windowed_handle_, GWLP_WNDPROC));
if (current_wnd_proc == NativeWndProc) {
SetWindowLongPtr(windowed_handle_,
GWLP_WNDPROC,
reinterpret_cast<LONG>(plugin_wnd_proc_));
}
plugin_->WillDestroyWindow(windowed_handle_);
DestroyWindow(windowed_handle_);
windowed_handle_ = 0;
}
}
// Erase all messages in the queue destined for a particular window.
// When windows are closing, callers should use this function to clear
// the queue.
// static
void WebPluginDelegateImpl::ClearThrottleQueueForWindow(HWND window) {
ThrottleQueue* throttle_queue = g_throttle_queue.Pointer();
ThrottleQueue::iterator it;
for (it = throttle_queue->begin(); it != throttle_queue->end(); ) {
if (it->hwnd == window) {
it = throttle_queue->erase(it);
} else {
it++;
}
}
}
// Delayed callback for processing throttled messages.
// Throttled messages are aggregated globally across all plugins.
// static
void WebPluginDelegateImpl::OnThrottleMessage() {
// The current algorithm walks the list and processes the first
// message it finds for each plugin. It is important to service
// all active plugins with each pass through the throttle, otherwise
// we see video jankiness. Copy the set to notify before notifying
// since we may re-enter OnThrottleMessage from CallWindowProc!
ThrottleQueue* throttle_queue = g_throttle_queue.Pointer();
ThrottleQueue notify_queue;
std::set<HWND> processed;
ThrottleQueue::iterator it = throttle_queue->begin();
while (it != throttle_queue->end()) {
const MSG& msg = *it;
if (processed.find(msg.hwnd) == processed.end()) {
processed.insert(msg.hwnd);
notify_queue.push_back(msg);
it = throttle_queue->erase(it);
} else {
it++;
}
}
for (it = notify_queue.begin(); it != notify_queue.end(); ++it) {
const MSG& msg = *it;
WNDPROC proc = reinterpret_cast<WNDPROC>(msg.time);
// It is possible that the window was closed after we queued
// this message. This is a rare event; just verify the window
// is alive. (see also bug 1259488)
if (IsWindow(msg.hwnd))
CallWindowProc(proc, msg.hwnd, msg.message, msg.wParam, msg.lParam);
}
if (!throttle_queue->empty()) {
MessageLoop::current()->PostDelayedTask(FROM_HERE,
NewRunnableFunction(&WebPluginDelegateImpl::OnThrottleMessage),
kFlashWMUSERMessageThrottleDelayMs);
}
}
// Schedule a windows message for delivery later.
// static
void WebPluginDelegateImpl::ThrottleMessage(WNDPROC proc, HWND hwnd,
UINT message, WPARAM wParam,
LPARAM lParam) {
MSG msg;
msg.time = reinterpret_cast<DWORD>(proc);
msg.hwnd = hwnd;
msg.message = message;
msg.wParam = wParam;
msg.lParam = lParam;
ThrottleQueue* throttle_queue = g_throttle_queue.Pointer();
throttle_queue->push_back(msg);
if (throttle_queue->size() == 1) {
MessageLoop::current()->PostDelayedTask(FROM_HERE,
NewRunnableFunction(&WebPluginDelegateImpl::OnThrottleMessage),
kFlashWMUSERMessageThrottleDelayMs);
}
}
// We go out of our way to find the hidden windows created by Flash for
// windowless plugins. We throttle the rate at which they deliver messages
// so that they will not consume outrageous amounts of CPU.
// static
LRESULT CALLBACK WebPluginDelegateImpl::FlashWindowlessWndProc(HWND hwnd,
UINT message, WPARAM wparam, LPARAM lparam) {
std::map<HWND, WNDPROC>::iterator index =
g_window_handle_proc_map.Get().find(hwnd);
WNDPROC old_proc = (*index).second;
DCHECK(old_proc);
switch (message) {
case WM_NCDESTROY: {
WebPluginDelegateImpl::ClearThrottleQueueForWindow(hwnd);
g_window_handle_proc_map.Get().erase(index);
break;
}
// Flash may flood the message queue with WM_USER+1 message causing 100% CPU
// usage. See https://bugzilla.mozilla.org/show_bug.cgi?id=132759. We
// prevent this by throttling the messages.
case WM_USER + 1: {
WebPluginDelegateImpl::ThrottleMessage(old_proc, hwnd, message, wparam,
lparam);
return TRUE;
}
default: {
break;
}
}
return CallWindowProc(old_proc, hwnd, message, wparam, lparam);
}
// Callback for enumerating the Flash windows.
BOOL CALLBACK EnumFlashWindows(HWND window, LPARAM arg) {
WNDPROC wnd_proc = reinterpret_cast<WNDPROC>(arg);
TCHAR class_name[1024];
if (!RealGetWindowClass(window, class_name,
sizeof(class_name)/sizeof(TCHAR))) {
LOG(ERROR) << "RealGetWindowClass failure: " << GetLastError();
return FALSE;
}
if (wcscmp(class_name, L"SWFlash_PlaceholderX"))
return TRUE;
WNDPROC current_wnd_proc = reinterpret_cast<WNDPROC>(
GetWindowLongPtr(window, GWLP_WNDPROC));
if (current_wnd_proc != wnd_proc) {
WNDPROC old_flash_proc = reinterpret_cast<WNDPROC>(SetWindowLongPtr(
window, GWLP_WNDPROC,
reinterpret_cast<LONG>(wnd_proc)));
DCHECK(old_flash_proc);
g_window_handle_proc_map.Get()[window] = old_flash_proc;
}
return TRUE;
}
bool WebPluginDelegateImpl::CreateDummyWindowForActivation() {
DCHECK(!dummy_window_for_activation_);
// Built-in Flash runs with UIPI, but in windowless mode Flash sometimes
// tries to attach windows to the parent (which fails under UIPI). To make
// it work we add an extra dummy parent in the low-integrity process.
if (quirks_ & PLUGIN_QUIRK_REPARENT_IN_BROWSER) {
parent_proxy_window_ = CreateWindowEx(
0,
L"Static",
kDummyActivationWindowName,
WS_POPUP,
0,
0,
0,
0,
0,
0,
GetModuleHandle(NULL),
0);
if (parent_proxy_window_ == 0)
return false;
plugin_->ReparentPluginWindow(parent_proxy_window_, parent_);
}
dummy_window_for_activation_ = CreateWindowEx(
0,
L"Static",
kDummyActivationWindowName,
WS_CHILD,
0,
0,
0,
0,
parent_proxy_window_ ? parent_proxy_window_ : parent_,
0,
GetModuleHandle(NULL),
0);
if (dummy_window_for_activation_ == 0)
return false;
// Flash creates background windows which use excessive CPU in our
// environment; we wrap these windows and throttle them so that they don't
// get out of hand.
if (!EnumThreadWindows(::GetCurrentThreadId(), EnumFlashWindows,
reinterpret_cast<LPARAM>(
&WebPluginDelegateImpl::FlashWindowlessWndProc))) {
// Log that this happened. Flash will still work; it just means the
// throttle isn't installed (and Flash will use more CPU).
NOTREACHED();
LOG(ERROR) << "Failed to wrap all windowless Flash windows";
}
return true;
}
bool WebPluginDelegateImpl::WindowedReposition(
const gfx::Rect& window_rect,
const gfx::Rect& clip_rect) {
if (!windowed_handle_) {
NOTREACHED();
return false;
}
if (window_rect_ == window_rect && clip_rect_ == clip_rect)
return false;
// We only set the plugin's size here. Its position is moved elsewhere, which
// allows the window moves/scrolling/clipping to be synchronized with the page
// and other windows.
// If the plugin window has no parent, then don't focus it because it isn't
// being displayed anywhere. See:
// http://code.google.com/p/chromium/issues/detail?id=32658
if (window_rect.size() != window_rect_.size()) {
UINT flags = SWP_NOMOVE | SWP_NOZORDER;
if (!GetParent(windowed_handle_))
flags |= SWP_NOACTIVATE;
::SetWindowPos(windowed_handle_,
NULL,
0,
0,
window_rect.width(),
window_rect.height(),
flags);
}
window_rect_ = window_rect;
clip_rect_ = clip_rect;
// Ensure that the entire window gets repainted.
::InvalidateRect(windowed_handle_, NULL, FALSE);
return true;
}
void WebPluginDelegateImpl::WindowedSetWindow() {
if (!instance_)
return;
if (!windowed_handle_) {
NOTREACHED();
return;
}
instance()->set_window_handle(windowed_handle_);
DCHECK(!instance()->windowless());
window_.clipRect.top = std::max(0, clip_rect_.y());
window_.clipRect.left = std::max(0, clip_rect_.x());
window_.clipRect.bottom = std::max(0, clip_rect_.y() + clip_rect_.height());
window_.clipRect.right = std::max(0, clip_rect_.x() + clip_rect_.width());
window_.height = window_rect_.height();
window_.width = window_rect_.width();
window_.x = 0;
window_.y = 0;
window_.window = windowed_handle_;
window_.type = NPWindowTypeWindow;
// Reset this flag before entering the instance in case of side-effects.
windowed_did_set_window_ = true;
NPError err = instance()->NPP_SetWindow(&window_);
if (quirks_ & PLUGIN_QUIRK_SETWINDOW_TWICE)
instance()->NPP_SetWindow(&window_);
WNDPROC current_wnd_proc = reinterpret_cast<WNDPROC>(
GetWindowLongPtr(windowed_handle_, GWLP_WNDPROC));
if (current_wnd_proc != NativeWndProc) {
plugin_wnd_proc_ = reinterpret_cast<WNDPROC>(SetWindowLongPtr(
windowed_handle_, GWLP_WNDPROC, reinterpret_cast<LONG>(NativeWndProc)));
}
}
ATOM WebPluginDelegateImpl::RegisterNativeWindowClass() {
static bool have_registered_window_class = false;
if (have_registered_window_class == true)
return true;
have_registered_window_class = true;
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_DBLCLKS;
wcex.lpfnWndProc = DummyWindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = GetModuleHandle(NULL);
wcex.hIcon = 0;
wcex.hCursor = 0;
// Some plugins like windows media player 11 create child windows parented
// by our plugin window, where the media content is rendered. These plugins
// dont implement WM_ERASEBKGND, which causes painting issues, when the
// window where the media is rendered is moved around. DefWindowProc does
// implement WM_ERASEBKGND correctly if we have a valid background brush.
wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW+1);
wcex.lpszMenuName = 0;
wcex.lpszClassName = kNativeWindowClassName;
wcex.hIconSm = 0;
return RegisterClassEx(&wcex);
}
LRESULT CALLBACK WebPluginDelegateImpl::DummyWindowProc(
HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
// This is another workaround for Issue 2673 in chromium "Flash: IME not
// available". Somehow, the CallWindowProc() function does not dispatch
// window messages when its first parameter is a handle representing the
// DefWindowProc() function. To avoid this problem, this code creates a
// wrapper function which just encapsulates the DefWindowProc() function
// and set it as the window procedure of a windowed plug-in.
return DefWindowProc(hWnd, message, wParam, lParam);
}
// Returns true if the message passed in corresponds to a user gesture.
static bool IsUserGestureMessage(unsigned int message) {
switch (message) {
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
case WM_KEYDOWN:
case WM_KEYUP:
return true;
default:
break;
}
return false;
}
LRESULT CALLBACK WebPluginDelegateImpl::NativeWndProc(
HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
WebPluginDelegateImpl* delegate = reinterpret_cast<WebPluginDelegateImpl*>(
GetProp(hwnd, kWebPluginDelegateProperty));
if (!delegate) {
NOTREACHED();
return 0;
}
if (message == delegate->last_message_ &&
delegate->GetQuirks() & PLUGIN_QUIRK_DONT_CALL_WND_PROC_RECURSIVELY &&
delegate->is_calling_wndproc) {
// Real may go into a state where it recursively dispatches the same event
// when subclassed. See https://bugzilla.mozilla.org/show_bug.cgi?id=192914
// We only do the recursive check for Real because it's possible and valid
// for a plugin to synchronously dispatch a message to itself such that it
// looks like it's in recursion.
return TRUE;
}
// Flash may flood the message queue with WM_USER+1 message causing 100% CPU
// usage. See https://bugzilla.mozilla.org/show_bug.cgi?id=132759. We
// prevent this by throttling the messages.
if (message == WM_USER + 1 &&
delegate->GetQuirks() & PLUGIN_QUIRK_THROTTLE_WM_USER_PLUS_ONE) {
WebPluginDelegateImpl::ThrottleMessage(delegate->plugin_wnd_proc_, hwnd,
message, wparam, lparam);
return FALSE;
}
// Track the keystate to work around a UIPI issue.
if (delegate->GetQuirks() & PLUGIN_QUIRK_PATCH_GETKEYSTATE) {
switch (message) {
case WM_KEYDOWN:
SetSavedKeyState(wparam);
break;
case WM_KEYUP:
UnsetSavedKeyState(wparam);
break;
// Clear out the saved keystate whenever the Flash thread loses focus.
case WM_KILLFOCUS:
case WM_SETFOCUS:
if (::GetCurrentThreadId() != ::GetWindowThreadProcessId(
reinterpret_cast<HWND>(wparam), NULL)) {
ClearSavedKeyState();
}
break;
default:
break;
}
}
LRESULT result;
uint32 old_message = delegate->last_message_;
delegate->last_message_ = message;
static UINT custom_msg = RegisterWindowMessage(kPaintMessageName);
if (message == custom_msg) {
// Get the invalid rect which is in screen coordinates and convert to
// window coordinates.
gfx::Rect invalid_rect;
invalid_rect.set_x(wparam >> 16);
invalid_rect.set_y(wparam & 0xFFFF);
invalid_rect.set_width(lparam >> 16);
invalid_rect.set_height(lparam & 0xFFFF);
RECT window_rect;
GetWindowRect(hwnd, &window_rect);
invalid_rect.Offset(-window_rect.left, -window_rect.top);
// The plugin window might have non-client area. If we don't pass in
// RDW_FRAME then the children don't receive WM_NCPAINT messages while
// scrolling, which causes painting problems (http://b/issue?id=923945).
uint32 flags = RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_FRAME;
// If a plugin (like Google Earth or Java) has child windows that are hosted
// in a different process, then RedrawWindow with UPDATENOW will
// synchronously wait for this call to complete. Some messages are pumped
// but not others, which could lead to a deadlock. So avoid reentrancy by
// only synchronously calling RedrawWindow once at a time.
if (old_message != custom_msg)
flags |= RDW_UPDATENOW;
RedrawWindow(hwnd, &invalid_rect.ToRECT(), NULL, flags);
result = FALSE;
} else {
delegate->is_calling_wndproc = true;
if (!delegate->user_gesture_message_posted_ &&
IsUserGestureMessage(message)) {
delegate->user_gesture_message_posted_ = true;
delegate->instance()->PushPopupsEnabledState(true);
MessageLoop::current()->PostDelayedTask(FROM_HERE,
delegate->user_gesture_msg_factory_.NewRunnableMethod(
&WebPluginDelegateImpl::OnUserGestureEnd),
kWindowedPluginPopupTimerMs);
}
HandleCaptureForMessage(hwnd, message);
// Maintain a local/global stack for the g_current_plugin_instance variable
// as this may be a nested invocation.
WebPluginDelegateImpl* last_plugin_instance = g_current_plugin_instance;
g_current_plugin_instance = delegate;
result = CallWindowProc(
delegate->plugin_wnd_proc_, hwnd, message, wparam, lparam);
delegate->is_calling_wndproc = false;
g_current_plugin_instance = last_plugin_instance;
if (message == WM_NCDESTROY) {
RemoveProp(hwnd, kWebPluginDelegateProperty);
ATOM plugin_name_atom = reinterpret_cast<ATOM>(
RemoveProp(hwnd, kPluginNameAtomProperty));
if (plugin_name_atom != 0)
GlobalDeleteAtom(plugin_name_atom);
ClearThrottleQueueForWindow(hwnd);
}
}
delegate->last_message_ = old_message;
return result;
}
void WebPluginDelegateImpl::WindowlessUpdateGeometry(
const gfx::Rect& window_rect,
const gfx::Rect& clip_rect) {
bool window_rect_changed = (window_rect_ != window_rect);
// Only resend to the instance if the geometry has changed.
if (!window_rect_changed && clip_rect == clip_rect_)
return;
clip_rect_ = clip_rect;
window_rect_ = window_rect;
WindowlessSetWindow();
if (window_rect_changed) {
WINDOWPOS win_pos = {0};
win_pos.x = window_rect_.x();
win_pos.y = window_rect_.y();
win_pos.cx = window_rect_.width();
win_pos.cy = window_rect_.height();
NPEvent pos_changed_event;
pos_changed_event.event = WM_WINDOWPOSCHANGED;
pos_changed_event.wParam = 0;
pos_changed_event.lParam = PtrToUlong(&win_pos);
instance()->NPP_HandleEvent(&pos_changed_event);
}
}
void WebPluginDelegateImpl::WindowlessPaint(HDC hdc,
const gfx::Rect& damage_rect) {
DCHECK(hdc);
RECT damage_rect_win;
damage_rect_win.left = damage_rect.x(); // + window_rect_.x();
damage_rect_win.top = damage_rect.y(); // + window_rect_.y();
damage_rect_win.right = damage_rect_win.left + damage_rect.width();
damage_rect_win.bottom = damage_rect_win.top + damage_rect.height();
// Save away the old HDC as this could be a nested invocation.
void* old_dc = window_.window;
window_.window = hdc;
NPEvent paint_event;
paint_event.event = WM_PAINT;
// NOTE: NPAPI is not 64bit safe. It puts pointers into 32bit values.
paint_event.wParam = PtrToUlong(hdc);
paint_event.lParam = PtrToUlong(&damage_rect_win);
base::StatsRate plugin_paint("Plugin.Paint");
base::StatsScope<base::StatsRate> scope(plugin_paint);
instance()->NPP_HandleEvent(&paint_event);
window_.window = old_dc;
}
void WebPluginDelegateImpl::WindowlessSetWindow() {
if (!instance())
return;
if (window_rect_.IsEmpty()) // wait for geometry to be set.
return;
DCHECK(instance()->windowless());
window_.clipRect.top = clip_rect_.y();
window_.clipRect.left = clip_rect_.x();
window_.clipRect.bottom = clip_rect_.y() + clip_rect_.height();
window_.clipRect.right = clip_rect_.x() + clip_rect_.width();
window_.height = window_rect_.height();
window_.width = window_rect_.width();
window_.x = window_rect_.x();
window_.y = window_rect_.y();
window_.type = NPWindowTypeDrawable;
DrawableContextEnforcer enforcer(&window_);
NPError err = instance()->NPP_SetWindow(&window_);
DCHECK(err == NPERR_NO_ERROR);
}
bool WebPluginDelegateImpl::PlatformSetPluginHasFocus(bool focused) {
DCHECK(instance()->windowless());
NPEvent focus_event;
focus_event.event = focused ? WM_SETFOCUS : WM_KILLFOCUS;
focus_event.wParam = 0;
focus_event.lParam = 0;
if (GetQuirks() & PLUGIN_QUIRK_PATCH_GETKEYSTATE)
ClearSavedKeyState();
instance()->NPP_HandleEvent(&focus_event);
return true;
}
static bool NPEventFromWebMouseEvent(const WebMouseEvent& event,
NPEvent *np_event) {
np_event->lParam = static_cast<uint32>(MAKELPARAM(event.windowX,
event.windowY));
np_event->wParam = 0;
if (event.modifiers & WebInputEvent::ControlKey)
np_event->wParam |= MK_CONTROL;
if (event.modifiers & WebInputEvent::ShiftKey)
np_event->wParam |= MK_SHIFT;
if (event.modifiers & WebInputEvent::LeftButtonDown)
np_event->wParam |= MK_LBUTTON;
if (event.modifiers & WebInputEvent::MiddleButtonDown)
np_event->wParam |= MK_MBUTTON;
if (event.modifiers & WebInputEvent::RightButtonDown)
np_event->wParam |= MK_RBUTTON;
switch (event.type) {
case WebInputEvent::MouseMove:
case WebInputEvent::MouseLeave:
case WebInputEvent::MouseEnter:
np_event->event = WM_MOUSEMOVE;
return true;
case WebInputEvent::MouseDown:
switch (event.button) {
case WebMouseEvent::ButtonLeft:
np_event->event = WM_LBUTTONDOWN;
break;
case WebMouseEvent::ButtonMiddle:
np_event->event = WM_MBUTTONDOWN;
break;
case WebMouseEvent::ButtonRight:
np_event->event = WM_RBUTTONDOWN;
break;
}
return true;
case WebInputEvent::MouseUp:
switch (event.button) {
case WebMouseEvent::ButtonLeft:
np_event->event = WM_LBUTTONUP;
break;
case WebMouseEvent::ButtonMiddle:
np_event->event = WM_MBUTTONUP;
break;
case WebMouseEvent::ButtonRight:
np_event->event = WM_RBUTTONUP;
break;
}
return true;
default:
NOTREACHED();
return false;
}
}
static bool NPEventFromWebKeyboardEvent(const WebKeyboardEvent& event,
NPEvent *np_event) {
np_event->wParam = event.windowsKeyCode;
switch (event.type) {
case WebInputEvent::KeyDown:
np_event->event = WM_KEYDOWN;
np_event->lParam = 0;
return true;
case WebInputEvent::Char:
np_event->event = WM_CHAR;
np_event->lParam = 0;
return true;
case WebInputEvent::KeyUp:
np_event->event = WM_KEYUP;
np_event->lParam = 0x8000;
return true;
default:
NOTREACHED();
return false;
}
}
static bool NPEventFromWebInputEvent(const WebInputEvent& event,
NPEvent* np_event) {
switch (event.type) {
case WebInputEvent::MouseMove:
case WebInputEvent::MouseLeave:
case WebInputEvent::MouseEnter:
case WebInputEvent::MouseDown:
case WebInputEvent::MouseUp:
if (event.size < sizeof(WebMouseEvent)) {
NOTREACHED();
return false;
}
return NPEventFromWebMouseEvent(
*static_cast<const WebMouseEvent*>(&event), np_event);
case WebInputEvent::KeyDown:
case WebInputEvent::Char:
case WebInputEvent::KeyUp:
if (event.size < sizeof(WebKeyboardEvent)) {
NOTREACHED();
return false;
}
return NPEventFromWebKeyboardEvent(
*static_cast<const WebKeyboardEvent*>(&event), np_event);
default:
return false;
}
}
bool WebPluginDelegateImpl::PlatformHandleInputEvent(
const WebInputEvent& event, WebCursorInfo* cursor_info) {
DCHECK(cursor_info != NULL);
NPEvent np_event;
if (!NPEventFromWebInputEvent(event, &np_event)) {
return false;
}
if (GetQuirks() & PLUGIN_QUIRK_PATCH_GETKEYSTATE) {
if (np_event.event == WM_KEYDOWN)
SetSavedKeyState(np_event.wParam);
else if (np_event.event == WM_KEYUP)
UnsetSavedKeyState(np_event.wParam);
}
HWND last_focus_window = NULL;
if (ShouldTrackEventForModalLoops(&np_event)) {
// A windowless plugin can enter a modal loop in a NPP_HandleEvent call.
// For e.g. Flash puts up a context menu when we right click on the
// windowless plugin area. We detect this by setting up a message filter
// hook pror to calling NPP_HandleEvent on the plugin and unhook on
// return from NPP_HandleEvent. If the plugin does enter a modal loop
// in that context we unhook on receiving the first notification in
// the message filter hook.
handle_event_message_filter_hook_ =
SetWindowsHookEx(WH_MSGFILTER, HandleEventMessageFilterHook, NULL,
GetCurrentThreadId());
// To ensure that the plugin receives keyboard events we set focus to the
// dummy window.
// TODO(iyengar) We need a framework in the renderer to identify which
// windowless plugin is under the mouse and to handle this. This would
// also require some changes in RenderWidgetHost to detect this in the
// WM_MOUSEACTIVATE handler and inform the renderer accordingly.
last_focus_window = ::SetFocus(dummy_window_for_activation_);
}
bool old_task_reentrancy_state =
MessageLoop::current()->NestableTasksAllowed();
// Maintain a local/global stack for the g_current_plugin_instance variable
// as this may be a nested invocation.
WebPluginDelegateImpl* last_plugin_instance = g_current_plugin_instance;
g_current_plugin_instance = this;
handle_event_depth_++;
bool popups_enabled = false;
if (IsUserGestureMessage(np_event.event)) {
instance()->PushPopupsEnabledState(true);
popups_enabled = true;
}
bool ret = instance()->NPP_HandleEvent(&np_event) != 0;
if (popups_enabled) {
instance()->PopPopupsEnabledState();
}
// Flash and SilverLight always return false, even when they swallow the
// event. Flash does this because it passes the event to its window proc,
// which is supposed to return 0 if an event was handled. There are few
// exceptions, such as IME, where it sometimes returns true.
ret = true;
if (np_event.event == WM_MOUSEMOVE) {
current_windowless_cursor_.InitFromExternalCursor(GetCursor());
// Snag a reference to the current cursor ASAP in case the plugin modified
// it. There is a nasty race condition here with the multiprocess browser
// as someone might be setting the cursor in the main process as well.
current_windowless_cursor_.GetCursorInfo(cursor_info);
}
handle_event_depth_--;
g_current_plugin_instance = last_plugin_instance;
// We could have multiple NPP_HandleEvent calls nested together in case
// the plugin enters a modal loop. Reset the pump messages event when
// the outermost NPP_HandleEvent call unwinds.
if (handle_event_depth_ == 0) {
ResetEvent(handle_event_pump_messages_event_);
}
if (::IsWindow(last_focus_window)) {
// Restore the nestable tasks allowed state in the message loop and reset
// the os modal loop state as the plugin returned from the TrackPopupMenu
// API call.
MessageLoop::current()->SetNestableTasksAllowed(old_task_reentrancy_state);
MessageLoop::current()->set_os_modal_loop(false);
// The Flash plugin at times sets focus to its hidden top level window
// with class name SWFlash_PlaceholderX. This causes the chrome browser
// window to receive a WM_ACTIVATEAPP message as a top level window from
// another thread is now active. We end up in a state where the chrome
// browser window is not active even though the user clicked on it.
// Our workaround for this is to send over a raw
// WM_LBUTTONDOWN/WM_LBUTTONUP combination to the last focus window, which
// does the trick.
if (dummy_window_for_activation_ != ::GetFocus()) {
INPUT input_info = {0};
input_info.type = INPUT_MOUSE;
input_info.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
::SendInput(1, &input_info, sizeof(INPUT));
input_info.type = INPUT_MOUSE;
input_info.mi.dwFlags = MOUSEEVENTF_LEFTUP;
::SendInput(1, &input_info, sizeof(INPUT));
} else {
::SetFocus(last_focus_window);
}
}
return ret;
}
void WebPluginDelegateImpl::OnModalLoopEntered() {
DCHECK(handle_event_pump_messages_event_ != NULL);
SetEvent(handle_event_pump_messages_event_);
MessageLoop::current()->SetNestableTasksAllowed(true);
MessageLoop::current()->set_os_modal_loop(true);
UnhookWindowsHookEx(handle_event_message_filter_hook_);
handle_event_message_filter_hook_ = NULL;
}
bool WebPluginDelegateImpl::ShouldTrackEventForModalLoops(NPEvent* event) {
if (event->event == WM_RBUTTONDOWN)
return true;
return false;
}
void WebPluginDelegateImpl::OnUserGestureEnd() {
user_gesture_message_posted_ = false;
instance()->PopPopupsEnabledState();
}
BOOL WINAPI WebPluginDelegateImpl::TrackPopupMenuPatch(
HMENU menu, unsigned int flags, int x, int y, int reserved,
HWND window, const RECT* rect) {
if (g_current_plugin_instance) {
unsigned long window_process_id = 0;
unsigned long window_thread_id =
GetWindowThreadProcessId(window, &window_process_id);
// TrackPopupMenu fails if the window passed in belongs to a different
// thread.
if (::GetCurrentThreadId() != window_thread_id) {
window = g_current_plugin_instance->dummy_window_for_activation_;
}
}
BOOL result = TrackPopupMenu(menu, flags, x, y, reserved, window, rect);
return result;
}
HCURSOR WINAPI WebPluginDelegateImpl::SetCursorPatch(HCURSOR cursor) {
// The windowless flash plugin periodically calls SetCursor in a wndproc
// instantiated on the plugin thread. This causes annoying cursor flicker
// when the mouse is moved on a foreground tab, with a windowless plugin
// instance in a background tab. We just ignore the call here.
if (!g_current_plugin_instance) {
HCURSOR current_cursor = GetCursor();
if (current_cursor != cursor) {
::SetCursor(cursor);
}
return current_cursor;
}
return ::SetCursor(cursor);
}
LONG WINAPI WebPluginDelegateImpl::RegEnumKeyExWPatch(
HKEY key, DWORD index, LPWSTR name, LPDWORD name_size, LPDWORD reserved,
LPWSTR class_name, LPDWORD class_size, PFILETIME last_write_time) {
DWORD orig_size = *name_size;
LONG rv = RegEnumKeyExW(key, index, name, name_size, reserved, class_name,
class_size, last_write_time);
if (rv == ERROR_SUCCESS &&
GetKeyPath(key).find(L"Microsoft\\MediaPlayer\\ShimInclusionList") !=
std::wstring::npos) {
static const wchar_t kChromeExeName[] = L"chrome.exe";
wcsncpy_s(name, orig_size, kChromeExeName, arraysize(kChromeExeName));
*name_size =
std::min(orig_size, static_cast<DWORD>(arraysize(kChromeExeName)));
}
return rv;
}
void WebPluginDelegateImpl::HandleCaptureForMessage(HWND window,
UINT message) {
if (!WebPluginDelegateImpl::IsPluginDelegateWindow(window))
return;
switch (message) {
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
::SetCapture(window);
// As per documentation the WM_PARENTNOTIFY message is sent to the parent
// window chain if mouse input is received by the child window. However
// the parent receives the WM_PARENTNOTIFY message only if we doubleclick
// on the window. We send the WM_PARENTNOTIFY message for mouse input
// messages to the parent to indicate that user action is expected.
::SendMessage(::GetParent(window), WM_PARENTNOTIFY, message, 0);
break;
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
::ReleaseCapture();
break;
default:
break;
}
}
} // namespace npapi
} // namespace webkit