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/cursors/webcursor.h"
31 #include "content/common/plugin_constants_win.h"
32 #include "content/public/common/content_constants.h"
33 #include "skia/ext/platform_canvas.h"
34 #include "third_party/WebKit/public/web/WebInputEvent.h"
35 #include "ui/gfx/win/dpi.h"
36 #include "ui/gfx/win/hwnd_util.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
;
91 base::LazyInstance
<base::win::IATPatchFunction
> g_iat_patch_window_from_point
=
92 LAZY_INSTANCE_INITIALIZER
;
94 // http://crbug.com/16114
95 // Enforces providing a valid device context in NPWindow, so that NPP_SetWindow
96 // is never called with NPNWindoTypeDrawable and NPWindow set to NULL.
97 // Doing so allows removing NPP_SetWindow call during painting a windowless
98 // plugin, which otherwise could trigger layout change while painting by
99 // invoking NPN_Evaluate. Which would cause bad, bad crashes. Bad crashes.
100 // TODO(dglazkov): If this approach doesn't produce regressions, move class to
101 // webplugin_delegate_impl.h and implement for other platforms.
102 class DrawableContextEnforcer
{
104 explicit DrawableContextEnforcer(NPWindow
* window
)
106 disposable_dc_(window
&& !window
->window
) {
107 // If NPWindow is NULL, create a device context with monochrome 1x1 surface
108 // and stuff it to NPWindow.
110 window_
->window
= CreateCompatibleDC(NULL
);
113 ~DrawableContextEnforcer() {
117 DeleteDC(static_cast<HDC
>(window_
->window
));
118 window_
->window
= NULL
;
126 // These are from ntddk.h
127 typedef LONG NTSTATUS
;
129 #ifndef STATUS_SUCCESS
130 #define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
133 #ifndef STATUS_BUFFER_TOO_SMALL
134 #define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
137 typedef enum _KEY_INFORMATION_CLASS
{
142 KeyCachedInformation
,
143 KeyVirtualizationInformation
144 } KEY_INFORMATION_CLASS
;
146 typedef struct _KEY_NAME_INFORMATION
{
149 } KEY_NAME_INFORMATION
, *PKEY_NAME_INFORMATION
;
151 typedef DWORD (__stdcall
*ZwQueryKeyType
)(
153 int key_information_class
,
154 PVOID key_information
,
156 PULONG result_length
);
158 // Returns a key's full path.
159 std::wstring
GetKeyPath(HKEY key
) {
163 HMODULE dll
= GetModuleHandle(L
"ntdll.dll");
167 ZwQueryKeyType func
= reinterpret_cast<ZwQueryKeyType
>(
168 ::GetProcAddress(dll
, "ZwQueryKey"));
174 result
= func(key
, KeyNameInformation
, 0, 0, &size
);
175 if (result
!= STATUS_BUFFER_TOO_SMALL
)
178 scoped_ptr
<char[]> buffer(new char[size
]);
179 if (buffer
.get() == NULL
)
182 result
= func(key
, KeyNameInformation
, buffer
.get(), size
, &size
);
183 if (result
!= STATUS_SUCCESS
)
186 KEY_NAME_INFORMATION
* info
=
187 reinterpret_cast<KEY_NAME_INFORMATION
*>(buffer
.get());
188 return std::wstring(info
->Name
, info
->NameLength
/ sizeof(wchar_t));
191 int GetPluginMajorVersion(const WebPluginInfo
& plugin_info
) {
192 Version plugin_version
;
193 WebPluginInfo::CreateVersionFromString(plugin_info
.version
, &plugin_version
);
195 int major_version
= 0;
196 if (plugin_version
.IsValid())
197 major_version
= plugin_version
.components()[0];
199 return major_version
;
204 LRESULT CALLBACK
WebPluginDelegateImpl::HandleEventMessageFilterHook(
205 int code
, WPARAM wParam
, LPARAM lParam
) {
206 if (g_current_plugin_instance
) {
207 g_current_plugin_instance
->OnModalLoopEntered();
211 return CallNextHookEx(NULL
, code
, wParam
, lParam
);
214 LRESULT CALLBACK
WebPluginDelegateImpl::MouseHookProc(
215 int code
, WPARAM wParam
, LPARAM lParam
) {
216 if (code
== HC_ACTION
) {
217 MOUSEHOOKSTRUCT
* hook_struct
= reinterpret_cast<MOUSEHOOKSTRUCT
*>(lParam
);
219 HandleCaptureForMessage(hook_struct
->hwnd
, wParam
);
222 return CallNextHookEx(NULL
, code
, wParam
, lParam
);
225 WebPluginDelegateImpl::WebPluginDelegateImpl(
227 PluginInstance
* instance
)
228 : instance_(instance
),
232 windowed_handle_(NULL
),
233 windowed_did_set_window_(false),
234 plugin_wnd_proc_(NULL
),
236 is_calling_wndproc(false),
237 dummy_window_for_activation_(NULL
),
238 dummy_window_parent_(NULL
),
239 old_dummy_window_proc_(NULL
),
240 handle_event_message_filter_hook_(NULL
),
241 handle_event_pump_messages_event_(NULL
),
242 user_gesture_message_posted_(false),
243 user_gesture_msg_factory_(this),
244 handle_event_depth_(0),
246 first_set_window_call_(true),
247 plugin_has_focus_(false),
248 has_webkit_focus_(false),
249 containing_view_has_focus_(true),
250 creation_succeeded_(false) {
251 memset(&window_
, 0, sizeof(window_
));
253 const WebPluginInfo
& plugin_info
= instance_
->plugin_lib()->plugin_info();
254 std::wstring filename
=
255 StringToLowerASCII(plugin_info
.path
.BaseName().value());
257 if (instance_
->mime_type() == kFlashPluginSwfMimeType
||
258 filename
== kFlashPlugin
) {
259 // Flash only requests windowless plugins if we return a Mozilla user
261 instance_
->set_use_mozilla_user_agent();
262 quirks_
|= PLUGIN_QUIRK_THROTTLE_WM_USER_PLUS_ONE
;
263 quirks_
|= PLUGIN_QUIRK_PATCH_SETCURSOR
;
264 quirks_
|= PLUGIN_QUIRK_ALWAYS_NOTIFY_SUCCESS
;
265 quirks_
|= PLUGIN_QUIRK_HANDLE_MOUSE_CAPTURE
;
266 quirks_
|= PLUGIN_QUIRK_EMULATE_IME
;
267 quirks_
|= PLUGIN_QUIRK_FAKE_WINDOW_FROM_POINT
;
268 } else if (filename
== kAcrobatReaderPlugin
) {
269 // Check for the version number above or equal 9.
270 int major_version
= GetPluginMajorVersion(plugin_info
);
271 if (major_version
>= 9) {
272 quirks_
|= PLUGIN_QUIRK_DIE_AFTER_UNLOAD
;
274 quirks_
|= PLUGIN_QUIRK_SETWINDOW_TWICE
;
276 quirks_
|= PLUGIN_QUIRK_BLOCK_NONSTANDARD_GETURL_REQUESTS
;
277 } else if (plugin_info
.name
.find(L
"Windows Media Player") !=
278 std::wstring::npos
) {
279 // Windows Media Player needs two NPP_SetWindow calls.
280 quirks_
|= PLUGIN_QUIRK_SETWINDOW_TWICE
;
282 // Windowless mode doesn't work in the WMP NPAPI plugin.
283 quirks_
|= PLUGIN_QUIRK_NO_WINDOWLESS
;
285 // The media player plugin sets its size on the first NPP_SetWindow call
286 // and never updates its size. We should call the underlying NPP_SetWindow
287 // only when we have the correct size.
288 quirks_
|= PLUGIN_QUIRK_IGNORE_FIRST_SETWINDOW_CALL
;
290 if (filename
== kOldWMPPlugin
) {
291 // Non-admin users on XP couldn't modify the key to force the new UI.
292 quirks_
|= PLUGIN_QUIRK_PATCH_REGENUMKEYEXW
;
294 } else if (instance_
->mime_type() == "audio/x-pn-realaudio-plugin" ||
295 filename
== kRealPlayerPlugin
) {
296 quirks_
|= PLUGIN_QUIRK_DONT_CALL_WND_PROC_RECURSIVELY
;
297 } else if (plugin_info
.name
.find(L
"VLC Multimedia Plugin") !=
298 std::wstring::npos
||
299 plugin_info
.name
.find(L
"VLC Multimedia Plug-in") !=
300 std::wstring::npos
) {
301 // VLC hangs on NPP_Destroy if we call NPP_SetWindow with a null window
303 quirks_
|= PLUGIN_QUIRK_DONT_SET_NULL_WINDOW_HANDLE_ON_DESTROY
;
304 int major_version
= GetPluginMajorVersion(plugin_info
);
305 if (major_version
== 0) {
306 // VLC 0.8.6d and 0.8.6e crash if multiple instances are created.
307 quirks_
|= PLUGIN_QUIRK_DONT_ALLOW_MULTIPLE_INSTANCES
;
309 } else if (filename
== kSilverlightPlugin
) {
310 // Explanation for this quirk can be found in
311 // WebPluginDelegateImpl::Initialize.
312 quirks_
|= PLUGIN_QUIRK_PATCH_SETCURSOR
;
313 } else if (plugin_info
.name
.find(L
"DivX Web Player") !=
314 std::wstring::npos
) {
315 // The divx plugin sets its size on the first NPP_SetWindow call and never
316 // updates its size. We should call the underlying NPP_SetWindow only when
317 // we have the correct size.
318 quirks_
|= PLUGIN_QUIRK_IGNORE_FIRST_SETWINDOW_CALL
;
322 WebPluginDelegateImpl::~WebPluginDelegateImpl() {
323 if (::IsWindow(dummy_window_for_activation_
)) {
324 WNDPROC current_wnd_proc
= reinterpret_cast<WNDPROC
>(
325 GetWindowLongPtr(dummy_window_for_activation_
, GWLP_WNDPROC
));
326 if (current_wnd_proc
== DummyWindowProc
) {
327 SetWindowLongPtr(dummy_window_for_activation_
,
329 reinterpret_cast<LONG_PTR
>(old_dummy_window_proc_
));
331 ::DestroyWindow(dummy_window_for_activation_
);
337 WindowedDestroyWindow();
339 if (handle_event_pump_messages_event_
) {
340 CloseHandle(handle_event_pump_messages_event_
);
344 bool WebPluginDelegateImpl::PlatformInitialize() {
345 plugin_
->SetWindow(windowed_handle_
);
348 CreateDummyWindowForActivation();
349 handle_event_pump_messages_event_
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
350 plugin_
->SetWindowlessData(
351 handle_event_pump_messages_event_
,
352 reinterpret_cast<gfx::NativeViewId
>(dummy_window_for_activation_
));
355 // Windowless plugins call the WindowFromPoint API and passes the result of
356 // that to the TrackPopupMenu API call as the owner window. This causes the
357 // API to fail as the API expects the window handle to live on the same
358 // thread as the caller. It works in the other browsers as the plugin lives
359 // on the browser thread. Our workaround is to intercept the TrackPopupMenu
360 // API and replace the window handle with the dummy activation window.
361 if (windowless_
&& !g_iat_patch_track_popup_menu
.Pointer()->is_patched()) {
362 g_iat_patch_track_popup_menu
.Pointer()->Patch(
363 GetPluginPath().value().c_str(), "user32.dll", "TrackPopupMenu",
364 WebPluginDelegateImpl::TrackPopupMenuPatch
);
367 // Windowless plugins can set cursors by calling the SetCursor API. This
368 // works because the thread inputs of the browser UI thread and the plugin
369 // thread are attached. We intercept the SetCursor API for windowless
370 // plugins and remember the cursor being set. This is shipped over to the
371 // browser in the HandleEvent call, which ensures that the cursor does not
372 // change when a windowless plugin instance changes the cursor
373 // in a background tab.
374 if (windowless_
&& !g_iat_patch_set_cursor
.Pointer()->is_patched() &&
375 (quirks_
& PLUGIN_QUIRK_PATCH_SETCURSOR
)) {
376 g_iat_patch_set_cursor
.Pointer()->Patch(
377 GetPluginPath().value().c_str(), "user32.dll", "SetCursor",
378 WebPluginDelegateImpl::SetCursorPatch
);
381 // The windowed flash plugin has a bug which occurs when the plugin enters
382 // fullscreen mode. It basically captures the mouse on WM_LBUTTONDOWN and
383 // does not release capture correctly causing it to stop receiving
384 // subsequent mouse events. This problem is also seen in Safari where there
385 // is code to handle this in the wndproc. However the plugin subclasses the
386 // window again in WM_LBUTTONDOWN before entering full screen. As a result
387 // Safari does not receive the WM_LBUTTONUP message. To workaround this
388 // issue we use a per thread mouse hook. This bug does not occur in Firefox
389 // and opera. Firefox has code similar to Safari. It could well be a bug in
390 // the flash plugin, which only occurs in webkit based browsers.
391 if (quirks_
& PLUGIN_QUIRK_HANDLE_MOUSE_CAPTURE
) {
392 mouse_hook_
= SetWindowsHookEx(WH_MOUSE
, MouseHookProc
, NULL
,
393 GetCurrentThreadId());
396 // On XP, WMP will use its old UI unless a registry key under HKLM has the
397 // name of the current process. We do it in the installer for admin users,
398 // for the rest patch this function.
399 if ((quirks_
& PLUGIN_QUIRK_PATCH_REGENUMKEYEXW
) &&
400 base::win::GetVersion() == base::win::VERSION_XP
&&
401 (base::win::RegKey().Open(HKEY_LOCAL_MACHINE
,
402 L
"SOFTWARE\\Microsoft\\MediaPlayer\\ShimInclusionList\\chrome.exe",
403 KEY_READ
) != ERROR_SUCCESS
) &&
404 !g_iat_patch_reg_enum_key_ex_w
.Pointer()->is_patched()) {
405 g_iat_patch_reg_enum_key_ex_w
.Pointer()->Patch(
406 L
"wmpdxm.dll", "advapi32.dll", "RegEnumKeyExW",
407 WebPluginDelegateImpl::RegEnumKeyExWPatch
);
410 // Flash retrieves the pointers to IMM32 functions with GetProcAddress() calls
411 // and use them to retrieve IME data. We add a patch to this function so we
412 // can dispatch these IMM32 calls to the WebPluginIMEWin class, which emulates
413 // IMM32 functions for Flash.
414 if (!g_iat_patch_get_proc_address
.Pointer()->is_patched() &&
415 (quirks_
& PLUGIN_QUIRK_EMULATE_IME
)) {
416 g_iat_patch_get_proc_address
.Pointer()->Patch(
417 GetPluginPath().value().c_str(), "kernel32.dll", "GetProcAddress",
418 GetProcAddressPatch
);
421 if (windowless_
&& !g_iat_patch_window_from_point
.Pointer()->is_patched() &&
422 (quirks_
& PLUGIN_QUIRK_FAKE_WINDOW_FROM_POINT
)) {
423 g_iat_patch_window_from_point
.Pointer()->Patch(
424 GetPluginPath().value().c_str(), "user32.dll", "WindowFromPoint",
425 WebPluginDelegateImpl::WindowFromPointPatch
);
431 void WebPluginDelegateImpl::PlatformDestroyInstance() {
432 if (!instance_
->plugin_lib())
435 // Unpatch if this is the last plugin instance.
436 if (instance_
->plugin_lib()->instance_count() != 1)
439 if (g_iat_patch_set_cursor
.Pointer()->is_patched())
440 g_iat_patch_set_cursor
.Pointer()->Unpatch();
442 if (g_iat_patch_track_popup_menu
.Pointer()->is_patched())
443 g_iat_patch_track_popup_menu
.Pointer()->Unpatch();
445 if (g_iat_patch_reg_enum_key_ex_w
.Pointer()->is_patched())
446 g_iat_patch_reg_enum_key_ex_w
.Pointer()->Unpatch();
448 if (g_iat_patch_window_from_point
.Pointer()->is_patched())
449 g_iat_patch_window_from_point
.Pointer()->Unpatch();
452 UnhookWindowsHookEx(mouse_hook_
);
457 void WebPluginDelegateImpl::Paint(SkCanvas
* canvas
, const gfx::Rect
& rect
) {
458 if (windowless_
&& skia::SupportsPlatformPaint(canvas
)) {
459 skia::ScopedPlatformPaint
scoped_platform_paint(canvas
);
460 HDC hdc
= scoped_platform_paint
.GetPlatformSurface();
461 WindowlessPaint(hdc
, rect
);
465 bool WebPluginDelegateImpl::WindowedCreatePlugin() {
466 DCHECK(!windowed_handle_
);
468 RegisterNativeWindowClass();
470 // The window will be sized and shown later.
471 windowed_handle_
= CreateWindowEx(
472 WS_EX_LEFT
| WS_EX_LTRREADING
| WS_EX_RIGHTSCROLLBAR
,
473 kNativeWindowClassName
,
475 WS_POPUP
| WS_CLIPCHILDREN
| WS_CLIPSIBLINGS
,
482 GetModuleHandle(NULL
),
484 if (windowed_handle_
== 0)
487 // This is a tricky workaround for Issue 2673 in chromium "Flash: IME not
488 // available". To use IMEs in this window, we have to make Windows attach
489 // IMEs to this window (i.e. load IME DLLs, attach them to this process, and
490 // add their message hooks to this window). Windows attaches IMEs while this
491 // process creates a top-level window. On the other hand, to layout this
492 // window correctly in the given parent window (RenderWidgetHostViewWin or
493 // RenderWidgetHostViewAura), this window should be a child window of the
494 // parent window. To satisfy both of the above conditions, this code once
495 // creates a top-level window and change it to a child window of the parent
496 // window (in the browser process).
497 SetWindowLongPtr(windowed_handle_
, GWL_STYLE
,
498 WS_CHILD
| WS_CLIPCHILDREN
| WS_CLIPSIBLINGS
);
500 BOOL result
= SetProp(windowed_handle_
, kWebPluginDelegateProperty
, this);
501 DCHECK(result
== TRUE
) << "SetProp failed, last error = " << GetLastError();
502 // Get the name and version of the plugin, create atoms and set them in a
503 // window property. Use atoms so that other processes can access the name and
504 // version of the plugin that this window is hosting.
505 if (instance_
!= NULL
) {
506 PluginLib
* plugin_lib
= instance()->plugin_lib();
507 if (plugin_lib
!= NULL
) {
508 std::wstring plugin_name
= plugin_lib
->plugin_info().name
;
509 if (!plugin_name
.empty()) {
510 ATOM plugin_name_atom
= GlobalAddAtomW(plugin_name
.c_str());
511 DCHECK_NE(0, plugin_name_atom
);
512 result
= SetProp(windowed_handle_
,
513 kPluginNameAtomProperty
,
514 reinterpret_cast<HANDLE
>(plugin_name_atom
));
515 DCHECK(result
== TRUE
) << "SetProp failed, last error = " <<
518 base::string16 plugin_version
= plugin_lib
->plugin_info().version
;
519 if (!plugin_version
.empty()) {
520 ATOM plugin_version_atom
= GlobalAddAtomW(plugin_version
.c_str());
521 DCHECK_NE(0, plugin_version_atom
);
522 result
= SetProp(windowed_handle_
,
523 kPluginVersionAtomProperty
,
524 reinterpret_cast<HANDLE
>(plugin_version_atom
));
525 DCHECK(result
== TRUE
) << "SetProp failed, last error = " <<
531 // Calling SetWindowLongPtrA here makes the window proc ASCII, which is
532 // required by at least the Shockwave Director plug-in.
533 SetWindowLongPtrA(windowed_handle_
,
535 reinterpret_cast<LONG_PTR
>(DefWindowProcA
));
540 void WebPluginDelegateImpl::WindowedDestroyWindow() {
541 if (windowed_handle_
!= NULL
) {
542 // Unsubclass the window.
543 WNDPROC current_wnd_proc
= reinterpret_cast<WNDPROC
>(
544 GetWindowLongPtr(windowed_handle_
, GWLP_WNDPROC
));
545 if (current_wnd_proc
== NativeWndProc
) {
546 SetWindowLongPtr(windowed_handle_
,
548 reinterpret_cast<LONG_PTR
>(plugin_wnd_proc_
));
551 plugin_
->WillDestroyWindow(windowed_handle_
);
553 DestroyWindow(windowed_handle_
);
554 windowed_handle_
= 0;
558 // Erase all messages in the queue destined for a particular window.
559 // When windows are closing, callers should use this function to clear
562 void WebPluginDelegateImpl::ClearThrottleQueueForWindow(HWND window
) {
563 ThrottleQueue
* throttle_queue
= g_throttle_queue
.Pointer();
565 ThrottleQueue::iterator it
;
566 for (it
= throttle_queue
->begin(); it
!= throttle_queue
->end(); ) {
567 if (it
->hwnd
== window
) {
568 it
= throttle_queue
->erase(it
);
575 // Delayed callback for processing throttled messages.
576 // Throttled messages are aggregated globally across all plugins.
578 void WebPluginDelegateImpl::OnThrottleMessage() {
579 // The current algorithm walks the list and processes the first
580 // message it finds for each plugin. It is important to service
581 // all active plugins with each pass through the throttle, otherwise
582 // we see video jankiness. Copy the set to notify before notifying
583 // since we may re-enter OnThrottleMessage from CallWindowProc!
584 ThrottleQueue
* throttle_queue
= g_throttle_queue
.Pointer();
585 ThrottleQueue notify_queue
;
586 std::set
<HWND
> processed
;
588 ThrottleQueue::iterator it
= throttle_queue
->begin();
589 while (it
!= throttle_queue
->end()) {
590 const MSG
& msg
= *it
;
591 if (processed
.find(msg
.hwnd
) == processed
.end()) {
592 processed
.insert(msg
.hwnd
);
593 notify_queue
.push_back(msg
);
594 it
= throttle_queue
->erase(it
);
600 // Due to re-entrancy, we must save our queue state now. Otherwise, we may
601 // self-post below, and *also* start up another delayed task when the first
602 // entry is pushed onto the queue in ThrottleMessage().
603 bool throttle_queue_was_empty
= throttle_queue
->empty();
605 for (it
= notify_queue
.begin(); it
!= notify_queue
.end(); ++it
) {
606 const MSG
& msg
= *it
;
607 WNDPROC proc
= reinterpret_cast<WNDPROC
>(msg
.time
);
608 // It is possible that the window was closed after we queued
609 // this message. This is a rare event; just verify the window
610 // is alive. (see also bug 1259488)
611 if (IsWindow(msg
.hwnd
))
612 CallWindowProc(proc
, msg
.hwnd
, msg
.message
, msg
.wParam
, msg
.lParam
);
615 if (!throttle_queue_was_empty
) {
616 base::MessageLoop::current()->PostDelayedTask(
618 base::Bind(&WebPluginDelegateImpl::OnThrottleMessage
),
619 base::TimeDelta::FromMilliseconds(kFlashWMUSERMessageThrottleDelayMs
));
623 // Schedule a windows message for delivery later.
625 void WebPluginDelegateImpl::ThrottleMessage(WNDPROC proc
, HWND hwnd
,
626 UINT message
, WPARAM wParam
,
629 msg
.time
= reinterpret_cast<DWORD
>(proc
);
631 msg
.message
= message
;
635 ThrottleQueue
* throttle_queue
= g_throttle_queue
.Pointer();
637 throttle_queue
->push_back(msg
);
639 if (throttle_queue
->size() == 1) {
640 base::MessageLoop::current()->PostDelayedTask(
642 base::Bind(&WebPluginDelegateImpl::OnThrottleMessage
),
643 base::TimeDelta::FromMilliseconds(kFlashWMUSERMessageThrottleDelayMs
));
647 // We go out of our way to find the hidden windows created by Flash for
648 // windowless plugins. We throttle the rate at which they deliver messages
649 // so that they will not consume outrageous amounts of CPU.
651 LRESULT CALLBACK
WebPluginDelegateImpl::FlashWindowlessWndProc(
652 HWND hwnd
, UINT message
, WPARAM wparam
, LPARAM lparam
) {
653 std::map
<HWND
, WNDPROC
>::iterator index
=
654 g_window_handle_proc_map
.Get().find(hwnd
);
656 WNDPROC old_proc
= (*index
).second
;
661 WebPluginDelegateImpl::ClearThrottleQueueForWindow(hwnd
);
662 g_window_handle_proc_map
.Get().erase(index
);
665 // Flash may flood the message queue with WM_USER+1 message causing 100% CPU
666 // usage. See https://bugzilla.mozilla.org/show_bug.cgi?id=132759. We
667 // prevent this by throttling the messages.
669 WebPluginDelegateImpl::ThrottleMessage(old_proc
, hwnd
, message
, wparam
,
678 return CallWindowProc(old_proc
, hwnd
, message
, wparam
, lparam
);
681 LRESULT CALLBACK
WebPluginDelegateImpl::DummyWindowProc(
682 HWND hwnd
, UINT message
, WPARAM w_param
, LPARAM l_param
) {
683 WebPluginDelegateImpl
* delegate
= reinterpret_cast<WebPluginDelegateImpl
*>(
684 GetProp(hwnd
, kWebPluginDelegateProperty
));
686 if (message
== WM_WINDOWPOSCHANGING
) {
687 // We need to know when the dummy window is parented because windowless
688 // plugins need the parent window for things like menus. There's no message
689 // for a parent being changed, but a WM_WINDOWPOSCHANGING is sent so we
690 // check every time we get it.
691 // For non-aura builds, this never changes since RenderWidgetHostViewWin's
692 // window is constant. For aura builds, this changes every time the tab gets
693 // dragged to a new window.
694 HWND parent
= GetParent(hwnd
);
695 if (parent
!= delegate
->dummy_window_parent_
) {
696 delegate
->dummy_window_parent_
= parent
;
698 // Set the containing window handle as the instance window handle. This is
699 // what Safari does. Not having a valid window handle causes subtle bugs
700 // with plugins which retrieve the window handle and use it for things
701 // like context menus. The window handle can be retrieved via
702 // NPN_GetValue of NPNVnetscapeWindow.
703 delegate
->instance_
->set_window_handle(parent
);
705 // The plugin caches the result of NPNVnetscapeWindow when we originally
706 // called NPP_SetWindow, so force it to get the new value.
707 delegate
->WindowlessSetWindow();
709 } else if (message
== WM_NCDESTROY
) {
710 RemoveProp(hwnd
, kWebPluginDelegateProperty
);
712 return CallWindowProc(
713 delegate
->old_dummy_window_proc_
, hwnd
, message
, w_param
, l_param
);
716 // Callback for enumerating the Flash windows.
717 BOOL CALLBACK
EnumFlashWindows(HWND window
, LPARAM arg
) {
718 WNDPROC wnd_proc
= reinterpret_cast<WNDPROC
>(arg
);
719 TCHAR class_name
[1024];
720 if (!RealGetWindowClass(window
, class_name
,
721 sizeof(class_name
)/sizeof(TCHAR
))) {
722 LOG(ERROR
) << "RealGetWindowClass failure: " << GetLastError();
726 if (wcscmp(class_name
, L
"SWFlash_PlaceholderX"))
729 WNDPROC current_wnd_proc
= reinterpret_cast<WNDPROC
>(
730 GetWindowLongPtr(window
, GWLP_WNDPROC
));
731 if (current_wnd_proc
!= wnd_proc
) {
732 WNDPROC old_flash_proc
= reinterpret_cast<WNDPROC
>(SetWindowLongPtr(
733 window
, GWLP_WNDPROC
,
734 reinterpret_cast<LONG_PTR
>(wnd_proc
)));
735 DCHECK(old_flash_proc
);
736 g_window_handle_proc_map
.Get()[window
] = old_flash_proc
;
742 bool WebPluginDelegateImpl::CreateDummyWindowForActivation() {
743 DCHECK(!dummy_window_for_activation_
);
745 dummy_window_for_activation_
= CreateWindowEx(
748 kDummyActivationWindowName
,
754 // We don't know the parent of the dummy window yet, so just set it to the
755 // desktop and it'll get parented by the browser.
758 GetModuleHandle(NULL
),
761 if (dummy_window_for_activation_
== 0)
764 BOOL result
= SetProp(dummy_window_for_activation_
,
765 kWebPluginDelegateProperty
, this);
766 DCHECK(result
== TRUE
) << "SetProp failed, last error = " << GetLastError();
767 old_dummy_window_proc_
= reinterpret_cast<WNDPROC
>(SetWindowLongPtr(
768 dummy_window_for_activation_
, GWLP_WNDPROC
,
769 reinterpret_cast<LONG_PTR
>(DummyWindowProc
)));
771 // Flash creates background windows which use excessive CPU in our
772 // environment; we wrap these windows and throttle them so that they don't
774 if (!EnumThreadWindows(::GetCurrentThreadId(), EnumFlashWindows
,
775 reinterpret_cast<LPARAM
>(
776 &WebPluginDelegateImpl::FlashWindowlessWndProc
))) {
777 // Log that this happened. Flash will still work; it just means the
778 // throttle isn't installed (and Flash will use more CPU).
780 LOG(ERROR
) << "Failed to wrap all windowless Flash windows";
785 bool WebPluginDelegateImpl::WindowedReposition(
786 const gfx::Rect
& window_rect_in_dip
,
787 const gfx::Rect
& clip_rect_in_dip
) {
788 if (!windowed_handle_
) {
793 gfx::Rect window_rect
= gfx::win::DIPToScreenRect(window_rect_in_dip
);
794 gfx::Rect clip_rect
= gfx::win::DIPToScreenRect(clip_rect_in_dip
);
795 if (window_rect_
== window_rect
&& clip_rect_
== clip_rect
)
798 // We only set the plugin's size here. Its position is moved elsewhere, which
799 // allows the window moves/scrolling/clipping to be synchronized with the page
800 // and other windows.
801 // If the plugin window has no parent, then don't focus it because it isn't
802 // being displayed anywhere. See:
803 // http://code.google.com/p/chromium/issues/detail?id=32658
804 if (window_rect
.size() != window_rect_
.size()) {
805 UINT flags
= SWP_NOMOVE
| SWP_NOZORDER
;
806 if (!GetParent(windowed_handle_
))
807 flags
|= SWP_NOACTIVATE
;
808 ::SetWindowPos(windowed_handle_
,
813 window_rect
.height(),
817 window_rect_
= window_rect
;
818 clip_rect_
= clip_rect
;
820 // Ensure that the entire window gets repainted.
821 ::InvalidateRect(windowed_handle_
, NULL
, FALSE
);
826 void WebPluginDelegateImpl::WindowedSetWindow() {
830 if (!windowed_handle_
) {
835 instance()->set_window_handle(windowed_handle_
);
837 DCHECK(!instance()->windowless());
839 window_
.clipRect
.top
= std::max(0, clip_rect_
.y());
840 window_
.clipRect
.left
= std::max(0, clip_rect_
.x());
841 window_
.clipRect
.bottom
= std::max(0, clip_rect_
.y() + clip_rect_
.height());
842 window_
.clipRect
.right
= std::max(0, clip_rect_
.x() + clip_rect_
.width());
843 window_
.height
= window_rect_
.height();
844 window_
.width
= window_rect_
.width();
848 window_
.window
= windowed_handle_
;
849 window_
.type
= NPWindowTypeWindow
;
851 // Reset this flag before entering the instance in case of side-effects.
852 windowed_did_set_window_
= true;
854 NPError err
= instance()->NPP_SetWindow(&window_
);
855 if (quirks_
& PLUGIN_QUIRK_SETWINDOW_TWICE
)
856 instance()->NPP_SetWindow(&window_
);
858 WNDPROC current_wnd_proc
= reinterpret_cast<WNDPROC
>(
859 GetWindowLongPtr(windowed_handle_
, GWLP_WNDPROC
));
860 if (current_wnd_proc
!= NativeWndProc
) {
861 plugin_wnd_proc_
= reinterpret_cast<WNDPROC
>(
862 SetWindowLongPtr(windowed_handle_
,
864 reinterpret_cast<LONG_PTR
>(NativeWndProc
)));
868 ATOM
WebPluginDelegateImpl::RegisterNativeWindowClass() {
869 static bool have_registered_window_class
= false;
870 if (have_registered_window_class
== true)
873 have_registered_window_class
= true;
876 wcex
.cbSize
= sizeof(WNDCLASSEX
);
877 wcex
.style
= CS_DBLCLKS
;
878 wcex
.lpfnWndProc
= WrapperWindowProc
;
881 wcex
.hInstance
= GetModuleHandle(NULL
);
884 // Some plugins like windows media player 11 create child windows parented
885 // by our plugin window, where the media content is rendered. These plugins
886 // dont implement WM_ERASEBKGND, which causes painting issues, when the
887 // window where the media is rendered is moved around. DefWindowProc does
888 // implement WM_ERASEBKGND correctly if we have a valid background brush.
889 wcex
.hbrBackground
= reinterpret_cast<HBRUSH
>(COLOR_WINDOW
+1);
890 wcex
.lpszMenuName
= 0;
891 wcex
.lpszClassName
= kNativeWindowClassName
;
894 return RegisterClassEx(&wcex
);
897 LRESULT CALLBACK
WebPluginDelegateImpl::WrapperWindowProc(
898 HWND hWnd
, UINT message
, WPARAM wParam
, LPARAM lParam
) {
899 // This is another workaround for Issue 2673 in chromium "Flash: IME not
900 // available". Somehow, the CallWindowProc() function does not dispatch
901 // window messages when its first parameter is a handle representing the
902 // DefWindowProc() function. To avoid this problem, this code creates a
903 // wrapper function which just encapsulates the DefWindowProc() function
904 // and set it as the window procedure of a windowed plug-in.
905 return DefWindowProc(hWnd
, message
, wParam
, lParam
);
908 // Returns true if the message passed in corresponds to a user gesture.
909 static bool IsUserGestureMessage(unsigned int message
) {
928 LRESULT CALLBACK
WebPluginDelegateImpl::NativeWndProc(
929 HWND hwnd
, UINT message
, WPARAM wparam
, LPARAM lparam
) {
930 WebPluginDelegateImpl
* delegate
= reinterpret_cast<WebPluginDelegateImpl
*>(
931 GetProp(hwnd
, kWebPluginDelegateProperty
));
937 if (message
== delegate
->last_message_
&&
938 delegate
->GetQuirks() & PLUGIN_QUIRK_DONT_CALL_WND_PROC_RECURSIVELY
&&
939 delegate
->is_calling_wndproc
) {
940 // Real may go into a state where it recursively dispatches the same event
941 // when subclassed. See https://bugzilla.mozilla.org/show_bug.cgi?id=192914
942 // We only do the recursive check for Real because it's possible and valid
943 // for a plugin to synchronously dispatch a message to itself such that it
944 // looks like it's in recursion.
948 // Flash may flood the message queue with WM_USER+1 message causing 100% CPU
949 // usage. See https://bugzilla.mozilla.org/show_bug.cgi?id=132759. We
950 // prevent this by throttling the messages.
951 if (message
== WM_USER
+ 1 &&
952 delegate
->GetQuirks() & PLUGIN_QUIRK_THROTTLE_WM_USER_PLUS_ONE
) {
953 WebPluginDelegateImpl::ThrottleMessage(delegate
->plugin_wnd_proc_
, hwnd
,
954 message
, wparam
, lparam
);
959 uint32 old_message
= delegate
->last_message_
;
960 delegate
->last_message_
= message
;
962 static UINT custom_msg
= RegisterWindowMessage(kPaintMessageName
);
963 if (message
== custom_msg
) {
964 // Get the invalid rect which is in screen coordinates and convert to
965 // window coordinates.
966 gfx::Rect invalid_rect
;
967 invalid_rect
.set_x(static_cast<short>(LOWORD(wparam
)));
968 invalid_rect
.set_y(static_cast<short>(HIWORD(wparam
)));
969 invalid_rect
.set_width(static_cast<short>(LOWORD(lparam
)));
970 invalid_rect
.set_height(static_cast<short>(HIWORD(lparam
)));
973 GetWindowRect(hwnd
, &window_rect
);
974 invalid_rect
.Offset(-window_rect
.left
, -window_rect
.top
);
976 // The plugin window might have non-client area. If we don't pass in
977 // RDW_FRAME then the children don't receive WM_NCPAINT messages while
978 // scrolling, which causes painting problems (http://b/issue?id=923945).
979 uint32 flags
= RDW_INVALIDATE
| RDW_ALLCHILDREN
| RDW_FRAME
;
981 // If a plugin (like Google Earth or Java) has child windows that are hosted
982 // in a different process, then RedrawWindow with UPDATENOW will
983 // synchronously wait for this call to complete. Some messages are pumped
984 // but not others, which could lead to a deadlock. So avoid reentrancy by
985 // only synchronously calling RedrawWindow once at a time.
986 if (old_message
!= custom_msg
)
987 flags
|= RDW_UPDATENOW
;
988 RECT rect
= invalid_rect
.ToRECT();
989 RedrawWindow(hwnd
, &rect
, NULL
, flags
);
992 delegate
->is_calling_wndproc
= true;
994 if (!delegate
->user_gesture_message_posted_
&&
995 IsUserGestureMessage(message
)) {
996 delegate
->user_gesture_message_posted_
= true;
998 delegate
->instance()->PushPopupsEnabledState(true);
1000 base::MessageLoop::current()->PostDelayedTask(
1002 base::Bind(&WebPluginDelegateImpl::OnUserGestureEnd
,
1003 delegate
->user_gesture_msg_factory_
.GetWeakPtr()),
1004 base::TimeDelta::FromMilliseconds(kWindowedPluginPopupTimerMs
));
1007 HandleCaptureForMessage(hwnd
, message
);
1009 // Maintain a local/global stack for the g_current_plugin_instance variable
1010 // as this may be a nested invocation.
1011 WebPluginDelegateImpl
* last_plugin_instance
= g_current_plugin_instance
;
1013 g_current_plugin_instance
= delegate
;
1015 result
= CallWindowProc(
1016 delegate
->plugin_wnd_proc_
, hwnd
, message
, wparam
, lparam
);
1018 // The plugin instance may have been destroyed in the CallWindowProc call
1019 // above. This will also destroy the plugin window. Before attempting to
1020 // access the WebPluginDelegateImpl instance we validate if the window is
1022 if (::IsWindow(hwnd
))
1023 delegate
->is_calling_wndproc
= false;
1025 g_current_plugin_instance
= last_plugin_instance
;
1027 if (message
== WM_NCDESTROY
) {
1028 RemoveProp(hwnd
, kWebPluginDelegateProperty
);
1029 ATOM plugin_name_atom
= reinterpret_cast<ATOM
>(
1030 RemoveProp(hwnd
, kPluginNameAtomProperty
));
1031 if (plugin_name_atom
!= 0)
1032 GlobalDeleteAtom(plugin_name_atom
);
1033 ATOM plugin_version_atom
= reinterpret_cast<ATOM
>(
1034 RemoveProp(hwnd
, kPluginVersionAtomProperty
));
1035 if (plugin_version_atom
!= 0)
1036 GlobalDeleteAtom(plugin_version_atom
);
1037 ClearThrottleQueueForWindow(hwnd
);
1040 if (::IsWindow(hwnd
))
1041 delegate
->last_message_
= old_message
;
1045 void WebPluginDelegateImpl::WindowlessUpdateGeometry(
1046 const gfx::Rect
& window_rect
,
1047 const gfx::Rect
& clip_rect
) {
1048 bool window_rect_changed
= (window_rect_
!= window_rect
);
1049 // Only resend to the instance if the geometry has changed.
1050 if (!window_rect_changed
&& clip_rect
== clip_rect_
)
1053 clip_rect_
= clip_rect
;
1054 window_rect_
= window_rect
;
1056 WindowlessSetWindow();
1058 if (window_rect_changed
) {
1059 WINDOWPOS win_pos
= {0};
1060 win_pos
.x
= window_rect_
.x();
1061 win_pos
.y
= window_rect_
.y();
1062 win_pos
.cx
= window_rect_
.width();
1063 win_pos
.cy
= window_rect_
.height();
1065 NPEvent pos_changed_event
;
1066 pos_changed_event
.event
= WM_WINDOWPOSCHANGED
;
1067 pos_changed_event
.wParam
= 0;
1068 pos_changed_event
.lParam
= PtrToUlong(&win_pos
);
1070 instance()->NPP_HandleEvent(&pos_changed_event
);
1074 void WebPluginDelegateImpl::WindowlessPaint(HDC hdc
,
1075 const gfx::Rect
& damage_rect
) {
1078 RECT damage_rect_win
;
1079 damage_rect_win
.left
= damage_rect
.x(); // + window_rect_.x();
1080 damage_rect_win
.top
= damage_rect
.y(); // + window_rect_.y();
1081 damage_rect_win
.right
= damage_rect_win
.left
+ damage_rect
.width();
1082 damage_rect_win
.bottom
= damage_rect_win
.top
+ damage_rect
.height();
1084 // Save away the old HDC as this could be a nested invocation.
1085 void* old_dc
= window_
.window
;
1086 window_
.window
= hdc
;
1088 NPEvent paint_event
;
1089 paint_event
.event
= WM_PAINT
;
1090 // NOTE: NPAPI is not 64bit safe. It puts pointers into 32bit values.
1091 paint_event
.wParam
= PtrToUlong(hdc
);
1092 paint_event
.lParam
= PtrToUlong(&damage_rect_win
);
1093 base::StatsRate
plugin_paint("Plugin.Paint");
1094 base::StatsScope
<base::StatsRate
> scope(plugin_paint
);
1095 instance()->NPP_HandleEvent(&paint_event
);
1096 window_
.window
= old_dc
;
1099 void WebPluginDelegateImpl::WindowlessSetWindow() {
1103 if (window_rect_
.IsEmpty()) // wait for geometry to be set.
1106 DCHECK(instance()->windowless());
1108 window_
.clipRect
.top
= clip_rect_
.y();
1109 window_
.clipRect
.left
= clip_rect_
.x();
1110 window_
.clipRect
.bottom
= clip_rect_
.y() + clip_rect_
.height();
1111 window_
.clipRect
.right
= clip_rect_
.x() + clip_rect_
.width();
1112 window_
.height
= window_rect_
.height();
1113 window_
.width
= window_rect_
.width();
1114 window_
.x
= window_rect_
.x();
1115 window_
.y
= window_rect_
.y();
1116 window_
.type
= NPWindowTypeDrawable
;
1117 DrawableContextEnforcer
enforcer(&window_
);
1119 NPError err
= instance()->NPP_SetWindow(&window_
);
1120 DCHECK(err
== NPERR_NO_ERROR
);
1123 bool WebPluginDelegateImpl::PlatformSetPluginHasFocus(bool focused
) {
1124 DCHECK(instance()->windowless());
1126 NPEvent focus_event
;
1127 focus_event
.event
= focused
? WM_SETFOCUS
: WM_KILLFOCUS
;
1128 focus_event
.wParam
= 0;
1129 focus_event
.lParam
= 0;
1131 instance()->NPP_HandleEvent(&focus_event
);
1135 static bool NPEventFromWebMouseEvent(const WebMouseEvent
& event
,
1136 NPEvent
* np_event
) {
1137 np_event
->lParam
= static_cast<uint32
>(MAKELPARAM(event
.windowX
,
1139 np_event
->wParam
= 0;
1141 if (event
.modifiers
& WebInputEvent::ControlKey
)
1142 np_event
->wParam
|= MK_CONTROL
;
1143 if (event
.modifiers
& WebInputEvent::ShiftKey
)
1144 np_event
->wParam
|= MK_SHIFT
;
1145 if (event
.modifiers
& WebInputEvent::LeftButtonDown
)
1146 np_event
->wParam
|= MK_LBUTTON
;
1147 if (event
.modifiers
& WebInputEvent::MiddleButtonDown
)
1148 np_event
->wParam
|= MK_MBUTTON
;
1149 if (event
.modifiers
& WebInputEvent::RightButtonDown
)
1150 np_event
->wParam
|= MK_RBUTTON
;
1152 switch (event
.type
) {
1153 case WebInputEvent::MouseMove
:
1154 case WebInputEvent::MouseLeave
:
1155 case WebInputEvent::MouseEnter
:
1156 np_event
->event
= WM_MOUSEMOVE
;
1158 case WebInputEvent::MouseDown
:
1159 switch (event
.button
) {
1160 case WebMouseEvent::ButtonLeft
:
1161 np_event
->event
= WM_LBUTTONDOWN
;
1163 case WebMouseEvent::ButtonMiddle
:
1164 np_event
->event
= WM_MBUTTONDOWN
;
1166 case WebMouseEvent::ButtonRight
:
1167 np_event
->event
= WM_RBUTTONDOWN
;
1171 case WebInputEvent::MouseUp
:
1172 switch (event
.button
) {
1173 case WebMouseEvent::ButtonLeft
:
1174 np_event
->event
= WM_LBUTTONUP
;
1176 case WebMouseEvent::ButtonMiddle
:
1177 np_event
->event
= WM_MBUTTONUP
;
1179 case WebMouseEvent::ButtonRight
:
1180 np_event
->event
= WM_RBUTTONUP
;
1190 static bool NPEventFromWebKeyboardEvent(const WebKeyboardEvent
& event
,
1191 NPEvent
* np_event
) {
1192 np_event
->wParam
= event
.windowsKeyCode
;
1194 switch (event
.type
) {
1195 case WebInputEvent::KeyDown
:
1196 np_event
->event
= WM_KEYDOWN
;
1197 np_event
->lParam
= 0;
1199 case WebInputEvent::Char
:
1200 np_event
->event
= WM_CHAR
;
1201 np_event
->lParam
= 0;
1203 case WebInputEvent::KeyUp
:
1204 np_event
->event
= WM_KEYUP
;
1205 np_event
->lParam
= 0x8000;
1213 static bool NPEventFromWebInputEvent(const WebInputEvent
& event
,
1214 NPEvent
* np_event
) {
1215 switch (event
.type
) {
1216 case WebInputEvent::MouseMove
:
1217 case WebInputEvent::MouseLeave
:
1218 case WebInputEvent::MouseEnter
:
1219 case WebInputEvent::MouseDown
:
1220 case WebInputEvent::MouseUp
:
1221 if (event
.size
< sizeof(WebMouseEvent
)) {
1225 return NPEventFromWebMouseEvent(
1226 *static_cast<const WebMouseEvent
*>(&event
), np_event
);
1227 case WebInputEvent::KeyDown
:
1228 case WebInputEvent::Char
:
1229 case WebInputEvent::KeyUp
:
1230 if (event
.size
< sizeof(WebKeyboardEvent
)) {
1234 return NPEventFromWebKeyboardEvent(
1235 *static_cast<const WebKeyboardEvent
*>(&event
), np_event
);
1241 bool WebPluginDelegateImpl::PlatformHandleInputEvent(
1242 const WebInputEvent
& event
, WebCursor::CursorInfo
* cursor_info
) {
1243 DCHECK(cursor_info
!= NULL
);
1246 if (!NPEventFromWebInputEvent(event
, &np_event
)) {
1250 // Allow this plug-in to access this IME emulator through IMM32 API while the
1251 // plug-in is processing this event.
1252 if (GetQuirks() & PLUGIN_QUIRK_EMULATE_IME
) {
1254 plugin_ime_
.reset(new WebPluginIMEWin
);
1256 WebPluginIMEWin::ScopedLock
lock(
1257 event
.isKeyboardEventType(event
.type
) ? plugin_ime_
.get() : NULL
);
1259 HWND last_focus_window
= NULL
;
1261 if (ShouldTrackEventForModalLoops(&np_event
)) {
1262 // A windowless plugin can enter a modal loop in a NPP_HandleEvent call.
1263 // For e.g. Flash puts up a context menu when we right click on the
1264 // windowless plugin area. We detect this by setting up a message filter
1265 // hook pror to calling NPP_HandleEvent on the plugin and unhook on
1266 // return from NPP_HandleEvent. If the plugin does enter a modal loop
1267 // in that context we unhook on receiving the first notification in
1268 // the message filter hook.
1269 handle_event_message_filter_hook_
=
1270 SetWindowsHookEx(WH_MSGFILTER
, HandleEventMessageFilterHook
, NULL
,
1271 GetCurrentThreadId());
1272 // To ensure that the plugin receives keyboard events we set focus to the
1274 // TODO(iyengar) We need a framework in the renderer to identify which
1275 // windowless plugin is under the mouse and to handle this. This would
1276 // also require some changes in RenderWidgetHost to detect this in the
1277 // WM_MOUSEACTIVATE handler and inform the renderer accordingly.
1278 bool valid
= GetParent(dummy_window_for_activation_
) != GetDesktopWindow();
1280 last_focus_window
= ::SetFocus(dummy_window_for_activation_
);
1282 NOTREACHED() << "Dummy window not parented";
1286 bool old_task_reentrancy_state
=
1287 base::MessageLoop::current()->NestableTasksAllowed();
1289 // Maintain a local/global stack for the g_current_plugin_instance variable
1290 // as this may be a nested invocation.
1291 WebPluginDelegateImpl
* last_plugin_instance
= g_current_plugin_instance
;
1293 g_current_plugin_instance
= this;
1295 handle_event_depth_
++;
1297 bool popups_enabled
= false;
1299 if (IsUserGestureMessage(np_event
.event
)) {
1300 instance()->PushPopupsEnabledState(true);
1301 popups_enabled
= true;
1304 bool ret
= instance()->NPP_HandleEvent(&np_event
) != 0;
1306 if (popups_enabled
) {
1307 instance()->PopPopupsEnabledState();
1310 // Flash and SilverLight always return false, even when they swallow the
1311 // event. Flash does this because it passes the event to its window proc,
1312 // which is supposed to return 0 if an event was handled. There are few
1313 // exceptions, such as IME, where it sometimes returns true.
1316 if (np_event
.event
== WM_MOUSEMOVE
) {
1317 current_windowless_cursor_
.InitFromExternalCursor(GetCursor());
1318 // Snag a reference to the current cursor ASAP in case the plugin modified
1319 // it. There is a nasty race condition here with the multiprocess browser
1320 // as someone might be setting the cursor in the main process as well.
1321 current_windowless_cursor_
.GetCursorInfo(cursor_info
);
1324 handle_event_depth_
--;
1326 g_current_plugin_instance
= last_plugin_instance
;
1328 // We could have multiple NPP_HandleEvent calls nested together in case
1329 // the plugin enters a modal loop. Reset the pump messages event when
1330 // the outermost NPP_HandleEvent call unwinds.
1331 if (handle_event_depth_
== 0) {
1332 ResetEvent(handle_event_pump_messages_event_
);
1335 // If we didn't enter a modal loop, need to unhook the filter.
1336 if (handle_event_message_filter_hook_
) {
1337 UnhookWindowsHookEx(handle_event_message_filter_hook_
);
1338 handle_event_message_filter_hook_
= NULL
;
1341 if (::IsWindow(last_focus_window
)) {
1342 // Restore the nestable tasks allowed state in the message loop and reset
1343 // the os modal loop state as the plugin returned from the TrackPopupMenu
1345 base::MessageLoop::current()->SetNestableTasksAllowed(
1346 old_task_reentrancy_state
);
1347 base::MessageLoop::current()->set_os_modal_loop(false);
1348 // The Flash plugin at times sets focus to its hidden top level window
1349 // with class name SWFlash_PlaceholderX. This causes the chrome browser
1350 // window to receive a WM_ACTIVATEAPP message as a top level window from
1351 // another thread is now active. We end up in a state where the chrome
1352 // browser window is not active even though the user clicked on it.
1353 // Our workaround for this is to send over a raw
1354 // WM_LBUTTONDOWN/WM_LBUTTONUP combination to the last focus window, which
1356 if (dummy_window_for_activation_
!= ::GetFocus()) {
1357 INPUT input_info
= {0};
1358 input_info
.type
= INPUT_MOUSE
;
1359 input_info
.mi
.dwFlags
= MOUSEEVENTF_LEFTDOWN
;
1360 ::SendInput(1, &input_info
, sizeof(INPUT
));
1362 input_info
.type
= INPUT_MOUSE
;
1363 input_info
.mi
.dwFlags
= MOUSEEVENTF_LEFTUP
;
1364 ::SendInput(1, &input_info
, sizeof(INPUT
));
1366 ::SetFocus(last_focus_window
);
1373 void WebPluginDelegateImpl::OnModalLoopEntered() {
1374 DCHECK(handle_event_pump_messages_event_
!= NULL
);
1375 SetEvent(handle_event_pump_messages_event_
);
1377 base::MessageLoop::current()->SetNestableTasksAllowed(true);
1378 base::MessageLoop::current()->set_os_modal_loop(true);
1380 UnhookWindowsHookEx(handle_event_message_filter_hook_
);
1381 handle_event_message_filter_hook_
= NULL
;
1384 bool WebPluginDelegateImpl::ShouldTrackEventForModalLoops(NPEvent
* event
) {
1385 if (event
->event
== WM_RBUTTONDOWN
)
1390 void WebPluginDelegateImpl::OnUserGestureEnd() {
1391 user_gesture_message_posted_
= false;
1392 instance()->PopPopupsEnabledState();
1395 BOOL WINAPI
WebPluginDelegateImpl::TrackPopupMenuPatch(
1396 HMENU menu
, unsigned int flags
, int x
, int y
, int reserved
,
1397 HWND window
, const RECT
* rect
) {
1399 if (g_current_plugin_instance
) {
1400 unsigned long window_process_id
= 0;
1401 unsigned long window_thread_id
=
1402 GetWindowThreadProcessId(window
, &window_process_id
);
1403 // TrackPopupMenu fails if the window passed in belongs to a different
1405 if (::GetCurrentThreadId() != window_thread_id
) {
1407 GetParent(g_current_plugin_instance
->dummy_window_for_activation_
) !=
1410 window
= g_current_plugin_instance
->dummy_window_for_activation_
;
1412 NOTREACHED() << "Dummy window not parented";
1417 BOOL result
= TrackPopupMenu(menu
, flags
, x
, y
, reserved
, window
, rect
);
1421 HCURSOR WINAPI
WebPluginDelegateImpl::SetCursorPatch(HCURSOR cursor
) {
1422 // The windowless flash plugin periodically calls SetCursor in a wndproc
1423 // instantiated on the plugin thread. This causes annoying cursor flicker
1424 // when the mouse is moved on a foreground tab, with a windowless plugin
1425 // instance in a background tab. We just ignore the call here.
1426 if (!g_current_plugin_instance
) {
1427 HCURSOR current_cursor
= GetCursor();
1428 if (current_cursor
!= cursor
) {
1429 ::SetCursor(cursor
);
1431 return current_cursor
;
1433 return ::SetCursor(cursor
);
1436 LONG WINAPI
WebPluginDelegateImpl::RegEnumKeyExWPatch(
1437 HKEY key
, DWORD index
, LPWSTR name
, LPDWORD name_size
, LPDWORD reserved
,
1438 LPWSTR class_name
, LPDWORD class_size
, PFILETIME last_write_time
) {
1439 DWORD orig_size
= *name_size
;
1440 LONG rv
= RegEnumKeyExW(key
, index
, name
, name_size
, reserved
, class_name
,
1441 class_size
, last_write_time
);
1442 if (rv
== ERROR_SUCCESS
&&
1443 GetKeyPath(key
).find(L
"Microsoft\\MediaPlayer\\ShimInclusionList") !=
1444 std::wstring::npos
) {
1445 static const wchar_t kChromeExeName
[] = L
"chrome.exe";
1446 wcsncpy_s(name
, orig_size
, kChromeExeName
, arraysize(kChromeExeName
));
1448 std::min(orig_size
, static_cast<DWORD
>(arraysize(kChromeExeName
)));
1454 void WebPluginDelegateImpl::ImeCompositionUpdated(
1455 const base::string16
& text
,
1456 const std::vector
<int>& clauses
,
1457 const std::vector
<int>& target
,
1458 int cursor_position
) {
1460 plugin_ime_
.reset(new WebPluginIMEWin
);
1462 plugin_ime_
->CompositionUpdated(text
, clauses
, target
, cursor_position
);
1463 plugin_ime_
->SendEvents(instance());
1466 void WebPluginDelegateImpl::ImeCompositionCompleted(
1467 const base::string16
& text
) {
1469 plugin_ime_
.reset(new WebPluginIMEWin
);
1470 plugin_ime_
->CompositionCompleted(text
);
1471 plugin_ime_
->SendEvents(instance());
1474 bool WebPluginDelegateImpl::GetIMEStatus(int* input_type
,
1475 gfx::Rect
* caret_rect
) {
1478 return plugin_ime_
->GetStatus(input_type
, caret_rect
);
1482 FARPROC WINAPI
WebPluginDelegateImpl::GetProcAddressPatch(HMODULE module
,
1484 FARPROC imm_function
= WebPluginIMEWin::GetProcAddress(name
);
1486 return imm_function
;
1487 return ::GetProcAddress(module
, name
);
1490 HWND WINAPI
WebPluginDelegateImpl::WindowFromPointPatch(POINT point
) {
1491 HWND window
= WindowFromPoint(point
);
1492 if (::ScreenToClient(window
, &point
)) {
1493 HWND child
= ChildWindowFromPoint(window
, point
);
1494 if (::IsWindow(child
) &&
1495 ::GetProp(child
, content::kPluginDummyParentProperty
))
1501 void WebPluginDelegateImpl::HandleCaptureForMessage(HWND window
,
1503 if (gfx::GetClassName(window
) != base::string16(kNativeWindowClassName
))
1507 case WM_LBUTTONDOWN
:
1508 case WM_MBUTTONDOWN
:
1509 case WM_RBUTTONDOWN
:
1510 ::SetCapture(window
);
1511 // As per documentation the WM_PARENTNOTIFY message is sent to the parent
1512 // window chain if mouse input is received by the child window. However
1513 // the parent receives the WM_PARENTNOTIFY message only if we doubleclick
1514 // on the window. We send the WM_PARENTNOTIFY message for mouse input
1515 // messages to the parent to indicate that user action is expected.
1516 ::SendMessage(::GetParent(window
), WM_PARENTNOTIFY
, message
, 0);
1530 } // namespace content