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. 4 5 #include "content/child/npapi/webplugin_delegate_impl.h" 6 7 #include <map> 8 #include <set> 9 #include <string> 10 #include <vector> 11 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" 37 38 using blink::WebKeyboardEvent; 39 using blink::WebInputEvent; 40 using blink::WebMouseEvent; 41 42 namespace content { 43 44 namespace { 45 46 const wchar_t kWebPluginDelegateProperty[] = L"WebPluginDelegateProperty"; 47 const wchar_t kPluginFlashThrottle[] = L"FlashThrottle"; 48 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. 53 // 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; 59 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; 65 66 // The current instance of the plugin which entered the modal loop. 67 WebPluginDelegateImpl* g_current_plugin_instance = NULL; 68 69 typedef std::deque<MSG> ThrottleQueue; 70 base::LazyInstance<ThrottleQueue> g_throttle_queue = LAZY_INSTANCE_INITIALIZER; 71 72 base::LazyInstance<std::map<HWND, WNDPROC> > g_window_handle_proc_map = 73 LAZY_INSTANCE_INITIALIZER; 74 75 // Helper object for patching the TrackPopupMenu API. 76 base::LazyInstance<base::win::IATPatchFunction> g_iat_patch_track_popup_menu = 77 LAZY_INSTANCE_INITIALIZER; 78 79 // Helper object for patching the SetCursor API. 80 base::LazyInstance<base::win::IATPatchFunction> g_iat_patch_set_cursor = 81 LAZY_INSTANCE_INITIALIZER; 82 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; 86 87 // Helper object for patching the GetProcAddress API. 88 base::LazyInstance<base::win::IATPatchFunction> g_iat_patch_get_proc_address = 89 LAZY_INSTANCE_INITIALIZER; 90 91 #if defined(USE_AURA) 92 base::LazyInstance<base::win::IATPatchFunction> g_iat_patch_window_from_point = 93 LAZY_INSTANCE_INITIALIZER; 94 #endif 95 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 { 105 public: 106 explicit DrawableContextEnforcer(NPWindow* window) 107 : window_(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. 111 if (disposable_dc_) 112 window_->window = CreateCompatibleDC(NULL); 113 } 114 115 ~DrawableContextEnforcer() { 116 if (!disposable_dc_) 117 return; 118 119 DeleteDC(static_cast<HDC>(window_->window)); 120 window_->window = NULL; 121 } 122 123 private: 124 NPWindow* window_; 125 bool disposable_dc_; 126 }; 127 128 // These are from ntddk.h 129 typedef LONG NTSTATUS; 130 131 #ifndef STATUS_SUCCESS 132 #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) 133 #endif 134 135 #ifndef STATUS_BUFFER_TOO_SMALL 136 #define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) 137 #endif 138 139 typedef enum _KEY_INFORMATION_CLASS { 140 KeyBasicInformation, 141 KeyNodeInformation, 142 KeyFullInformation, 143 KeyNameInformation, 144 KeyCachedInformation, 145 KeyVirtualizationInformation 146 } KEY_INFORMATION_CLASS; 147 148 typedef struct _KEY_NAME_INFORMATION { 149 ULONG NameLength; 150 WCHAR Name[1]; 151 } KEY_NAME_INFORMATION, *PKEY_NAME_INFORMATION; 152 153 typedef DWORD (__stdcall *ZwQueryKeyType)( 154 HANDLE key_handle, 155 int key_information_class, 156 PVOID key_information, 157 ULONG length, 158 PULONG result_length); 159 160 // Returns a key's full path. 161 std::wstring GetKeyPath(HKEY key) { 162 if (key == NULL) 163 return L""; 164 165 HMODULE dll = GetModuleHandle(L"ntdll.dll"); 166 if (dll == NULL) 167 return L""; 168 169 ZwQueryKeyType func = reinterpret_cast<ZwQueryKeyType>( 170 ::GetProcAddress(dll, "ZwQueryKey")); 171 if (func == NULL) 172 return L""; 173 174 DWORD size = 0; 175 DWORD result = 0; 176 result = func(key, KeyNameInformation, 0, 0, &size); 177 if (result != STATUS_BUFFER_TOO_SMALL) 178 return L""; 179 180 scoped_ptr<char[]> buffer(new char[size]); 181 if (buffer.get() == NULL) 182 return L""; 183 184 result = func(key, KeyNameInformation, buffer.get(), size, &size); 185 if (result != STATUS_SUCCESS) 186 return L""; 187 188 KEY_NAME_INFORMATION* info = 189 reinterpret_cast<KEY_NAME_INFORMATION*>(buffer.get()); 190 return std::wstring(info->Name, info->NameLength / sizeof(wchar_t)); 191 } 192 193 int GetPluginMajorVersion(const WebPluginInfo& plugin_info) { 194 Version plugin_version; 195 WebPluginInfo::CreateVersionFromString(plugin_info.version, &plugin_version); 196 197 int major_version = 0; 198 if (plugin_version.IsValid()) 199 major_version = plugin_version.components()[0]; 200 201 return major_version; 202 } 203 204 } // namespace 205 206 LRESULT CALLBACK WebPluginDelegateImpl::HandleEventMessageFilterHook( 207 int code, WPARAM wParam, LPARAM lParam) { 208 if (g_current_plugin_instance) { 209 g_current_plugin_instance->OnModalLoopEntered(); 210 } else { 211 NOTREACHED(); 212 } 213 return CallNextHookEx(NULL, code, wParam, lParam); 214 } 215 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); 220 if (hook_struct) 221 HandleCaptureForMessage(hook_struct->hwnd, wParam); 222 } 223 224 return CallNextHookEx(NULL, code, wParam, lParam); 225 } 226 227 WebPluginDelegateImpl::WebPluginDelegateImpl( 228 WebPlugin* plugin, 229 PluginInstance* instance) 230 : instance_(instance), 231 quirks_(0), 232 plugin_(plugin), 233 windowless_(false), 234 windowed_handle_(NULL), 235 windowed_did_set_window_(false), 236 plugin_wnd_proc_(NULL), 237 last_message_(0), 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), 247 mouse_hook_(NULL), 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_)); 254 255 const WebPluginInfo& plugin_info = instance_->plugin_lib()->plugin_info(); 256 std::wstring filename = 257 StringToLowerASCII(plugin_info.path.BaseName().value()); 258 259 if (instance_->mime_type() == kFlashPluginSwfMimeType || 260 filename == kFlashPlugin) { 261 // Flash only requests windowless plugins if we return a Mozilla user 262 // agent. 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; 271 #endif 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; 277 // 9.2 needs this. 278 quirks_ |= PLUGIN_QUIRK_SETWINDOW_TWICE; 279 } 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; 285 286 // Windowless mode doesn't work in the WMP NPAPI plugin. 287 quirks_ |= PLUGIN_QUIRK_NO_WINDOWLESS; 288 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; 293 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; 297 } 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 306 // handle 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; 312 } 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; 323 } 324 } 325 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_, 332 GWLP_WNDPROC, 333 reinterpret_cast<LONG_PTR>(old_dummy_window_proc_)); 334 } 335 ::DestroyWindow(dummy_window_for_activation_); 336 } 337 338 DestroyInstance(); 339 340 if (!windowless_) 341 WindowedDestroyWindow(); 342 343 if (handle_event_pump_messages_event_) { 344 CloseHandle(handle_event_pump_messages_event_); 345 } 346 } 347 348 bool WebPluginDelegateImpl::PlatformInitialize() { 349 plugin_->SetWindow(windowed_handle_); 350 351 if (windowless_) { 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_)); 357 } 358 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); 369 } 370 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); 383 } 384 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()); 398 } 399 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); 412 } 413 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); 423 } 424 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); 431 } 432 #endif 433 return true; 434 } 435 436 void WebPluginDelegateImpl::PlatformDestroyInstance() { 437 if (!instance_->plugin_lib()) 438 return; 439 440 // Unpatch if this is the last plugin instance. 441 if (instance_->plugin_lib()->instance_count() != 1) 442 return; 443 444 if (g_iat_patch_set_cursor.Pointer()->is_patched()) 445 g_iat_patch_set_cursor.Pointer()->Unpatch(); 446 447 if (g_iat_patch_track_popup_menu.Pointer()->is_patched()) 448 g_iat_patch_track_popup_menu.Pointer()->Unpatch(); 449 450 if (g_iat_patch_reg_enum_key_ex_w.Pointer()->is_patched()) 451 g_iat_patch_reg_enum_key_ex_w.Pointer()->Unpatch(); 452 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(); 456 #endif 457 458 if (mouse_hook_) { 459 UnhookWindowsHookEx(mouse_hook_); 460 mouse_hook_ = NULL; 461 } 462 } 463 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); 469 } 470 } 471 472 bool WebPluginDelegateImpl::WindowedCreatePlugin() { 473 DCHECK(!windowed_handle_); 474 475 RegisterNativeWindowClass(); 476 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, 481 0, 482 WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 483 0, 484 0, 485 0, 486 0, 487 GetDesktopWindow(), 488 0, 489 GetModuleHandle(NULL), 490 0); 491 if (windowed_handle_ == 0) 492 return false; 493 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); 506 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 = " << 523 GetLastError(); 524 } 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 = " << 533 GetLastError(); 534 } 535 } 536 } 537 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_, 541 GWLP_WNDPROC, 542 reinterpret_cast<LONG_PTR>(DefWindowProcA)); 543 544 return true; 545 } 546 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_, 554 GWLP_WNDPROC, 555 reinterpret_cast<LONG_PTR>(plugin_wnd_proc_)); 556 } 557 558 plugin_->WillDestroyWindow(windowed_handle_); 559 560 DestroyWindow(windowed_handle_); 561 windowed_handle_ = 0; 562 } 563 } 564 565 // Erase all messages in the queue destined for a particular window. 566 // When windows are closing, callers should use this function to clear 567 // the queue. 568 // static 569 void WebPluginDelegateImpl::ClearThrottleQueueForWindow(HWND window) { 570 ThrottleQueue* throttle_queue = g_throttle_queue.Pointer(); 571 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); 576 } else { 577 it++; 578 } 579 } 580 } 581 582 // Delayed callback for processing throttled messages. 583 // Throttled messages are aggregated globally across all plugins. 584 // static 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; 594 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); 602 } else { 603 it++; 604 } 605 } 606 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(); 611 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); 620 } 621 622 if (!throttle_queue_was_empty) { 623 base::MessageLoop::current()->PostDelayedTask( 624 FROM_HERE, 625 base::Bind(&WebPluginDelegateImpl::OnThrottleMessage), 626 base::TimeDelta::FromMilliseconds(kFlashWMUSERMessageThrottleDelayMs)); 627 } 628 } 629 630 // Schedule a windows message for delivery later. 631 // static 632 void WebPluginDelegateImpl::ThrottleMessage(WNDPROC proc, HWND hwnd, 633 UINT message, WPARAM wParam, 634 LPARAM lParam) { 635 MSG msg; 636 msg.time = reinterpret_cast<DWORD>(proc); 637 msg.hwnd = hwnd; 638 msg.message = message; 639 msg.wParam = wParam; 640 msg.lParam = lParam; 641 642 ThrottleQueue* throttle_queue = g_throttle_queue.Pointer(); 643 644 throttle_queue->push_back(msg); 645 646 if (throttle_queue->size() == 1) { 647 base::MessageLoop::current()->PostDelayedTask( 648 FROM_HERE, 649 base::Bind(&WebPluginDelegateImpl::OnThrottleMessage), 650 base::TimeDelta::FromMilliseconds(kFlashWMUSERMessageThrottleDelayMs)); 651 } 652 } 653 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. 657 // static 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); 662 663 WNDPROC old_proc = (*index).second; 664 DCHECK(old_proc); 665 666 switch (message) { 667 case WM_NCDESTROY: { 668 WebPluginDelegateImpl::ClearThrottleQueueForWindow(hwnd); 669 g_window_handle_proc_map.Get().erase(index); 670 break; 671 } 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. 675 case WM_USER + 1: { 676 WebPluginDelegateImpl::ThrottleMessage(old_proc, hwnd, message, wparam, 677 lparam); 678 return TRUE; 679 } 680 681 default: { 682 break; 683 } 684 } 685 return CallWindowProc(old_proc, hwnd, message, wparam, lparam); 686 } 687 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)); 692 CHECK(delegate); 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; 704 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); 711 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(); 715 } 716 } else if (message == WM_NCDESTROY) { 717 RemoveProp(hwnd, kWebPluginDelegateProperty); 718 } 719 return CallWindowProc( 720 delegate->old_dummy_window_proc_, hwnd, message, w_param, l_param); 721 } 722 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(); 730 return FALSE; 731 } 732 733 if (wcscmp(class_name, L"SWFlash_PlaceholderX")) 734 return TRUE; 735 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; 744 } 745 746 return TRUE; 747 } 748 749 bool WebPluginDelegateImpl::CreateDummyWindowForActivation() { 750 DCHECK(!dummy_window_for_activation_); 751 752 dummy_window_for_activation_ = CreateWindowEx( 753 0, 754 L"Static", 755 kDummyActivationWindowName, 756 WS_CHILD, 757 0, 758 0, 759 0, 760 0, 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. 763 GetDesktopWindow(), 764 0, 765 GetModuleHandle(NULL), 766 0); 767 768 if (dummy_window_for_activation_ == 0) 769 return false; 770 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))); 777 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 780 // get out of hand. 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). 786 NOTREACHED(); 787 LOG(ERROR) << "Failed to wrap all windowless Flash windows"; 788 } 789 return true; 790 } 791 792 bool WebPluginDelegateImpl::WindowedReposition( 793 const gfx::Rect& window_rect_in_dip, 794 const gfx::Rect& clip_rect_in_dip) { 795 if (!windowed_handle_) { 796 NOTREACHED(); 797 return false; 798 } 799 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) 803 return false; 804 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_, 816 NULL, 817 0, 818 0, 819 window_rect.width(), 820 window_rect.height(), 821 flags); 822 } 823 824 window_rect_ = window_rect; 825 clip_rect_ = clip_rect; 826 827 // Ensure that the entire window gets repainted. 828 ::InvalidateRect(windowed_handle_, NULL, FALSE); 829 830 return true; 831 } 832 833 void WebPluginDelegateImpl::WindowedSetWindow() { 834 if (!instance_) 835 return; 836 837 if (!windowed_handle_) { 838 NOTREACHED(); 839 return; 840 } 841 842 instance()->set_window_handle(windowed_handle_); 843 844 DCHECK(!instance()->windowless()); 845 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(); 852 window_.x = 0; 853 window_.y = 0; 854 855 window_.window = windowed_handle_; 856 window_.type = NPWindowTypeWindow; 857 858 // Reset this flag before entering the instance in case of side-effects. 859 windowed_did_set_window_ = true; 860 861 NPError err = instance()->NPP_SetWindow(&window_); 862 if (quirks_ & PLUGIN_QUIRK_SETWINDOW_TWICE) 863 instance()->NPP_SetWindow(&window_); 864 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_, 870 GWLP_WNDPROC, 871 reinterpret_cast<LONG_PTR>(NativeWndProc))); 872 } 873 } 874 875 ATOM WebPluginDelegateImpl::RegisterNativeWindowClass() { 876 static bool have_registered_window_class = false; 877 if (have_registered_window_class == true) 878 return true; 879 880 have_registered_window_class = true; 881 882 WNDCLASSEX wcex; 883 wcex.cbSize = sizeof(WNDCLASSEX); 884 wcex.style = CS_DBLCLKS; 885 wcex.lpfnWndProc = WrapperWindowProc; 886 wcex.cbClsExtra = 0; 887 wcex.cbWndExtra = 0; 888 wcex.hInstance = GetModuleHandle(NULL); 889 wcex.hIcon = 0; 890 wcex.hCursor = 0; 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; 899 wcex.hIconSm = 0; 900 901 return RegisterClassEx(&wcex); 902 } 903 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); 913 } 914 915 // Returns true if the message passed in corresponds to a user gesture. 916 static bool IsUserGestureMessage(unsigned int message) { 917 switch (message) { 918 case WM_LBUTTONDOWN: 919 case WM_LBUTTONUP: 920 case WM_RBUTTONDOWN: 921 case WM_RBUTTONUP: 922 case WM_MBUTTONDOWN: 923 case WM_MBUTTONUP: 924 case WM_KEYDOWN: 925 case WM_KEYUP: 926 return true; 927 928 default: 929 break; 930 } 931 932 return false; 933 } 934 935 LRESULT CALLBACK WebPluginDelegateImpl::NativeWndProc( 936 HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { 937 WebPluginDelegateImpl* delegate = reinterpret_cast<WebPluginDelegateImpl*>( 938 GetProp(hwnd, kWebPluginDelegateProperty)); 939 if (!delegate) { 940 NOTREACHED(); 941 return 0; 942 } 943 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. 952 return TRUE; 953 } 954 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); 962 return FALSE; 963 } 964 965 LRESULT result; 966 uint32 old_message = delegate->last_message_; 967 delegate->last_message_ = message; 968 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))); 978 979 RECT window_rect; 980 GetWindowRect(hwnd, &window_rect); 981 invalid_rect.Offset(-window_rect.left, -window_rect.top); 982 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; 987 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); 997 result = FALSE; 998 } else { 999 delegate->is_calling_wndproc = true; 1000 1001 if (!delegate->user_gesture_message_posted_ && 1002 IsUserGestureMessage(message)) { 1003 delegate->user_gesture_message_posted_ = true; 1004 1005 delegate->instance()->PushPopupsEnabledState(true); 1006 1007 base::MessageLoop::current()->PostDelayedTask( 1008 FROM_HERE, 1009 base::Bind(&WebPluginDelegateImpl::OnUserGestureEnd, 1010 delegate->user_gesture_msg_factory_.GetWeakPtr()), 1011 base::TimeDelta::FromMilliseconds(kWindowedPluginPopupTimerMs)); 1012 } 1013 1014 HandleCaptureForMessage(hwnd, message); 1015 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; 1019 1020 g_current_plugin_instance = delegate; 1021 1022 result = CallWindowProc( 1023 delegate->plugin_wnd_proc_, hwnd, message, wparam, lparam); 1024 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 1028 // still valid. 1029 if (::IsWindow(hwnd)) 1030 delegate->is_calling_wndproc = false; 1031 1032 g_current_plugin_instance = last_plugin_instance; 1033 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); 1045 } 1046 } 1047 if (::IsWindow(hwnd)) 1048 delegate->last_message_ = old_message; 1049 return result; 1050 } 1051 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_) 1058 return; 1059 1060 clip_rect_ = clip_rect; 1061 window_rect_ = window_rect; 1062 1063 WindowlessSetWindow(); 1064 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(); 1071 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); 1076 1077 instance()->NPP_HandleEvent(&pos_changed_event); 1078 } 1079 } 1080 1081 void WebPluginDelegateImpl::WindowlessPaint(HDC hdc, 1082 const gfx::Rect& damage_rect) { 1083 DCHECK(hdc); 1084 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(); 1090 1091 // Save away the old HDC as this could be a nested invocation. 1092 void* old_dc = window_.window; 1093 window_.window = hdc; 1094 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; 1104 } 1105 1106 void WebPluginDelegateImpl::WindowlessSetWindow() { 1107 if (!instance()) 1108 return; 1109 1110 if (window_rect_.IsEmpty()) // wait for geometry to be set. 1111 return; 1112 1113 DCHECK(instance()->windowless()); 1114 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_); 1125 1126 NPError err = instance()->NPP_SetWindow(&window_); 1127 DCHECK(err == NPERR_NO_ERROR); 1128 } 1129 1130 bool WebPluginDelegateImpl::PlatformSetPluginHasFocus(bool focused) { 1131 DCHECK(instance()->windowless()); 1132 1133 NPEvent focus_event; 1134 focus_event.event = focused ? WM_SETFOCUS : WM_KILLFOCUS; 1135 focus_event.wParam = 0; 1136 focus_event.lParam = 0; 1137 1138 instance()->NPP_HandleEvent(&focus_event); 1139 return true; 1140 } 1141 1142 static bool NPEventFromWebMouseEvent(const WebMouseEvent& event, 1143 NPEvent* np_event) { 1144 np_event->lParam = static_cast<uint32>(MAKELPARAM(event.windowX, 1145 event.windowY)); 1146 np_event->wParam = 0; 1147 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; 1158 1159 switch (event.type) { 1160 case WebInputEvent::MouseMove: 1161 case WebInputEvent::MouseLeave: 1162 case WebInputEvent::MouseEnter: 1163 np_event->event = WM_MOUSEMOVE; 1164 return true; 1165 case WebInputEvent::MouseDown: 1166 switch (event.button) { 1167 case WebMouseEvent::ButtonLeft: 1168 np_event->event = WM_LBUTTONDOWN; 1169 break; 1170 case WebMouseEvent::ButtonMiddle: 1171 np_event->event = WM_MBUTTONDOWN; 1172 break; 1173 case WebMouseEvent::ButtonRight: 1174 np_event->event = WM_RBUTTONDOWN; 1175 break; 1176 } 1177 return true; 1178 case WebInputEvent::MouseUp: 1179 switch (event.button) { 1180 case WebMouseEvent::ButtonLeft: 1181 np_event->event = WM_LBUTTONUP; 1182 break; 1183 case WebMouseEvent::ButtonMiddle: 1184 np_event->event = WM_MBUTTONUP; 1185 break; 1186 case WebMouseEvent::ButtonRight: 1187 np_event->event = WM_RBUTTONUP; 1188 break; 1189 } 1190 return true; 1191 default: 1192 NOTREACHED(); 1193 return false; 1194 } 1195 } 1196 1197 static bool NPEventFromWebKeyboardEvent(const WebKeyboardEvent& event, 1198 NPEvent* np_event) { 1199 np_event->wParam = event.windowsKeyCode; 1200 1201 switch (event.type) { 1202 case WebInputEvent::KeyDown: 1203 np_event->event = WM_KEYDOWN; 1204 np_event->lParam = 0; 1205 return true; 1206 case WebInputEvent::Char: 1207 np_event->event = WM_CHAR; 1208 np_event->lParam = 0; 1209 return true; 1210 case WebInputEvent::KeyUp: 1211 np_event->event = WM_KEYUP; 1212 np_event->lParam = 0x8000; 1213 return true; 1214 default: 1215 NOTREACHED(); 1216 return false; 1217 } 1218 } 1219 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)) { 1229 NOTREACHED(); 1230 return false; 1231 } 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)) { 1238 NOTREACHED(); 1239 return false; 1240 } 1241 return NPEventFromWebKeyboardEvent( 1242 *static_cast<const WebKeyboardEvent*>(&event), np_event); 1243 default: 1244 return false; 1245 } 1246 } 1247 1248 bool WebPluginDelegateImpl::PlatformHandleInputEvent( 1249 const WebInputEvent& event, WebCursor::CursorInfo* cursor_info) { 1250 DCHECK(cursor_info != NULL); 1251 1252 NPEvent np_event; 1253 if (!NPEventFromWebInputEvent(event, &np_event)) { 1254 return false; 1255 } 1256 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) { 1260 if (!plugin_ime_) 1261 plugin_ime_.reset(new WebPluginIMEWin); 1262 } 1263 WebPluginIMEWin::ScopedLock lock( 1264 event.isKeyboardEventType(event.type) ? plugin_ime_.get() : NULL); 1265 1266 HWND last_focus_window = NULL; 1267 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 1280 // dummy window. 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(); 1286 if (valid) { 1287 last_focus_window = ::SetFocus(dummy_window_for_activation_); 1288 } else { 1289 NOTREACHED() << "Dummy window not parented"; 1290 } 1291 } 1292 1293 bool old_task_reentrancy_state = 1294 base::MessageLoop::current()->NestableTasksAllowed(); 1295 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; 1299 1300 g_current_plugin_instance = this; 1301 1302 handle_event_depth_++; 1303 1304 bool popups_enabled = false; 1305 1306 if (IsUserGestureMessage(np_event.event)) { 1307 instance()->PushPopupsEnabledState(true); 1308 popups_enabled = true; 1309 } 1310 1311 bool ret = instance()->NPP_HandleEvent(&np_event) != 0; 1312 1313 if (popups_enabled) { 1314 instance()->PopPopupsEnabledState(); 1315 } 1316 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. 1321 ret = true; 1322 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); 1329 } 1330 1331 handle_event_depth_--; 1332 1333 g_current_plugin_instance = last_plugin_instance; 1334 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_); 1340 } 1341 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; 1346 } 1347 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 1351 // API call. 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 1362 // does the trick. 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)); 1368 1369 input_info.type = INPUT_MOUSE; 1370 input_info.mi.dwFlags = MOUSEEVENTF_LEFTUP; 1371 ::SendInput(1, &input_info, sizeof(INPUT)); 1372 } else { 1373 ::SetFocus(last_focus_window); 1374 } 1375 } 1376 return ret; 1377 } 1378 1379 1380 void WebPluginDelegateImpl::OnModalLoopEntered() { 1381 DCHECK(handle_event_pump_messages_event_ != NULL); 1382 SetEvent(handle_event_pump_messages_event_); 1383 1384 base::MessageLoop::current()->SetNestableTasksAllowed(true); 1385 base::MessageLoop::current()->set_os_modal_loop(true); 1386 1387 UnhookWindowsHookEx(handle_event_message_filter_hook_); 1388 handle_event_message_filter_hook_ = NULL; 1389 } 1390 1391 bool WebPluginDelegateImpl::ShouldTrackEventForModalLoops(NPEvent* event) { 1392 if (event->event == WM_RBUTTONDOWN) 1393 return true; 1394 return false; 1395 } 1396 1397 void WebPluginDelegateImpl::OnUserGestureEnd() { 1398 user_gesture_message_posted_ = false; 1399 instance()->PopPopupsEnabledState(); 1400 } 1401 1402 BOOL WINAPI WebPluginDelegateImpl::TrackPopupMenuPatch( 1403 HMENU menu, unsigned int flags, int x, int y, int reserved, 1404 HWND window, const RECT* rect) { 1405 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 1411 // thread. 1412 if (::GetCurrentThreadId() != window_thread_id) { 1413 bool valid = 1414 GetParent(g_current_plugin_instance->dummy_window_for_activation_) != 1415 GetDesktopWindow(); 1416 if (valid) { 1417 window = g_current_plugin_instance->dummy_window_for_activation_; 1418 } else { 1419 NOTREACHED() << "Dummy window not parented"; 1420 } 1421 } 1422 } 1423 1424 BOOL result = TrackPopupMenu(menu, flags, x, y, reserved, window, rect); 1425 return result; 1426 } 1427 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); 1437 } 1438 return current_cursor; 1439 } 1440 return ::SetCursor(cursor); 1441 } 1442 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)); 1454 *name_size = 1455 std::min(orig_size, static_cast<DWORD>(arraysize(kChromeExeName))); 1456 } 1457 1458 return rv; 1459 } 1460 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) { 1466 if (!plugin_ime_) 1467 plugin_ime_.reset(new WebPluginIMEWin); 1468 1469 plugin_ime_->CompositionUpdated(text, clauses, target, cursor_position); 1470 plugin_ime_->SendEvents(instance()); 1471 } 1472 1473 void WebPluginDelegateImpl::ImeCompositionCompleted( 1474 const base::string16& text) { 1475 if (!plugin_ime_) 1476 plugin_ime_.reset(new WebPluginIMEWin); 1477 plugin_ime_->CompositionCompleted(text); 1478 plugin_ime_->SendEvents(instance()); 1479 } 1480 1481 bool WebPluginDelegateImpl::GetIMEStatus(int* input_type, 1482 gfx::Rect* caret_rect) { 1483 if (!plugin_ime_) 1484 return false; 1485 return plugin_ime_->GetStatus(input_type, caret_rect); 1486 } 1487 1488 // static 1489 FARPROC WINAPI WebPluginDelegateImpl::GetProcAddressPatch(HMODULE module, 1490 LPCSTR name) { 1491 FARPROC imm_function = WebPluginIMEWin::GetProcAddress(name); 1492 if (imm_function) 1493 return imm_function; 1494 return ::GetProcAddress(module, name); 1495 } 1496 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)) 1504 return child; 1505 } 1506 return window; 1507 } 1508 #endif 1509 1510 void WebPluginDelegateImpl::HandleCaptureForMessage(HWND window, 1511 UINT message) { 1512 if (gfx::GetClassName(window) != base::string16(kNativeWindowClassName)) 1513 return; 1514 1515 switch (message) { 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); 1526 break; 1527 1528 case WM_LBUTTONUP: 1529 case WM_MBUTTONUP: 1530 case WM_RBUTTONUP: 1531 ::ReleaseCapture(); 1532 break; 1533 1534 default: 1535 break; 1536 } 1537 } 1538 1539 } // namespace content 1540