1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/child/npapi/webplugin_delegate_impl.h"
12 #include "base/bind.h"
13 #include "base/compiler_specific.h"
14 #include "base/lazy_instance.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/metrics/stats_counters.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/synchronization/lock.h"
21 #include "base/version.h"
22 #include "base/win/iat_patch_function.h"
23 #include "base/win/registry.h"
24 #include "base/win/windows_version.h"
25 #include "content/child/npapi/plugin_instance.h"
26 #include "content/child/npapi/plugin_lib.h"
27 #include "content/child/npapi/plugin_stream_url.h"
28 #include "content/child/npapi/webplugin.h"
29 #include "content/child/npapi/webplugin_ime_win.h"
30 #include "content/common/plugin_constants_win.h"
31 #include "content/public/common/content_constants.h"
32 #include "skia/ext/platform_canvas.h"
33 #include "third_party/WebKit/public/web/WebInputEvent.h"
34 #include "ui/gfx/win/dpi.h"
35 #include "ui/gfx/win/hwnd_util.h"
36 #include "webkit/common/cursors/webcursor.h"
38 using blink::WebKeyboardEvent
;
39 using blink::WebInputEvent
;
40 using blink::WebMouseEvent
;
46 const wchar_t kWebPluginDelegateProperty
[] = L
"WebPluginDelegateProperty";
47 const wchar_t kPluginFlashThrottle
[] = L
"FlashThrottle";
49 // The fastest we are willing to process WM_USER+1 events for Flash.
50 // Flash can easily exceed the limits of our CPU if we don't throttle it.
51 // The throttle has been chosen by testing various delays and compromising
52 // on acceptable Flash performance and reasonable CPU consumption.
54 // I'd like to make the throttle delay variable, based on the amount of
55 // time currently required to paint Flash plugins. There isn't a good
56 // way to count the time spent in aggregate plugin painting, however, so
57 // this seems to work well enough.
58 const int kFlashWMUSERMessageThrottleDelayMs
= 5;
60 // Flash displays popups in response to user clicks by posting a WM_USER
61 // message to the plugin window. The handler for this message displays
62 // the popup. To ensure that the popups allowed state is sent correctly
63 // to the renderer we reset the popups allowed state in a timer.
64 const int kWindowedPluginPopupTimerMs
= 50;
66 // The current instance of the plugin which entered the modal loop.
67 WebPluginDelegateImpl
* g_current_plugin_instance
= NULL
;
69 typedef std::deque
<MSG
> ThrottleQueue
;
70 base::LazyInstance
<ThrottleQueue
> g_throttle_queue
= LAZY_INSTANCE_INITIALIZER
;
72 base::LazyInstance
<std::map
<HWND
, WNDPROC
> > g_window_handle_proc_map
=
73 LAZY_INSTANCE_INITIALIZER
;
75 // Helper object for patching the TrackPopupMenu API.
76 base::LazyInstance
<base::win::IATPatchFunction
> g_iat_patch_track_popup_menu
=
77 LAZY_INSTANCE_INITIALIZER
;
79 // Helper object for patching the SetCursor API.
80 base::LazyInstance
<base::win::IATPatchFunction
> g_iat_patch_set_cursor
=
81 LAZY_INSTANCE_INITIALIZER
;
83 // Helper object for patching the RegEnumKeyExW API.
84 base::LazyInstance
<base::win::IATPatchFunction
> g_iat_patch_reg_enum_key_ex_w
=
85 LAZY_INSTANCE_INITIALIZER
;
87 // Helper object for patching the GetProcAddress API.
88 base::LazyInstance
<base::win::IATPatchFunction
> g_iat_patch_get_proc_address
=
89 LAZY_INSTANCE_INITIALIZER
;
92 base::LazyInstance
<base::win::IATPatchFunction
> g_iat_patch_window_from_point
=
93 LAZY_INSTANCE_INITIALIZER
;
96 // http://crbug.com/16114
97 // Enforces providing a valid device context in NPWindow, so that NPP_SetWindow
98 // is never called with NPNWindoTypeDrawable and NPWindow set to NULL.
99 // Doing so allows removing NPP_SetWindow call during painting a windowless
100 // plugin, which otherwise could trigger layout change while painting by
101 // invoking NPN_Evaluate. Which would cause bad, bad crashes. Bad crashes.
102 // TODO(dglazkov): If this approach doesn't produce regressions, move class to
103 // webplugin_delegate_impl.h and implement for other platforms.
104 class DrawableContextEnforcer
{
106 explicit DrawableContextEnforcer(NPWindow
* window
)
108 disposable_dc_(window
&& !window
->window
) {
109 // If NPWindow is NULL, create a device context with monochrome 1x1 surface
110 // and stuff it to NPWindow.
112 window_
->window
= CreateCompatibleDC(NULL
);
115 ~DrawableContextEnforcer() {
119 DeleteDC(static_cast<HDC
>(window_
->window
));
120 window_
->window
= NULL
;
128 // These are from ntddk.h
129 typedef LONG NTSTATUS
;
131 #ifndef STATUS_SUCCESS
132 #define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
135 #ifndef STATUS_BUFFER_TOO_SMALL
136 #define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
139 typedef enum _KEY_INFORMATION_CLASS
{
144 KeyCachedInformation
,
145 KeyVirtualizationInformation
146 } KEY_INFORMATION_CLASS
;
148 typedef struct _KEY_NAME_INFORMATION
{
151 } KEY_NAME_INFORMATION
, *PKEY_NAME_INFORMATION
;
153 typedef DWORD (__stdcall
*ZwQueryKeyType
)(
155 int key_information_class
,
156 PVOID key_information
,
158 PULONG result_length
);
160 // Returns a key's full path.
161 std::wstring
GetKeyPath(HKEY key
) {
165 HMODULE dll
= GetModuleHandle(L
"ntdll.dll");
169 ZwQueryKeyType func
= reinterpret_cast<ZwQueryKeyType
>(
170 ::GetProcAddress(dll
, "ZwQueryKey"));
176 result
= func(key
, KeyNameInformation
, 0, 0, &size
);
177 if (result
!= STATUS_BUFFER_TOO_SMALL
)
180 scoped_ptr
<char[]> buffer(new char[size
]);
181 if (buffer
.get() == NULL
)
184 result
= func(key
, KeyNameInformation
, buffer
.get(), size
, &size
);
185 if (result
!= STATUS_SUCCESS
)
188 KEY_NAME_INFORMATION
* info
=
189 reinterpret_cast<KEY_NAME_INFORMATION
*>(buffer
.get());
190 return std::wstring(info
->Name
, info
->NameLength
/ sizeof(wchar_t));
193 int GetPluginMajorVersion(const WebPluginInfo
& plugin_info
) {
194 Version plugin_version
;
195 WebPluginInfo::CreateVersionFromString(plugin_info
.version
, &plugin_version
);
197 int major_version
= 0;
198 if (plugin_version
.IsValid())
199 major_version
= plugin_version
.components()[0];
201 return major_version
;
206 LRESULT CALLBACK
WebPluginDelegateImpl::HandleEventMessageFilterHook(
207 int code
, WPARAM wParam
, LPARAM lParam
) {
208 if (g_current_plugin_instance
) {
209 g_current_plugin_instance
->OnModalLoopEntered();
213 return CallNextHookEx(NULL
, code
, wParam
, lParam
);
216 LRESULT CALLBACK
WebPluginDelegateImpl::MouseHookProc(
217 int code
, WPARAM wParam
, LPARAM lParam
) {
218 if (code
== HC_ACTION
) {
219 MOUSEHOOKSTRUCT
* hook_struct
= reinterpret_cast<MOUSEHOOKSTRUCT
*>(lParam
);
221 HandleCaptureForMessage(hook_struct
->hwnd
, wParam
);
224 return CallNextHookEx(NULL
, code
, wParam
, lParam
);
227 WebPluginDelegateImpl::WebPluginDelegateImpl(
229 PluginInstance
* instance
)
230 : instance_(instance
),
234 windowed_handle_(NULL
),
235 windowed_did_set_window_(false),
236 plugin_wnd_proc_(NULL
),
238 is_calling_wndproc(false),
239 dummy_window_for_activation_(NULL
),
240 dummy_window_parent_(NULL
),
241 old_dummy_window_proc_(NULL
),
242 handle_event_message_filter_hook_(NULL
),
243 handle_event_pump_messages_event_(NULL
),
244 user_gesture_message_posted_(false),
245 user_gesture_msg_factory_(this),
246 handle_event_depth_(0),
248 first_set_window_call_(true),
249 plugin_has_focus_(false),
250 has_webkit_focus_(false),
251 containing_view_has_focus_(true),
252 creation_succeeded_(false) {
253 memset(&window_
, 0, sizeof(window_
));
255 const WebPluginInfo
& plugin_info
= instance_
->plugin_lib()->plugin_info();
256 std::wstring filename
=
257 StringToLowerASCII(plugin_info
.path
.BaseName().value());
259 if (instance_
->mime_type() == kFlashPluginSwfMimeType
||
260 filename
== kFlashPlugin
) {
261 // Flash only requests windowless plugins if we return a Mozilla user
263 instance_
->set_use_mozilla_user_agent();
264 quirks_
|= PLUGIN_QUIRK_THROTTLE_WM_USER_PLUS_ONE
;
265 quirks_
|= PLUGIN_QUIRK_PATCH_SETCURSOR
;
266 quirks_
|= PLUGIN_QUIRK_ALWAYS_NOTIFY_SUCCESS
;
267 quirks_
|= PLUGIN_QUIRK_HANDLE_MOUSE_CAPTURE
;
268 quirks_
|= PLUGIN_QUIRK_EMULATE_IME
;
269 #if defined(USE_AURA)
270 quirks_
|= PLUGIN_QUIRK_FAKE_WINDOW_FROM_POINT
;
272 } else if (filename
== kAcrobatReaderPlugin
) {
273 // Check for the version number above or equal 9.
274 int major_version
= GetPluginMajorVersion(plugin_info
);
275 if (major_version
>= 9) {
276 quirks_
|= PLUGIN_QUIRK_DIE_AFTER_UNLOAD
;
278 quirks_
|= PLUGIN_QUIRK_SETWINDOW_TWICE
;
280 quirks_
|= PLUGIN_QUIRK_BLOCK_NONSTANDARD_GETURL_REQUESTS
;
281 } else if (plugin_info
.name
.find(L
"Windows Media Player") !=
282 std::wstring::npos
) {
283 // Windows Media Player needs two NPP_SetWindow calls.
284 quirks_
|= PLUGIN_QUIRK_SETWINDOW_TWICE
;
286 // Windowless mode doesn't work in the WMP NPAPI plugin.
287 quirks_
|= PLUGIN_QUIRK_NO_WINDOWLESS
;
289 // The media player plugin sets its size on the first NPP_SetWindow call
290 // and never updates its size. We should call the underlying NPP_SetWindow
291 // only when we have the correct size.
292 quirks_
|= PLUGIN_QUIRK_IGNORE_FIRST_SETWINDOW_CALL
;
294 if (filename
== kOldWMPPlugin
) {
295 // Non-admin users on XP couldn't modify the key to force the new UI.
296 quirks_
|= PLUGIN_QUIRK_PATCH_REGENUMKEYEXW
;
298 } else if (instance_
->mime_type() == "audio/x-pn-realaudio-plugin" ||
299 filename
== kRealPlayerPlugin
) {
300 quirks_
|= PLUGIN_QUIRK_DONT_CALL_WND_PROC_RECURSIVELY
;
301 } else if (plugin_info
.name
.find(L
"VLC Multimedia Plugin") !=
302 std::wstring::npos
||
303 plugin_info
.name
.find(L
"VLC Multimedia Plug-in") !=
304 std::wstring::npos
) {
305 // VLC hangs on NPP_Destroy if we call NPP_SetWindow with a null window
307 quirks_
|= PLUGIN_QUIRK_DONT_SET_NULL_WINDOW_HANDLE_ON_DESTROY
;
308 int major_version
= GetPluginMajorVersion(plugin_info
);
309 if (major_version
== 0) {
310 // VLC 0.8.6d and 0.8.6e crash if multiple instances are created.
311 quirks_
|= PLUGIN_QUIRK_DONT_ALLOW_MULTIPLE_INSTANCES
;
313 } else if (filename
== kSilverlightPlugin
) {
314 // Explanation for this quirk can be found in
315 // WebPluginDelegateImpl::Initialize.
316 quirks_
|= PLUGIN_QUIRK_PATCH_SETCURSOR
;
317 } else if (plugin_info
.name
.find(L
"DivX Web Player") !=
318 std::wstring::npos
) {
319 // The divx plugin sets its size on the first NPP_SetWindow call and never
320 // updates its size. We should call the underlying NPP_SetWindow only when
321 // we have the correct size.
322 quirks_
|= PLUGIN_QUIRK_IGNORE_FIRST_SETWINDOW_CALL
;
326 WebPluginDelegateImpl::~WebPluginDelegateImpl() {
327 if (::IsWindow(dummy_window_for_activation_
)) {
328 WNDPROC current_wnd_proc
= reinterpret_cast<WNDPROC
>(
329 GetWindowLongPtr(dummy_window_for_activation_
, GWLP_WNDPROC
));
330 if (current_wnd_proc
== DummyWindowProc
) {
331 SetWindowLongPtr(dummy_window_for_activation_
,
333 reinterpret_cast<LONG_PTR
>(old_dummy_window_proc_
));
335 ::DestroyWindow(dummy_window_for_activation_
);
341 WindowedDestroyWindow();
343 if (handle_event_pump_messages_event_
) {
344 CloseHandle(handle_event_pump_messages_event_
);
348 bool WebPluginDelegateImpl::PlatformInitialize() {
349 plugin_
->SetWindow(windowed_handle_
);
352 CreateDummyWindowForActivation();
353 handle_event_pump_messages_event_
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
354 plugin_
->SetWindowlessData(
355 handle_event_pump_messages_event_
,
356 reinterpret_cast<gfx::NativeViewId
>(dummy_window_for_activation_
));
359 // Windowless plugins call the WindowFromPoint API and passes the result of
360 // that to the TrackPopupMenu API call as the owner window. This causes the
361 // API to fail as the API expects the window handle to live on the same
362 // thread as the caller. It works in the other browsers as the plugin lives
363 // on the browser thread. Our workaround is to intercept the TrackPopupMenu
364 // API and replace the window handle with the dummy activation window.
365 if (windowless_
&& !g_iat_patch_track_popup_menu
.Pointer()->is_patched()) {
366 g_iat_patch_track_popup_menu
.Pointer()->Patch(
367 GetPluginPath().value().c_str(), "user32.dll", "TrackPopupMenu",
368 WebPluginDelegateImpl::TrackPopupMenuPatch
);
371 // Windowless plugins can set cursors by calling the SetCursor API. This
372 // works because the thread inputs of the browser UI thread and the plugin
373 // thread are attached. We intercept the SetCursor API for windowless
374 // plugins and remember the cursor being set. This is shipped over to the
375 // browser in the HandleEvent call, which ensures that the cursor does not
376 // change when a windowless plugin instance changes the cursor
377 // in a background tab.
378 if (windowless_
&& !g_iat_patch_set_cursor
.Pointer()->is_patched() &&
379 (quirks_
& PLUGIN_QUIRK_PATCH_SETCURSOR
)) {
380 g_iat_patch_set_cursor
.Pointer()->Patch(
381 GetPluginPath().value().c_str(), "user32.dll", "SetCursor",
382 WebPluginDelegateImpl::SetCursorPatch
);
385 // The windowed flash plugin has a bug which occurs when the plugin enters
386 // fullscreen mode. It basically captures the mouse on WM_LBUTTONDOWN and
387 // does not release capture correctly causing it to stop receiving
388 // subsequent mouse events. This problem is also seen in Safari where there
389 // is code to handle this in the wndproc. However the plugin subclasses the
390 // window again in WM_LBUTTONDOWN before entering full screen. As a result
391 // Safari does not receive the WM_LBUTTONUP message. To workaround this
392 // issue we use a per thread mouse hook. This bug does not occur in Firefox
393 // and opera. Firefox has code similar to Safari. It could well be a bug in
394 // the flash plugin, which only occurs in webkit based browsers.
395 if (quirks_
& PLUGIN_QUIRK_HANDLE_MOUSE_CAPTURE
) {
396 mouse_hook_
= SetWindowsHookEx(WH_MOUSE
, MouseHookProc
, NULL
,
397 GetCurrentThreadId());
400 // On XP, WMP will use its old UI unless a registry key under HKLM has the
401 // name of the current process. We do it in the installer for admin users,
402 // for the rest patch this function.
403 if ((quirks_
& PLUGIN_QUIRK_PATCH_REGENUMKEYEXW
) &&
404 base::win::GetVersion() == base::win::VERSION_XP
&&
405 (base::win::RegKey().Open(HKEY_LOCAL_MACHINE
,
406 L
"SOFTWARE\\Microsoft\\MediaPlayer\\ShimInclusionList\\chrome.exe",
407 KEY_READ
) != ERROR_SUCCESS
) &&
408 !g_iat_patch_reg_enum_key_ex_w
.Pointer()->is_patched()) {
409 g_iat_patch_reg_enum_key_ex_w
.Pointer()->Patch(
410 L
"wmpdxm.dll", "advapi32.dll", "RegEnumKeyExW",
411 WebPluginDelegateImpl::RegEnumKeyExWPatch
);
414 // Flash retrieves the pointers to IMM32 functions with GetProcAddress() calls
415 // and use them to retrieve IME data. We add a patch to this function so we
416 // can dispatch these IMM32 calls to the WebPluginIMEWin class, which emulates
417 // IMM32 functions for Flash.
418 if (!g_iat_patch_get_proc_address
.Pointer()->is_patched() &&
419 (quirks_
& PLUGIN_QUIRK_EMULATE_IME
)) {
420 g_iat_patch_get_proc_address
.Pointer()->Patch(
421 GetPluginPath().value().c_str(), "kernel32.dll", "GetProcAddress",
422 GetProcAddressPatch
);
425 #if defined(USE_AURA)
426 if (windowless_
&& !g_iat_patch_window_from_point
.Pointer()->is_patched() &&
427 (quirks_
& PLUGIN_QUIRK_FAKE_WINDOW_FROM_POINT
)) {
428 g_iat_patch_window_from_point
.Pointer()->Patch(
429 GetPluginPath().value().c_str(), "user32.dll", "WindowFromPoint",
430 WebPluginDelegateImpl::WindowFromPointPatch
);
436 void WebPluginDelegateImpl::PlatformDestroyInstance() {
437 if (!instance_
->plugin_lib())
440 // Unpatch if this is the last plugin instance.
441 if (instance_
->plugin_lib()->instance_count() != 1)
444 if (g_iat_patch_set_cursor
.Pointer()->is_patched())
445 g_iat_patch_set_cursor
.Pointer()->Unpatch();
447 if (g_iat_patch_track_popup_menu
.Pointer()->is_patched())
448 g_iat_patch_track_popup_menu
.Pointer()->Unpatch();
450 if (g_iat_patch_reg_enum_key_ex_w
.Pointer()->is_patched())
451 g_iat_patch_reg_enum_key_ex_w
.Pointer()->Unpatch();
453 #if defined(USE_AURA)
454 if (g_iat_patch_window_from_point
.Pointer()->is_patched())
455 g_iat_patch_window_from_point
.Pointer()->Unpatch();
459 UnhookWindowsHookEx(mouse_hook_
);
464 void WebPluginDelegateImpl::Paint(SkCanvas
* canvas
, const gfx::Rect
& rect
) {
465 if (windowless_
&& skia::SupportsPlatformPaint(canvas
)) {
466 skia::ScopedPlatformPaint
scoped_platform_paint(canvas
);
467 HDC hdc
= scoped_platform_paint
.GetPlatformSurface();
468 WindowlessPaint(hdc
, rect
);
472 bool WebPluginDelegateImpl::WindowedCreatePlugin() {
473 DCHECK(!windowed_handle_
);
475 RegisterNativeWindowClass();
477 // The window will be sized and shown later.
478 windowed_handle_
= CreateWindowEx(
479 WS_EX_LEFT
| WS_EX_LTRREADING
| WS_EX_RIGHTSCROLLBAR
,
480 kNativeWindowClassName
,
482 WS_POPUP
| WS_CLIPCHILDREN
| WS_CLIPSIBLINGS
,
489 GetModuleHandle(NULL
),
491 if (windowed_handle_
== 0)
494 // This is a tricky workaround for Issue 2673 in chromium "Flash: IME not
495 // available". To use IMEs in this window, we have to make Windows attach
496 // IMEs to this window (i.e. load IME DLLs, attach them to this process, and
497 // add their message hooks to this window). Windows attaches IMEs while this
498 // process creates a top-level window. On the other hand, to layout this
499 // window correctly in the given parent window (RenderWidgetHostViewWin or
500 // RenderWidgetHostViewAura), this window should be a child window of the
501 // parent window. To satisfy both of the above conditions, this code once
502 // creates a top-level window and change it to a child window of the parent
503 // window (in the browser process).
504 SetWindowLongPtr(windowed_handle_
, GWL_STYLE
,
505 WS_CHILD
| WS_CLIPCHILDREN
| WS_CLIPSIBLINGS
);
507 BOOL result
= SetProp(windowed_handle_
, kWebPluginDelegateProperty
, this);
508 DCHECK(result
== TRUE
) << "SetProp failed, last error = " << GetLastError();
509 // Get the name and version of the plugin, create atoms and set them in a
510 // window property. Use atoms so that other processes can access the name and
511 // version of the plugin that this window is hosting.
512 if (instance_
!= NULL
) {
513 PluginLib
* plugin_lib
= instance()->plugin_lib();
514 if (plugin_lib
!= NULL
) {
515 std::wstring plugin_name
= plugin_lib
->plugin_info().name
;
516 if (!plugin_name
.empty()) {
517 ATOM plugin_name_atom
= GlobalAddAtomW(plugin_name
.c_str());
518 DCHECK_NE(0, plugin_name_atom
);
519 result
= SetProp(windowed_handle_
,
520 kPluginNameAtomProperty
,
521 reinterpret_cast<HANDLE
>(plugin_name_atom
));
522 DCHECK(result
== TRUE
) << "SetProp failed, last error = " <<
525 base::string16 plugin_version
= plugin_lib
->plugin_info().version
;
526 if (!plugin_version
.empty()) {
527 ATOM plugin_version_atom
= GlobalAddAtomW(plugin_version
.c_str());
528 DCHECK_NE(0, plugin_version_atom
);
529 result
= SetProp(windowed_handle_
,
530 kPluginVersionAtomProperty
,
531 reinterpret_cast<HANDLE
>(plugin_version_atom
));
532 DCHECK(result
== TRUE
) << "SetProp failed, last error = " <<
538 // Calling SetWindowLongPtrA here makes the window proc ASCII, which is
539 // required by at least the Shockwave Director plug-in.
540 SetWindowLongPtrA(windowed_handle_
,
542 reinterpret_cast<LONG_PTR
>(DefWindowProcA
));
547 void WebPluginDelegateImpl::WindowedDestroyWindow() {
548 if (windowed_handle_
!= NULL
) {
549 // Unsubclass the window.
550 WNDPROC current_wnd_proc
= reinterpret_cast<WNDPROC
>(
551 GetWindowLongPtr(windowed_handle_
, GWLP_WNDPROC
));
552 if (current_wnd_proc
== NativeWndProc
) {
553 SetWindowLongPtr(windowed_handle_
,
555 reinterpret_cast<LONG_PTR
>(plugin_wnd_proc_
));
558 plugin_
->WillDestroyWindow(windowed_handle_
);
560 DestroyWindow(windowed_handle_
);
561 windowed_handle_
= 0;
565 // Erase all messages in the queue destined for a particular window.
566 // When windows are closing, callers should use this function to clear
569 void WebPluginDelegateImpl::ClearThrottleQueueForWindow(HWND window
) {
570 ThrottleQueue
* throttle_queue
= g_throttle_queue
.Pointer();
572 ThrottleQueue::iterator it
;
573 for (it
= throttle_queue
->begin(); it
!= throttle_queue
->end(); ) {
574 if (it
->hwnd
== window
) {
575 it
= throttle_queue
->erase(it
);
582 // Delayed callback for processing throttled messages.
583 // Throttled messages are aggregated globally across all plugins.
585 void WebPluginDelegateImpl::OnThrottleMessage() {
586 // The current algorithm walks the list and processes the first
587 // message it finds for each plugin. It is important to service
588 // all active plugins with each pass through the throttle, otherwise
589 // we see video jankiness. Copy the set to notify before notifying
590 // since we may re-enter OnThrottleMessage from CallWindowProc!
591 ThrottleQueue
* throttle_queue
= g_throttle_queue
.Pointer();
592 ThrottleQueue notify_queue
;
593 std::set
<HWND
> processed
;
595 ThrottleQueue::iterator it
= throttle_queue
->begin();
596 while (it
!= throttle_queue
->end()) {
597 const MSG
& msg
= *it
;
598 if (processed
.find(msg
.hwnd
) == processed
.end()) {
599 processed
.insert(msg
.hwnd
);
600 notify_queue
.push_back(msg
);
601 it
= throttle_queue
->erase(it
);
607 // Due to re-entrancy, we must save our queue state now. Otherwise, we may
608 // self-post below, and *also* start up another delayed task when the first
609 // entry is pushed onto the queue in ThrottleMessage().
610 bool throttle_queue_was_empty
= throttle_queue
->empty();
612 for (it
= notify_queue
.begin(); it
!= notify_queue
.end(); ++it
) {
613 const MSG
& msg
= *it
;
614 WNDPROC proc
= reinterpret_cast<WNDPROC
>(msg
.time
);
615 // It is possible that the window was closed after we queued
616 // this message. This is a rare event; just verify the window
617 // is alive. (see also bug 1259488)
618 if (IsWindow(msg
.hwnd
))
619 CallWindowProc(proc
, msg
.hwnd
, msg
.message
, msg
.wParam
, msg
.lParam
);
622 if (!throttle_queue_was_empty
) {
623 base::MessageLoop::current()->PostDelayedTask(
625 base::Bind(&WebPluginDelegateImpl::OnThrottleMessage
),
626 base::TimeDelta::FromMilliseconds(kFlashWMUSERMessageThrottleDelayMs
));
630 // Schedule a windows message for delivery later.
632 void WebPluginDelegateImpl::ThrottleMessage(WNDPROC proc
, HWND hwnd
,
633 UINT message
, WPARAM wParam
,
636 msg
.time
= reinterpret_cast<DWORD
>(proc
);
638 msg
.message
= message
;
642 ThrottleQueue
* throttle_queue
= g_throttle_queue
.Pointer();
644 throttle_queue
->push_back(msg
);
646 if (throttle_queue
->size() == 1) {
647 base::MessageLoop::current()->PostDelayedTask(
649 base::Bind(&WebPluginDelegateImpl::OnThrottleMessage
),
650 base::TimeDelta::FromMilliseconds(kFlashWMUSERMessageThrottleDelayMs
));
654 // We go out of our way to find the hidden windows created by Flash for
655 // windowless plugins. We throttle the rate at which they deliver messages
656 // so that they will not consume outrageous amounts of CPU.
658 LRESULT CALLBACK
WebPluginDelegateImpl::FlashWindowlessWndProc(
659 HWND hwnd
, UINT message
, WPARAM wparam
, LPARAM lparam
) {
660 std::map
<HWND
, WNDPROC
>::iterator index
=
661 g_window_handle_proc_map
.Get().find(hwnd
);
663 WNDPROC old_proc
= (*index
).second
;
668 WebPluginDelegateImpl::ClearThrottleQueueForWindow(hwnd
);
669 g_window_handle_proc_map
.Get().erase(index
);
672 // Flash may flood the message queue with WM_USER+1 message causing 100% CPU
673 // usage. See https://bugzilla.mozilla.org/show_bug.cgi?id=132759. We
674 // prevent this by throttling the messages.
676 WebPluginDelegateImpl::ThrottleMessage(old_proc
, hwnd
, message
, wparam
,
685 return CallWindowProc(old_proc
, hwnd
, message
, wparam
, lparam
);
688 LRESULT CALLBACK
WebPluginDelegateImpl::DummyWindowProc(
689 HWND hwnd
, UINT message
, WPARAM w_param
, LPARAM l_param
) {
690 WebPluginDelegateImpl
* delegate
= reinterpret_cast<WebPluginDelegateImpl
*>(
691 GetProp(hwnd
, kWebPluginDelegateProperty
));
693 if (message
== WM_WINDOWPOSCHANGING
) {
694 // We need to know when the dummy window is parented because windowless
695 // plugins need the parent window for things like menus. There's no message
696 // for a parent being changed, but a WM_WINDOWPOSCHANGING is sent so we
697 // check every time we get it.
698 // For non-aura builds, this never changes since RenderWidgetHostViewWin's
699 // window is constant. For aura builds, this changes every time the tab gets
700 // dragged to a new window.
701 HWND parent
= GetParent(hwnd
);
702 if (parent
!= delegate
->dummy_window_parent_
) {
703 delegate
->dummy_window_parent_
= parent
;
705 // Set the containing window handle as the instance window handle. This is
706 // what Safari does. Not having a valid window handle causes subtle bugs
707 // with plugins which retrieve the window handle and use it for things
708 // like context menus. The window handle can be retrieved via
709 // NPN_GetValue of NPNVnetscapeWindow.
710 delegate
->instance_
->set_window_handle(parent
);
712 // The plugin caches the result of NPNVnetscapeWindow when we originally
713 // called NPP_SetWindow, so force it to get the new value.
714 delegate
->WindowlessSetWindow();
716 } else if (message
== WM_NCDESTROY
) {
717 RemoveProp(hwnd
, kWebPluginDelegateProperty
);
719 return CallWindowProc(
720 delegate
->old_dummy_window_proc_
, hwnd
, message
, w_param
, l_param
);
723 // Callback for enumerating the Flash windows.
724 BOOL CALLBACK
EnumFlashWindows(HWND window
, LPARAM arg
) {
725 WNDPROC wnd_proc
= reinterpret_cast<WNDPROC
>(arg
);
726 TCHAR class_name
[1024];
727 if (!RealGetWindowClass(window
, class_name
,
728 sizeof(class_name
)/sizeof(TCHAR
))) {
729 LOG(ERROR
) << "RealGetWindowClass failure: " << GetLastError();
733 if (wcscmp(class_name
, L
"SWFlash_PlaceholderX"))
736 WNDPROC current_wnd_proc
= reinterpret_cast<WNDPROC
>(
737 GetWindowLongPtr(window
, GWLP_WNDPROC
));
738 if (current_wnd_proc
!= wnd_proc
) {
739 WNDPROC old_flash_proc
= reinterpret_cast<WNDPROC
>(SetWindowLongPtr(
740 window
, GWLP_WNDPROC
,
741 reinterpret_cast<LONG_PTR
>(wnd_proc
)));
742 DCHECK(old_flash_proc
);
743 g_window_handle_proc_map
.Get()[window
] = old_flash_proc
;
749 bool WebPluginDelegateImpl::CreateDummyWindowForActivation() {
750 DCHECK(!dummy_window_for_activation_
);
752 dummy_window_for_activation_
= CreateWindowEx(
755 kDummyActivationWindowName
,
761 // We don't know the parent of the dummy window yet, so just set it to the
762 // desktop and it'll get parented by the browser.
765 GetModuleHandle(NULL
),
768 if (dummy_window_for_activation_
== 0)
771 BOOL result
= SetProp(dummy_window_for_activation_
,
772 kWebPluginDelegateProperty
, this);
773 DCHECK(result
== TRUE
) << "SetProp failed, last error = " << GetLastError();
774 old_dummy_window_proc_
= reinterpret_cast<WNDPROC
>(SetWindowLongPtr(
775 dummy_window_for_activation_
, GWLP_WNDPROC
,
776 reinterpret_cast<LONG_PTR
>(DummyWindowProc
)));
778 // Flash creates background windows which use excessive CPU in our
779 // environment; we wrap these windows and throttle them so that they don't
781 if (!EnumThreadWindows(::GetCurrentThreadId(), EnumFlashWindows
,
782 reinterpret_cast<LPARAM
>(
783 &WebPluginDelegateImpl::FlashWindowlessWndProc
))) {
784 // Log that this happened. Flash will still work; it just means the
785 // throttle isn't installed (and Flash will use more CPU).
787 LOG(ERROR
) << "Failed to wrap all windowless Flash windows";
792 bool WebPluginDelegateImpl::WindowedReposition(
793 const gfx::Rect
& window_rect_in_dip
,
794 const gfx::Rect
& clip_rect_in_dip
) {
795 if (!windowed_handle_
) {
800 gfx::Rect window_rect
= gfx::win::DIPToScreenRect(window_rect_in_dip
);
801 gfx::Rect clip_rect
= gfx::win::DIPToScreenRect(clip_rect_in_dip
);
802 if (window_rect_
== window_rect
&& clip_rect_
== clip_rect
)
805 // We only set the plugin's size here. Its position is moved elsewhere, which
806 // allows the window moves/scrolling/clipping to be synchronized with the page
807 // and other windows.
808 // If the plugin window has no parent, then don't focus it because it isn't
809 // being displayed anywhere. See:
810 // http://code.google.com/p/chromium/issues/detail?id=32658
811 if (window_rect
.size() != window_rect_
.size()) {
812 UINT flags
= SWP_NOMOVE
| SWP_NOZORDER
;
813 if (!GetParent(windowed_handle_
))
814 flags
|= SWP_NOACTIVATE
;
815 ::SetWindowPos(windowed_handle_
,
820 window_rect
.height(),
824 window_rect_
= window_rect
;
825 clip_rect_
= clip_rect
;
827 // Ensure that the entire window gets repainted.
828 ::InvalidateRect(windowed_handle_
, NULL
, FALSE
);
833 void WebPluginDelegateImpl::WindowedSetWindow() {
837 if (!windowed_handle_
) {
842 instance()->set_window_handle(windowed_handle_
);
844 DCHECK(!instance()->windowless());
846 window_
.clipRect
.top
= std::max(0, clip_rect_
.y());
847 window_
.clipRect
.left
= std::max(0, clip_rect_
.x());
848 window_
.clipRect
.bottom
= std::max(0, clip_rect_
.y() + clip_rect_
.height());
849 window_
.clipRect
.right
= std::max(0, clip_rect_
.x() + clip_rect_
.width());
850 window_
.height
= window_rect_
.height();
851 window_
.width
= window_rect_
.width();
855 window_
.window
= windowed_handle_
;
856 window_
.type
= NPWindowTypeWindow
;
858 // Reset this flag before entering the instance in case of side-effects.
859 windowed_did_set_window_
= true;
861 NPError err
= instance()->NPP_SetWindow(&window_
);
862 if (quirks_
& PLUGIN_QUIRK_SETWINDOW_TWICE
)
863 instance()->NPP_SetWindow(&window_
);
865 WNDPROC current_wnd_proc
= reinterpret_cast<WNDPROC
>(
866 GetWindowLongPtr(windowed_handle_
, GWLP_WNDPROC
));
867 if (current_wnd_proc
!= NativeWndProc
) {
868 plugin_wnd_proc_
= reinterpret_cast<WNDPROC
>(
869 SetWindowLongPtr(windowed_handle_
,
871 reinterpret_cast<LONG_PTR
>(NativeWndProc
)));
875 ATOM
WebPluginDelegateImpl::RegisterNativeWindowClass() {
876 static bool have_registered_window_class
= false;
877 if (have_registered_window_class
== true)
880 have_registered_window_class
= true;
883 wcex
.cbSize
= sizeof(WNDCLASSEX
);
884 wcex
.style
= CS_DBLCLKS
;
885 wcex
.lpfnWndProc
= WrapperWindowProc
;
888 wcex
.hInstance
= GetModuleHandle(NULL
);
891 // Some plugins like windows media player 11 create child windows parented
892 // by our plugin window, where the media content is rendered. These plugins
893 // dont implement WM_ERASEBKGND, which causes painting issues, when the
894 // window where the media is rendered is moved around. DefWindowProc does
895 // implement WM_ERASEBKGND correctly if we have a valid background brush.
896 wcex
.hbrBackground
= reinterpret_cast<HBRUSH
>(COLOR_WINDOW
+1);
897 wcex
.lpszMenuName
= 0;
898 wcex
.lpszClassName
= kNativeWindowClassName
;
901 return RegisterClassEx(&wcex
);
904 LRESULT CALLBACK
WebPluginDelegateImpl::WrapperWindowProc(
905 HWND hWnd
, UINT message
, WPARAM wParam
, LPARAM lParam
) {
906 // This is another workaround for Issue 2673 in chromium "Flash: IME not
907 // available". Somehow, the CallWindowProc() function does not dispatch
908 // window messages when its first parameter is a handle representing the
909 // DefWindowProc() function. To avoid this problem, this code creates a
910 // wrapper function which just encapsulates the DefWindowProc() function
911 // and set it as the window procedure of a windowed plug-in.
912 return DefWindowProc(hWnd
, message
, wParam
, lParam
);
915 // Returns true if the message passed in corresponds to a user gesture.
916 static bool IsUserGestureMessage(unsigned int message
) {
935 LRESULT CALLBACK
WebPluginDelegateImpl::NativeWndProc(
936 HWND hwnd
, UINT message
, WPARAM wparam
, LPARAM lparam
) {
937 WebPluginDelegateImpl
* delegate
= reinterpret_cast<WebPluginDelegateImpl
*>(
938 GetProp(hwnd
, kWebPluginDelegateProperty
));
944 if (message
== delegate
->last_message_
&&
945 delegate
->GetQuirks() & PLUGIN_QUIRK_DONT_CALL_WND_PROC_RECURSIVELY
&&
946 delegate
->is_calling_wndproc
) {
947 // Real may go into a state where it recursively dispatches the same event
948 // when subclassed. See https://bugzilla.mozilla.org/show_bug.cgi?id=192914
949 // We only do the recursive check for Real because it's possible and valid
950 // for a plugin to synchronously dispatch a message to itself such that it
951 // looks like it's in recursion.
955 // Flash may flood the message queue with WM_USER+1 message causing 100% CPU
956 // usage. See https://bugzilla.mozilla.org/show_bug.cgi?id=132759. We
957 // prevent this by throttling the messages.
958 if (message
== WM_USER
+ 1 &&
959 delegate
->GetQuirks() & PLUGIN_QUIRK_THROTTLE_WM_USER_PLUS_ONE
) {
960 WebPluginDelegateImpl::ThrottleMessage(delegate
->plugin_wnd_proc_
, hwnd
,
961 message
, wparam
, lparam
);
966 uint32 old_message
= delegate
->last_message_
;
967 delegate
->last_message_
= message
;
969 static UINT custom_msg
= RegisterWindowMessage(kPaintMessageName
);
970 if (message
== custom_msg
) {
971 // Get the invalid rect which is in screen coordinates and convert to
972 // window coordinates.
973 gfx::Rect invalid_rect
;
974 invalid_rect
.set_x(static_cast<short>(LOWORD(wparam
)));
975 invalid_rect
.set_y(static_cast<short>(HIWORD(wparam
)));
976 invalid_rect
.set_width(static_cast<short>(LOWORD(lparam
)));
977 invalid_rect
.set_height(static_cast<short>(HIWORD(lparam
)));
980 GetWindowRect(hwnd
, &window_rect
);
981 invalid_rect
.Offset(-window_rect
.left
, -window_rect
.top
);
983 // The plugin window might have non-client area. If we don't pass in
984 // RDW_FRAME then the children don't receive WM_NCPAINT messages while
985 // scrolling, which causes painting problems (http://b/issue?id=923945).
986 uint32 flags
= RDW_INVALIDATE
| RDW_ALLCHILDREN
| RDW_FRAME
;
988 // If a plugin (like Google Earth or Java) has child windows that are hosted
989 // in a different process, then RedrawWindow with UPDATENOW will
990 // synchronously wait for this call to complete. Some messages are pumped
991 // but not others, which could lead to a deadlock. So avoid reentrancy by
992 // only synchronously calling RedrawWindow once at a time.
993 if (old_message
!= custom_msg
)
994 flags
|= RDW_UPDATENOW
;
995 RECT rect
= invalid_rect
.ToRECT();
996 RedrawWindow(hwnd
, &rect
, NULL
, flags
);
999 delegate
->is_calling_wndproc
= true;
1001 if (!delegate
->user_gesture_message_posted_
&&
1002 IsUserGestureMessage(message
)) {
1003 delegate
->user_gesture_message_posted_
= true;
1005 delegate
->instance()->PushPopupsEnabledState(true);
1007 base::MessageLoop::current()->PostDelayedTask(
1009 base::Bind(&WebPluginDelegateImpl::OnUserGestureEnd
,
1010 delegate
->user_gesture_msg_factory_
.GetWeakPtr()),
1011 base::TimeDelta::FromMilliseconds(kWindowedPluginPopupTimerMs
));
1014 HandleCaptureForMessage(hwnd
, message
);
1016 // Maintain a local/global stack for the g_current_plugin_instance variable
1017 // as this may be a nested invocation.
1018 WebPluginDelegateImpl
* last_plugin_instance
= g_current_plugin_instance
;
1020 g_current_plugin_instance
= delegate
;
1022 result
= CallWindowProc(
1023 delegate
->plugin_wnd_proc_
, hwnd
, message
, wparam
, lparam
);
1025 // The plugin instance may have been destroyed in the CallWindowProc call
1026 // above. This will also destroy the plugin window. Before attempting to
1027 // access the WebPluginDelegateImpl instance we validate if the window is
1029 if (::IsWindow(hwnd
))
1030 delegate
->is_calling_wndproc
= false;
1032 g_current_plugin_instance
= last_plugin_instance
;
1034 if (message
== WM_NCDESTROY
) {
1035 RemoveProp(hwnd
, kWebPluginDelegateProperty
);
1036 ATOM plugin_name_atom
= reinterpret_cast<ATOM
>(
1037 RemoveProp(hwnd
, kPluginNameAtomProperty
));
1038 if (plugin_name_atom
!= 0)
1039 GlobalDeleteAtom(plugin_name_atom
);
1040 ATOM plugin_version_atom
= reinterpret_cast<ATOM
>(
1041 RemoveProp(hwnd
, kPluginVersionAtomProperty
));
1042 if (plugin_version_atom
!= 0)
1043 GlobalDeleteAtom(plugin_version_atom
);
1044 ClearThrottleQueueForWindow(hwnd
);
1047 if (::IsWindow(hwnd
))
1048 delegate
->last_message_
= old_message
;
1052 void WebPluginDelegateImpl::WindowlessUpdateGeometry(
1053 const gfx::Rect
& window_rect
,
1054 const gfx::Rect
& clip_rect
) {
1055 bool window_rect_changed
= (window_rect_
!= window_rect
);
1056 // Only resend to the instance if the geometry has changed.
1057 if (!window_rect_changed
&& clip_rect
== clip_rect_
)
1060 clip_rect_
= clip_rect
;
1061 window_rect_
= window_rect
;
1063 WindowlessSetWindow();
1065 if (window_rect_changed
) {
1066 WINDOWPOS win_pos
= {0};
1067 win_pos
.x
= window_rect_
.x();
1068 win_pos
.y
= window_rect_
.y();
1069 win_pos
.cx
= window_rect_
.width();
1070 win_pos
.cy
= window_rect_
.height();
1072 NPEvent pos_changed_event
;
1073 pos_changed_event
.event
= WM_WINDOWPOSCHANGED
;
1074 pos_changed_event
.wParam
= 0;
1075 pos_changed_event
.lParam
= PtrToUlong(&win_pos
);
1077 instance()->NPP_HandleEvent(&pos_changed_event
);
1081 void WebPluginDelegateImpl::WindowlessPaint(HDC hdc
,
1082 const gfx::Rect
& damage_rect
) {
1085 RECT damage_rect_win
;
1086 damage_rect_win
.left
= damage_rect
.x(); // + window_rect_.x();
1087 damage_rect_win
.top
= damage_rect
.y(); // + window_rect_.y();
1088 damage_rect_win
.right
= damage_rect_win
.left
+ damage_rect
.width();
1089 damage_rect_win
.bottom
= damage_rect_win
.top
+ damage_rect
.height();
1091 // Save away the old HDC as this could be a nested invocation.
1092 void* old_dc
= window_
.window
;
1093 window_
.window
= hdc
;
1095 NPEvent paint_event
;
1096 paint_event
.event
= WM_PAINT
;
1097 // NOTE: NPAPI is not 64bit safe. It puts pointers into 32bit values.
1098 paint_event
.wParam
= PtrToUlong(hdc
);
1099 paint_event
.lParam
= PtrToUlong(&damage_rect_win
);
1100 base::StatsRate
plugin_paint("Plugin.Paint");
1101 base::StatsScope
<base::StatsRate
> scope(plugin_paint
);
1102 instance()->NPP_HandleEvent(&paint_event
);
1103 window_
.window
= old_dc
;
1106 void WebPluginDelegateImpl::WindowlessSetWindow() {
1110 if (window_rect_
.IsEmpty()) // wait for geometry to be set.
1113 DCHECK(instance()->windowless());
1115 window_
.clipRect
.top
= clip_rect_
.y();
1116 window_
.clipRect
.left
= clip_rect_
.x();
1117 window_
.clipRect
.bottom
= clip_rect_
.y() + clip_rect_
.height();
1118 window_
.clipRect
.right
= clip_rect_
.x() + clip_rect_
.width();
1119 window_
.height
= window_rect_
.height();
1120 window_
.width
= window_rect_
.width();
1121 window_
.x
= window_rect_
.x();
1122 window_
.y
= window_rect_
.y();
1123 window_
.type
= NPWindowTypeDrawable
;
1124 DrawableContextEnforcer
enforcer(&window_
);
1126 NPError err
= instance()->NPP_SetWindow(&window_
);
1127 DCHECK(err
== NPERR_NO_ERROR
);
1130 bool WebPluginDelegateImpl::PlatformSetPluginHasFocus(bool focused
) {
1131 DCHECK(instance()->windowless());
1133 NPEvent focus_event
;
1134 focus_event
.event
= focused
? WM_SETFOCUS
: WM_KILLFOCUS
;
1135 focus_event
.wParam
= 0;
1136 focus_event
.lParam
= 0;
1138 instance()->NPP_HandleEvent(&focus_event
);
1142 static bool NPEventFromWebMouseEvent(const WebMouseEvent
& event
,
1143 NPEvent
* np_event
) {
1144 np_event
->lParam
= static_cast<uint32
>(MAKELPARAM(event
.windowX
,
1146 np_event
->wParam
= 0;
1148 if (event
.modifiers
& WebInputEvent::ControlKey
)
1149 np_event
->wParam
|= MK_CONTROL
;
1150 if (event
.modifiers
& WebInputEvent::ShiftKey
)
1151 np_event
->wParam
|= MK_SHIFT
;
1152 if (event
.modifiers
& WebInputEvent::LeftButtonDown
)
1153 np_event
->wParam
|= MK_LBUTTON
;
1154 if (event
.modifiers
& WebInputEvent::MiddleButtonDown
)
1155 np_event
->wParam
|= MK_MBUTTON
;
1156 if (event
.modifiers
& WebInputEvent::RightButtonDown
)
1157 np_event
->wParam
|= MK_RBUTTON
;
1159 switch (event
.type
) {
1160 case WebInputEvent::MouseMove
:
1161 case WebInputEvent::MouseLeave
:
1162 case WebInputEvent::MouseEnter
:
1163 np_event
->event
= WM_MOUSEMOVE
;
1165 case WebInputEvent::MouseDown
:
1166 switch (event
.button
) {
1167 case WebMouseEvent::ButtonLeft
:
1168 np_event
->event
= WM_LBUTTONDOWN
;
1170 case WebMouseEvent::ButtonMiddle
:
1171 np_event
->event
= WM_MBUTTONDOWN
;
1173 case WebMouseEvent::ButtonRight
:
1174 np_event
->event
= WM_RBUTTONDOWN
;
1178 case WebInputEvent::MouseUp
:
1179 switch (event
.button
) {
1180 case WebMouseEvent::ButtonLeft
:
1181 np_event
->event
= WM_LBUTTONUP
;
1183 case WebMouseEvent::ButtonMiddle
:
1184 np_event
->event
= WM_MBUTTONUP
;
1186 case WebMouseEvent::ButtonRight
:
1187 np_event
->event
= WM_RBUTTONUP
;
1197 static bool NPEventFromWebKeyboardEvent(const WebKeyboardEvent
& event
,
1198 NPEvent
* np_event
) {
1199 np_event
->wParam
= event
.windowsKeyCode
;
1201 switch (event
.type
) {
1202 case WebInputEvent::KeyDown
:
1203 np_event
->event
= WM_KEYDOWN
;
1204 np_event
->lParam
= 0;
1206 case WebInputEvent::Char
:
1207 np_event
->event
= WM_CHAR
;
1208 np_event
->lParam
= 0;
1210 case WebInputEvent::KeyUp
:
1211 np_event
->event
= WM_KEYUP
;
1212 np_event
->lParam
= 0x8000;
1220 static bool NPEventFromWebInputEvent(const WebInputEvent
& event
,
1221 NPEvent
* np_event
) {
1222 switch (event
.type
) {
1223 case WebInputEvent::MouseMove
:
1224 case WebInputEvent::MouseLeave
:
1225 case WebInputEvent::MouseEnter
:
1226 case WebInputEvent::MouseDown
:
1227 case WebInputEvent::MouseUp
:
1228 if (event
.size
< sizeof(WebMouseEvent
)) {
1232 return NPEventFromWebMouseEvent(
1233 *static_cast<const WebMouseEvent
*>(&event
), np_event
);
1234 case WebInputEvent::KeyDown
:
1235 case WebInputEvent::Char
:
1236 case WebInputEvent::KeyUp
:
1237 if (event
.size
< sizeof(WebKeyboardEvent
)) {
1241 return NPEventFromWebKeyboardEvent(
1242 *static_cast<const WebKeyboardEvent
*>(&event
), np_event
);
1248 bool WebPluginDelegateImpl::PlatformHandleInputEvent(
1249 const WebInputEvent
& event
, WebCursor::CursorInfo
* cursor_info
) {
1250 DCHECK(cursor_info
!= NULL
);
1253 if (!NPEventFromWebInputEvent(event
, &np_event
)) {
1257 // Allow this plug-in to access this IME emulator through IMM32 API while the
1258 // plug-in is processing this event.
1259 if (GetQuirks() & PLUGIN_QUIRK_EMULATE_IME
) {
1261 plugin_ime_
.reset(new WebPluginIMEWin
);
1263 WebPluginIMEWin::ScopedLock
lock(
1264 event
.isKeyboardEventType(event
.type
) ? plugin_ime_
.get() : NULL
);
1266 HWND last_focus_window
= NULL
;
1268 if (ShouldTrackEventForModalLoops(&np_event
)) {
1269 // A windowless plugin can enter a modal loop in a NPP_HandleEvent call.
1270 // For e.g. Flash puts up a context menu when we right click on the
1271 // windowless plugin area. We detect this by setting up a message filter
1272 // hook pror to calling NPP_HandleEvent on the plugin and unhook on
1273 // return from NPP_HandleEvent. If the plugin does enter a modal loop
1274 // in that context we unhook on receiving the first notification in
1275 // the message filter hook.
1276 handle_event_message_filter_hook_
=
1277 SetWindowsHookEx(WH_MSGFILTER
, HandleEventMessageFilterHook
, NULL
,
1278 GetCurrentThreadId());
1279 // To ensure that the plugin receives keyboard events we set focus to the
1281 // TODO(iyengar) We need a framework in the renderer to identify which
1282 // windowless plugin is under the mouse and to handle this. This would
1283 // also require some changes in RenderWidgetHost to detect this in the
1284 // WM_MOUSEACTIVATE handler and inform the renderer accordingly.
1285 bool valid
= GetParent(dummy_window_for_activation_
) != GetDesktopWindow();
1287 last_focus_window
= ::SetFocus(dummy_window_for_activation_
);
1289 NOTREACHED() << "Dummy window not parented";
1293 bool old_task_reentrancy_state
=
1294 base::MessageLoop::current()->NestableTasksAllowed();
1296 // Maintain a local/global stack for the g_current_plugin_instance variable
1297 // as this may be a nested invocation.
1298 WebPluginDelegateImpl
* last_plugin_instance
= g_current_plugin_instance
;
1300 g_current_plugin_instance
= this;
1302 handle_event_depth_
++;
1304 bool popups_enabled
= false;
1306 if (IsUserGestureMessage(np_event
.event
)) {
1307 instance()->PushPopupsEnabledState(true);
1308 popups_enabled
= true;
1311 bool ret
= instance()->NPP_HandleEvent(&np_event
) != 0;
1313 if (popups_enabled
) {
1314 instance()->PopPopupsEnabledState();
1317 // Flash and SilverLight always return false, even when they swallow the
1318 // event. Flash does this because it passes the event to its window proc,
1319 // which is supposed to return 0 if an event was handled. There are few
1320 // exceptions, such as IME, where it sometimes returns true.
1323 if (np_event
.event
== WM_MOUSEMOVE
) {
1324 current_windowless_cursor_
.InitFromExternalCursor(GetCursor());
1325 // Snag a reference to the current cursor ASAP in case the plugin modified
1326 // it. There is a nasty race condition here with the multiprocess browser
1327 // as someone might be setting the cursor in the main process as well.
1328 current_windowless_cursor_
.GetCursorInfo(cursor_info
);
1331 handle_event_depth_
--;
1333 g_current_plugin_instance
= last_plugin_instance
;
1335 // We could have multiple NPP_HandleEvent calls nested together in case
1336 // the plugin enters a modal loop. Reset the pump messages event when
1337 // the outermost NPP_HandleEvent call unwinds.
1338 if (handle_event_depth_
== 0) {
1339 ResetEvent(handle_event_pump_messages_event_
);
1342 // If we didn't enter a modal loop, need to unhook the filter.
1343 if (handle_event_message_filter_hook_
) {
1344 UnhookWindowsHookEx(handle_event_message_filter_hook_
);
1345 handle_event_message_filter_hook_
= NULL
;
1348 if (::IsWindow(last_focus_window
)) {
1349 // Restore the nestable tasks allowed state in the message loop and reset
1350 // the os modal loop state as the plugin returned from the TrackPopupMenu
1352 base::MessageLoop::current()->SetNestableTasksAllowed(
1353 old_task_reentrancy_state
);
1354 base::MessageLoop::current()->set_os_modal_loop(false);
1355 // The Flash plugin at times sets focus to its hidden top level window
1356 // with class name SWFlash_PlaceholderX. This causes the chrome browser
1357 // window to receive a WM_ACTIVATEAPP message as a top level window from
1358 // another thread is now active. We end up in a state where the chrome
1359 // browser window is not active even though the user clicked on it.
1360 // Our workaround for this is to send over a raw
1361 // WM_LBUTTONDOWN/WM_LBUTTONUP combination to the last focus window, which
1363 if (dummy_window_for_activation_
!= ::GetFocus()) {
1364 INPUT input_info
= {0};
1365 input_info
.type
= INPUT_MOUSE
;
1366 input_info
.mi
.dwFlags
= MOUSEEVENTF_LEFTDOWN
;
1367 ::SendInput(1, &input_info
, sizeof(INPUT
));
1369 input_info
.type
= INPUT_MOUSE
;
1370 input_info
.mi
.dwFlags
= MOUSEEVENTF_LEFTUP
;
1371 ::SendInput(1, &input_info
, sizeof(INPUT
));
1373 ::SetFocus(last_focus_window
);
1380 void WebPluginDelegateImpl::OnModalLoopEntered() {
1381 DCHECK(handle_event_pump_messages_event_
!= NULL
);
1382 SetEvent(handle_event_pump_messages_event_
);
1384 base::MessageLoop::current()->SetNestableTasksAllowed(true);
1385 base::MessageLoop::current()->set_os_modal_loop(true);
1387 UnhookWindowsHookEx(handle_event_message_filter_hook_
);
1388 handle_event_message_filter_hook_
= NULL
;
1391 bool WebPluginDelegateImpl::ShouldTrackEventForModalLoops(NPEvent
* event
) {
1392 if (event
->event
== WM_RBUTTONDOWN
)
1397 void WebPluginDelegateImpl::OnUserGestureEnd() {
1398 user_gesture_message_posted_
= false;
1399 instance()->PopPopupsEnabledState();
1402 BOOL WINAPI
WebPluginDelegateImpl::TrackPopupMenuPatch(
1403 HMENU menu
, unsigned int flags
, int x
, int y
, int reserved
,
1404 HWND window
, const RECT
* rect
) {
1406 if (g_current_plugin_instance
) {
1407 unsigned long window_process_id
= 0;
1408 unsigned long window_thread_id
=
1409 GetWindowThreadProcessId(window
, &window_process_id
);
1410 // TrackPopupMenu fails if the window passed in belongs to a different
1412 if (::GetCurrentThreadId() != window_thread_id
) {
1414 GetParent(g_current_plugin_instance
->dummy_window_for_activation_
) !=
1417 window
= g_current_plugin_instance
->dummy_window_for_activation_
;
1419 NOTREACHED() << "Dummy window not parented";
1424 BOOL result
= TrackPopupMenu(menu
, flags
, x
, y
, reserved
, window
, rect
);
1428 HCURSOR WINAPI
WebPluginDelegateImpl::SetCursorPatch(HCURSOR cursor
) {
1429 // The windowless flash plugin periodically calls SetCursor in a wndproc
1430 // instantiated on the plugin thread. This causes annoying cursor flicker
1431 // when the mouse is moved on a foreground tab, with a windowless plugin
1432 // instance in a background tab. We just ignore the call here.
1433 if (!g_current_plugin_instance
) {
1434 HCURSOR current_cursor
= GetCursor();
1435 if (current_cursor
!= cursor
) {
1436 ::SetCursor(cursor
);
1438 return current_cursor
;
1440 return ::SetCursor(cursor
);
1443 LONG WINAPI
WebPluginDelegateImpl::RegEnumKeyExWPatch(
1444 HKEY key
, DWORD index
, LPWSTR name
, LPDWORD name_size
, LPDWORD reserved
,
1445 LPWSTR class_name
, LPDWORD class_size
, PFILETIME last_write_time
) {
1446 DWORD orig_size
= *name_size
;
1447 LONG rv
= RegEnumKeyExW(key
, index
, name
, name_size
, reserved
, class_name
,
1448 class_size
, last_write_time
);
1449 if (rv
== ERROR_SUCCESS
&&
1450 GetKeyPath(key
).find(L
"Microsoft\\MediaPlayer\\ShimInclusionList") !=
1451 std::wstring::npos
) {
1452 static const wchar_t kChromeExeName
[] = L
"chrome.exe";
1453 wcsncpy_s(name
, orig_size
, kChromeExeName
, arraysize(kChromeExeName
));
1455 std::min(orig_size
, static_cast<DWORD
>(arraysize(kChromeExeName
)));
1461 void WebPluginDelegateImpl::ImeCompositionUpdated(
1462 const base::string16
& text
,
1463 const std::vector
<int>& clauses
,
1464 const std::vector
<int>& target
,
1465 int cursor_position
) {
1467 plugin_ime_
.reset(new WebPluginIMEWin
);
1469 plugin_ime_
->CompositionUpdated(text
, clauses
, target
, cursor_position
);
1470 plugin_ime_
->SendEvents(instance());
1473 void WebPluginDelegateImpl::ImeCompositionCompleted(
1474 const base::string16
& text
) {
1476 plugin_ime_
.reset(new WebPluginIMEWin
);
1477 plugin_ime_
->CompositionCompleted(text
);
1478 plugin_ime_
->SendEvents(instance());
1481 bool WebPluginDelegateImpl::GetIMEStatus(int* input_type
,
1482 gfx::Rect
* caret_rect
) {
1485 return plugin_ime_
->GetStatus(input_type
, caret_rect
);
1489 FARPROC WINAPI
WebPluginDelegateImpl::GetProcAddressPatch(HMODULE module
,
1491 FARPROC imm_function
= WebPluginIMEWin::GetProcAddress(name
);
1493 return imm_function
;
1494 return ::GetProcAddress(module
, name
);
1497 #if defined(USE_AURA)
1498 HWND WINAPI
WebPluginDelegateImpl::WindowFromPointPatch(POINT point
) {
1499 HWND window
= WindowFromPoint(point
);
1500 if (::ScreenToClient(window
, &point
)) {
1501 HWND child
= ChildWindowFromPoint(window
, point
);
1502 if (::IsWindow(child
) &&
1503 ::GetProp(child
, content::kPluginDummyParentProperty
))
1510 void WebPluginDelegateImpl::HandleCaptureForMessage(HWND window
,
1512 if (gfx::GetClassName(window
) != base::string16(kNativeWindowClassName
))
1516 case WM_LBUTTONDOWN
:
1517 case WM_MBUTTONDOWN
:
1518 case WM_RBUTTONDOWN
:
1519 ::SetCapture(window
);
1520 // As per documentation the WM_PARENTNOTIFY message is sent to the parent
1521 // window chain if mouse input is received by the child window. However
1522 // the parent receives the WM_PARENTNOTIFY message only if we doubleclick
1523 // on the window. We send the WM_PARENTNOTIFY message for mouse input
1524 // messages to the parent to indicate that user action is expected.
1525 ::SendMessage(::GetParent(window
), WM_PARENTNOTIFY
, message
, 0);
1539 } // namespace content