| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "content/child/npapi/webplugin_delegate_impl.h" | 5 #include "content/child/npapi/webplugin_delegate_impl.h" |
| 6 | 6 |
| 7 #include <stdint.h> | 7 #include <stdint.h> |
| 8 #include <string.h> | 8 #include <string.h> |
| 9 | 9 |
| 10 #include <map> | 10 #include <map> |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 51 // Flash can easily exceed the limits of our CPU if we don't throttle it. | 51 // Flash can easily exceed the limits of our CPU if we don't throttle it. |
| 52 // The throttle has been chosen by testing various delays and compromising | 52 // The throttle has been chosen by testing various delays and compromising |
| 53 // on acceptable Flash performance and reasonable CPU consumption. | 53 // on acceptable Flash performance and reasonable CPU consumption. |
| 54 // | 54 // |
| 55 // I'd like to make the throttle delay variable, based on the amount of | 55 // I'd like to make the throttle delay variable, based on the amount of |
| 56 // time currently required to paint Flash plugins. There isn't a good | 56 // time currently required to paint Flash plugins. There isn't a good |
| 57 // way to count the time spent in aggregate plugin painting, however, so | 57 // way to count the time spent in aggregate plugin painting, however, so |
| 58 // this seems to work well enough. | 58 // this seems to work well enough. |
| 59 const int kFlashWMUSERMessageThrottleDelayMs = 5; | 59 const int kFlashWMUSERMessageThrottleDelayMs = 5; |
| 60 | 60 |
| 61 // Flash displays popups in response to user clicks by posting a WM_USER | |
| 62 // message to the plugin window. The handler for this message displays | |
| 63 // the popup. To ensure that the popups allowed state is sent correctly | |
| 64 // to the renderer we reset the popups allowed state in a timer. | |
| 65 const int kWindowedPluginPopupTimerMs = 50; | |
| 66 | |
| 67 // The current instance of the plugin which entered the modal loop. | 61 // The current instance of the plugin which entered the modal loop. |
| 68 WebPluginDelegateImpl* g_current_plugin_instance = NULL; | 62 WebPluginDelegateImpl* g_current_plugin_instance = NULL; |
| 69 | 63 |
| 70 typedef std::deque<MSG> ThrottleQueue; | 64 typedef std::deque<MSG> ThrottleQueue; |
| 71 base::LazyInstance<ThrottleQueue> g_throttle_queue = LAZY_INSTANCE_INITIALIZER; | 65 base::LazyInstance<ThrottleQueue> g_throttle_queue = LAZY_INSTANCE_INITIALIZER; |
| 72 | 66 |
| 73 base::LazyInstance<std::map<HWND, WNDPROC> > g_window_handle_proc_map = | 67 base::LazyInstance<std::map<HWND, WNDPROC> > g_window_handle_proc_map = |
| 74 LAZY_INSTANCE_INITIALIZER; | 68 LAZY_INSTANCE_INITIALIZER; |
| 75 | 69 |
| 76 // Helper object for patching the TrackPopupMenu API. | 70 // Helper object for patching the TrackPopupMenu API. |
| (...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 205 LRESULT CALLBACK WebPluginDelegateImpl::HandleEventMessageFilterHook( | 199 LRESULT CALLBACK WebPluginDelegateImpl::HandleEventMessageFilterHook( |
| 206 int code, WPARAM wParam, LPARAM lParam) { | 200 int code, WPARAM wParam, LPARAM lParam) { |
| 207 if (g_current_plugin_instance) { | 201 if (g_current_plugin_instance) { |
| 208 g_current_plugin_instance->OnModalLoopEntered(); | 202 g_current_plugin_instance->OnModalLoopEntered(); |
| 209 } else { | 203 } else { |
| 210 NOTREACHED(); | 204 NOTREACHED(); |
| 211 } | 205 } |
| 212 return CallNextHookEx(NULL, code, wParam, lParam); | 206 return CallNextHookEx(NULL, code, wParam, lParam); |
| 213 } | 207 } |
| 214 | 208 |
| 215 LRESULT CALLBACK WebPluginDelegateImpl::MouseHookProc( | |
| 216 int code, WPARAM wParam, LPARAM lParam) { | |
| 217 if (code == HC_ACTION) { | |
| 218 MOUSEHOOKSTRUCT* hook_struct = reinterpret_cast<MOUSEHOOKSTRUCT*>(lParam); | |
| 219 if (hook_struct) | |
| 220 HandleCaptureForMessage(hook_struct->hwnd, wParam); | |
| 221 } | |
| 222 | |
| 223 return CallNextHookEx(NULL, code, wParam, lParam); | |
| 224 } | |
| 225 | |
| 226 WebPluginDelegateImpl::WebPluginDelegateImpl(WebPlugin* plugin, | 209 WebPluginDelegateImpl::WebPluginDelegateImpl(WebPlugin* plugin, |
| 227 PluginInstance* instance) | 210 PluginInstance* instance) |
| 228 : windowed_handle_(NULL), | 211 : plugin_(plugin), |
| 229 windowed_did_set_window_(false), | |
| 230 windowless_(false), | |
| 231 plugin_(plugin), | |
| 232 instance_(instance), | 212 instance_(instance), |
| 233 plugin_wnd_proc_(NULL), | |
| 234 last_message_(0), | |
| 235 is_calling_wndproc(false), | |
| 236 quirks_(0), | 213 quirks_(0), |
| 237 dummy_window_for_activation_(NULL), | 214 dummy_window_for_activation_(NULL), |
| 238 dummy_window_parent_(NULL), | 215 dummy_window_parent_(NULL), |
| 239 old_dummy_window_proc_(NULL), | 216 old_dummy_window_proc_(NULL), |
| 240 handle_event_message_filter_hook_(NULL), | 217 handle_event_message_filter_hook_(NULL), |
| 241 handle_event_pump_messages_event_(NULL), | 218 handle_event_pump_messages_event_(NULL), |
| 242 user_gesture_message_posted_(false), | |
| 243 mouse_hook_(NULL), | |
| 244 handle_event_depth_(0), | 219 handle_event_depth_(0), |
| 245 first_set_window_call_(true), | 220 first_set_window_call_(true), |
| 246 plugin_has_focus_(false), | 221 plugin_has_focus_(false), |
| 247 has_webkit_focus_(false), | 222 has_webkit_focus_(false), |
| 248 containing_view_has_focus_(true), | 223 containing_view_has_focus_(true), |
| 249 creation_succeeded_(false), | 224 creation_succeeded_(false), |
| 250 user_gesture_msg_factory_(this) { | 225 user_gesture_msg_factory_(this) { |
| 251 memset(&window_, 0, sizeof(window_)); | 226 memset(&window_, 0, sizeof(window_)); |
| 252 | 227 |
| 253 const WebPluginInfo& plugin_info = instance_->plugin_lib()->plugin_info(); | 228 const WebPluginInfo& plugin_info = instance_->plugin_lib()->plugin_info(); |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 326 if (current_wnd_proc == DummyWindowProc) { | 301 if (current_wnd_proc == DummyWindowProc) { |
| 327 SetWindowLongPtr(dummy_window_for_activation_, | 302 SetWindowLongPtr(dummy_window_for_activation_, |
| 328 GWLP_WNDPROC, | 303 GWLP_WNDPROC, |
| 329 reinterpret_cast<LONG_PTR>(old_dummy_window_proc_)); | 304 reinterpret_cast<LONG_PTR>(old_dummy_window_proc_)); |
| 330 } | 305 } |
| 331 ::DestroyWindow(dummy_window_for_activation_); | 306 ::DestroyWindow(dummy_window_for_activation_); |
| 332 } | 307 } |
| 333 | 308 |
| 334 DestroyInstance(); | 309 DestroyInstance(); |
| 335 | 310 |
| 336 if (!windowless_) | |
| 337 WindowedDestroyWindow(); | |
| 338 | |
| 339 if (handle_event_pump_messages_event_) { | 311 if (handle_event_pump_messages_event_) { |
| 340 CloseHandle(handle_event_pump_messages_event_); | 312 CloseHandle(handle_event_pump_messages_event_); |
| 341 } | 313 } |
| 342 } | 314 } |
| 343 | 315 |
| 344 bool WebPluginDelegateImpl::PlatformInitialize() { | 316 bool WebPluginDelegateImpl::PlatformInitialize() { |
| 345 plugin_->SetWindow(windowed_handle_); | 317 CreateDummyWindowForActivation(); |
| 346 | 318 handle_event_pump_messages_event_ = CreateEvent(NULL, TRUE, FALSE, NULL); |
| 347 if (windowless_) { | 319 plugin_->SetWindowlessData( |
| 348 CreateDummyWindowForActivation(); | 320 handle_event_pump_messages_event_, |
| 349 handle_event_pump_messages_event_ = CreateEvent(NULL, TRUE, FALSE, NULL); | 321 reinterpret_cast<gfx::NativeViewId>(dummy_window_for_activation_)); |
| 350 plugin_->SetWindowlessData( | |
| 351 handle_event_pump_messages_event_, | |
| 352 reinterpret_cast<gfx::NativeViewId>(dummy_window_for_activation_)); | |
| 353 } | |
| 354 | 322 |
| 355 // Windowless plugins call the WindowFromPoint API and passes the result of | 323 // 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 | 324 // 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 | 325 // 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 | 326 // 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 | 327 // on the browser thread. Our workaround is to intercept the TrackPopupMenu |
| 360 // API and replace the window handle with the dummy activation window. | 328 // API and replace the window handle with the dummy activation window. |
| 361 if (windowless_ && !g_iat_patch_track_popup_menu.Pointer()->is_patched()) { | 329 if (!g_iat_patch_track_popup_menu.Pointer()->is_patched()) { |
| 362 g_iat_patch_track_popup_menu.Pointer()->Patch( | 330 g_iat_patch_track_popup_menu.Pointer()->Patch( |
| 363 GetPluginPath().value().c_str(), "user32.dll", "TrackPopupMenu", | 331 GetPluginPath().value().c_str(), "user32.dll", "TrackPopupMenu", |
| 364 WebPluginDelegateImpl::TrackPopupMenuPatch); | 332 WebPluginDelegateImpl::TrackPopupMenuPatch); |
| 365 } | 333 } |
| 366 | 334 |
| 367 // Windowless plugins can set cursors by calling the SetCursor API. This | 335 // 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 | 336 // works because the thread inputs of the browser UI thread and the plugin |
| 369 // thread are attached. We intercept the SetCursor API for windowless | 337 // thread are attached. We intercept the SetCursor API for windowless |
| 370 // plugins and remember the cursor being set. This is shipped over to the | 338 // 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 | 339 // browser in the HandleEvent call, which ensures that the cursor does not |
| 372 // change when a windowless plugin instance changes the cursor | 340 // change when a windowless plugin instance changes the cursor |
| 373 // in a background tab. | 341 // in a background tab. |
| 374 if (windowless_ && !g_iat_patch_set_cursor.Pointer()->is_patched() && | 342 if (!g_iat_patch_set_cursor.Pointer()->is_patched() && |
| 375 (quirks_ & PLUGIN_QUIRK_PATCH_SETCURSOR)) { | 343 (quirks_ & PLUGIN_QUIRK_PATCH_SETCURSOR)) { |
| 376 g_iat_patch_set_cursor.Pointer()->Patch( | 344 g_iat_patch_set_cursor.Pointer()->Patch( |
| 377 GetPluginPath().value().c_str(), "user32.dll", "SetCursor", | 345 GetPluginPath().value().c_str(), "user32.dll", "SetCursor", |
| 378 WebPluginDelegateImpl::SetCursorPatch); | 346 WebPluginDelegateImpl::SetCursorPatch); |
| 379 } | 347 } |
| 380 | 348 |
| 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()); | |
| 394 } | |
| 395 | |
| 396 // On XP, WMP will use its old UI unless a registry key under HKLM has the | 349 // 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, | 350 // name of the current process. We do it in the installer for admin users, |
| 398 // for the rest patch this function. | 351 // for the rest patch this function. |
| 399 if ((quirks_ & PLUGIN_QUIRK_PATCH_REGENUMKEYEXW) && | 352 if ((quirks_ & PLUGIN_QUIRK_PATCH_REGENUMKEYEXW) && |
| 400 base::win::GetVersion() == base::win::VERSION_XP && | 353 base::win::GetVersion() == base::win::VERSION_XP && |
| 401 (base::win::RegKey().Open(HKEY_LOCAL_MACHINE, | 354 (base::win::RegKey().Open(HKEY_LOCAL_MACHINE, |
| 402 L"SOFTWARE\\Microsoft\\MediaPlayer\\ShimInclusionList\\chrome.exe", | 355 L"SOFTWARE\\Microsoft\\MediaPlayer\\ShimInclusionList\\chrome.exe", |
| 403 KEY_READ) != ERROR_SUCCESS) && | 356 KEY_READ) != ERROR_SUCCESS) && |
| 404 !g_iat_patch_reg_enum_key_ex_w.Pointer()->is_patched()) { | 357 !g_iat_patch_reg_enum_key_ex_w.Pointer()->is_patched()) { |
| 405 g_iat_patch_reg_enum_key_ex_w.Pointer()->Patch( | 358 g_iat_patch_reg_enum_key_ex_w.Pointer()->Patch( |
| 406 L"wmpdxm.dll", "advapi32.dll", "RegEnumKeyExW", | 359 L"wmpdxm.dll", "advapi32.dll", "RegEnumKeyExW", |
| 407 WebPluginDelegateImpl::RegEnumKeyExWPatch); | 360 WebPluginDelegateImpl::RegEnumKeyExWPatch); |
| 408 } | 361 } |
| 409 | 362 |
| 410 // Flash retrieves the pointers to IMM32 functions with GetProcAddress() calls | 363 // 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 | 364 // 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 | 365 // can dispatch these IMM32 calls to the WebPluginIMEWin class, which emulates |
| 413 // IMM32 functions for Flash. | 366 // IMM32 functions for Flash. |
| 414 if (!g_iat_patch_get_proc_address.Pointer()->is_patched() && | 367 if (!g_iat_patch_get_proc_address.Pointer()->is_patched() && |
| 415 (quirks_ & PLUGIN_QUIRK_EMULATE_IME)) { | 368 (quirks_ & PLUGIN_QUIRK_EMULATE_IME)) { |
| 416 g_iat_patch_get_proc_address.Pointer()->Patch( | 369 g_iat_patch_get_proc_address.Pointer()->Patch( |
| 417 GetPluginPath().value().c_str(), "kernel32.dll", "GetProcAddress", | 370 GetPluginPath().value().c_str(), "kernel32.dll", "GetProcAddress", |
| 418 GetProcAddressPatch); | 371 GetProcAddressPatch); |
| 419 } | 372 } |
| 420 | 373 |
| 421 if (windowless_ && !g_iat_patch_window_from_point.Pointer()->is_patched() && | 374 if (!g_iat_patch_window_from_point.Pointer()->is_patched() && |
| 422 (quirks_ & PLUGIN_QUIRK_FAKE_WINDOW_FROM_POINT)) { | 375 (quirks_ & PLUGIN_QUIRK_FAKE_WINDOW_FROM_POINT)) { |
| 423 g_iat_patch_window_from_point.Pointer()->Patch( | 376 g_iat_patch_window_from_point.Pointer()->Patch( |
| 424 GetPluginPath().value().c_str(), "user32.dll", "WindowFromPoint", | 377 GetPluginPath().value().c_str(), "user32.dll", "WindowFromPoint", |
| 425 WebPluginDelegateImpl::WindowFromPointPatch); | 378 WebPluginDelegateImpl::WindowFromPointPatch); |
| 426 } | 379 } |
| 427 | 380 |
| 428 return true; | 381 return true; |
| 429 } | 382 } |
| 430 | 383 |
| 431 void WebPluginDelegateImpl::PlatformDestroyInstance() { | 384 void WebPluginDelegateImpl::PlatformDestroyInstance() { |
| 432 if (!instance_->plugin_lib()) | 385 if (!instance_->plugin_lib()) |
| 433 return; | 386 return; |
| 434 | 387 |
| 435 // Unpatch if this is the last plugin instance. | 388 // Unpatch if this is the last plugin instance. |
| 436 if (instance_->plugin_lib()->instance_count() != 1) | 389 if (instance_->plugin_lib()->instance_count() != 1) |
| 437 return; | 390 return; |
| 438 | 391 |
| 439 if (g_iat_patch_set_cursor.Pointer()->is_patched()) | 392 if (g_iat_patch_set_cursor.Pointer()->is_patched()) |
| 440 g_iat_patch_set_cursor.Pointer()->Unpatch(); | 393 g_iat_patch_set_cursor.Pointer()->Unpatch(); |
| 441 | 394 |
| 442 if (g_iat_patch_track_popup_menu.Pointer()->is_patched()) | 395 if (g_iat_patch_track_popup_menu.Pointer()->is_patched()) |
| 443 g_iat_patch_track_popup_menu.Pointer()->Unpatch(); | 396 g_iat_patch_track_popup_menu.Pointer()->Unpatch(); |
| 444 | 397 |
| 445 if (g_iat_patch_reg_enum_key_ex_w.Pointer()->is_patched()) | 398 if (g_iat_patch_reg_enum_key_ex_w.Pointer()->is_patched()) |
| 446 g_iat_patch_reg_enum_key_ex_w.Pointer()->Unpatch(); | 399 g_iat_patch_reg_enum_key_ex_w.Pointer()->Unpatch(); |
| 447 | 400 |
| 448 if (g_iat_patch_window_from_point.Pointer()->is_patched()) | 401 if (g_iat_patch_window_from_point.Pointer()->is_patched()) |
| 449 g_iat_patch_window_from_point.Pointer()->Unpatch(); | 402 g_iat_patch_window_from_point.Pointer()->Unpatch(); |
| 450 | |
| 451 if (mouse_hook_) { | |
| 452 UnhookWindowsHookEx(mouse_hook_); | |
| 453 mouse_hook_ = NULL; | |
| 454 } | |
| 455 } | 403 } |
| 456 | 404 |
| 457 void WebPluginDelegateImpl::Paint(SkCanvas* canvas, const gfx::Rect& rect) { | 405 void WebPluginDelegateImpl::Paint(SkCanvas* canvas, const gfx::Rect& rect) { |
| 458 if (windowless_ && skia::SupportsPlatformPaint(canvas)) { | 406 if (skia::SupportsPlatformPaint(canvas)) { |
| 459 skia::ScopedPlatformPaint scoped_platform_paint(canvas); | 407 skia::ScopedPlatformPaint scoped_platform_paint(canvas); |
| 460 HDC hdc = scoped_platform_paint.GetPlatformSurface(); | 408 HDC hdc = scoped_platform_paint.GetPlatformSurface(); |
| 461 WindowlessPaint(hdc, rect); | 409 WindowlessPaint(hdc, rect); |
| 462 } | 410 } |
| 463 } | 411 } |
| 464 | 412 |
| 465 bool WebPluginDelegateImpl::WindowedCreatePlugin() { | |
| 466 DCHECK(!windowed_handle_); | |
| 467 | |
| 468 RegisterNativeWindowClass(); | |
| 469 | |
| 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, | |
| 474 0, | |
| 475 WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, | |
| 476 0, | |
| 477 0, | |
| 478 0, | |
| 479 0, | |
| 480 GetDesktopWindow(), | |
| 481 0, | |
| 482 GetModuleHandle(NULL), | |
| 483 0); | |
| 484 if (windowed_handle_ == 0) | |
| 485 return false; | |
| 486 | |
| 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); | |
| 499 | |
| 500 BOOL result = SetProp(windowed_handle_, kWebPluginDelegateProperty, this); | |
| 501 DCHECK(result == TRUE) << "SetProp failed, last error = " << GetLastError(); | |
| 502 | |
| 503 // Calling SetWindowLongPtrA here makes the window proc ASCII, which is | |
| 504 // required by at least the Shockwave Director plugin. | |
| 505 SetWindowLongPtrA(windowed_handle_, | |
| 506 GWLP_WNDPROC, | |
| 507 reinterpret_cast<LONG_PTR>(DefWindowProcA)); | |
| 508 | |
| 509 return true; | |
| 510 } | |
| 511 | |
| 512 void WebPluginDelegateImpl::WindowedDestroyWindow() { | |
| 513 if (windowed_handle_ != NULL) { | |
| 514 // Unsubclass the window. | |
| 515 WNDPROC current_wnd_proc = reinterpret_cast<WNDPROC>( | |
| 516 GetWindowLongPtr(windowed_handle_, GWLP_WNDPROC)); | |
| 517 if (current_wnd_proc == NativeWndProc) { | |
| 518 SetWindowLongPtr(windowed_handle_, | |
| 519 GWLP_WNDPROC, | |
| 520 reinterpret_cast<LONG_PTR>(plugin_wnd_proc_)); | |
| 521 } | |
| 522 | |
| 523 plugin_->WillDestroyWindow(windowed_handle_); | |
| 524 | |
| 525 DestroyWindow(windowed_handle_); | |
| 526 windowed_handle_ = 0; | |
| 527 } | |
| 528 } | |
| 529 | |
| 530 // Erase all messages in the queue destined for a particular window. | 413 // Erase all messages in the queue destined for a particular window. |
| 531 // When windows are closing, callers should use this function to clear | 414 // When windows are closing, callers should use this function to clear |
| 532 // the queue. | 415 // the queue. |
| 533 // static | 416 // static |
| 534 void WebPluginDelegateImpl::ClearThrottleQueueForWindow(HWND window) { | 417 void WebPluginDelegateImpl::ClearThrottleQueueForWindow(HWND window) { |
| 535 ThrottleQueue* throttle_queue = g_throttle_queue.Pointer(); | 418 ThrottleQueue* throttle_queue = g_throttle_queue.Pointer(); |
| 536 | 419 |
| 537 ThrottleQueue::iterator it; | 420 ThrottleQueue::iterator it; |
| 538 for (it = throttle_queue->begin(); it != throttle_queue->end(); ) { | 421 for (it = throttle_queue->begin(); it != throttle_queue->end(); ) { |
| 539 if (it->hwnd == window) { | 422 if (it->hwnd == window) { |
| (...skipping 208 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 748 reinterpret_cast<LPARAM>( | 631 reinterpret_cast<LPARAM>( |
| 749 &WebPluginDelegateImpl::FlashWindowlessWndProc))) { | 632 &WebPluginDelegateImpl::FlashWindowlessWndProc))) { |
| 750 // Log that this happened. Flash will still work; it just means the | 633 // Log that this happened. Flash will still work; it just means the |
| 751 // throttle isn't installed (and Flash will use more CPU). | 634 // throttle isn't installed (and Flash will use more CPU). |
| 752 NOTREACHED(); | 635 NOTREACHED(); |
| 753 LOG(ERROR) << "Failed to wrap all windowless Flash windows"; | 636 LOG(ERROR) << "Failed to wrap all windowless Flash windows"; |
| 754 } | 637 } |
| 755 return true; | 638 return true; |
| 756 } | 639 } |
| 757 | 640 |
| 758 bool WebPluginDelegateImpl::WindowedReposition( | |
| 759 const gfx::Rect& window_rect_in_dip, | |
| 760 const gfx::Rect& clip_rect_in_dip) { | |
| 761 if (!windowed_handle_) { | |
| 762 NOTREACHED(); | |
| 763 return false; | |
| 764 } | |
| 765 | |
| 766 gfx::Rect window_rect = gfx::win::DIPToScreenRect(window_rect_in_dip); | |
| 767 gfx::Rect clip_rect = gfx::win::DIPToScreenRect(clip_rect_in_dip); | |
| 768 if (window_rect_ == window_rect && clip_rect_ == clip_rect) | |
| 769 return false; | |
| 770 | |
| 771 // We only set the plugin's size here. Its position is moved elsewhere, which | |
| 772 // allows the window moves/scrolling/clipping to be synchronized with the page | |
| 773 // and other windows. | |
| 774 // If the plugin window has no parent, then don't focus it because it isn't | |
| 775 // being displayed anywhere. See: | |
| 776 // http://code.google.com/p/chromium/issues/detail?id=32658 | |
| 777 if (window_rect.size() != window_rect_.size()) { | |
| 778 UINT flags = SWP_NOMOVE | SWP_NOZORDER; | |
| 779 if (!GetParent(windowed_handle_)) | |
| 780 flags |= SWP_NOACTIVATE; | |
| 781 ::SetWindowPos(windowed_handle_, | |
| 782 NULL, | |
| 783 0, | |
| 784 0, | |
| 785 window_rect.width(), | |
| 786 window_rect.height(), | |
| 787 flags); | |
| 788 } | |
| 789 | |
| 790 window_rect_ = window_rect; | |
| 791 clip_rect_ = clip_rect; | |
| 792 | |
| 793 // Ensure that the entire window gets repainted. | |
| 794 ::InvalidateRect(windowed_handle_, NULL, FALSE); | |
| 795 | |
| 796 return true; | |
| 797 } | |
| 798 | |
| 799 void WebPluginDelegateImpl::WindowedSetWindow() { | |
| 800 if (!instance_.get()) | |
| 801 return; | |
| 802 | |
| 803 if (!windowed_handle_) { | |
| 804 NOTREACHED(); | |
| 805 return; | |
| 806 } | |
| 807 | |
| 808 instance()->set_window_handle(windowed_handle_); | |
| 809 | |
| 810 DCHECK(!instance()->windowless()); | |
| 811 | |
| 812 window_.clipRect.top = std::max(0, clip_rect_.y()); | |
| 813 window_.clipRect.left = std::max(0, clip_rect_.x()); | |
| 814 window_.clipRect.bottom = std::max(0, clip_rect_.y() + clip_rect_.height()); | |
| 815 window_.clipRect.right = std::max(0, clip_rect_.x() + clip_rect_.width()); | |
| 816 window_.height = window_rect_.height(); | |
| 817 window_.width = window_rect_.width(); | |
| 818 window_.x = 0; | |
| 819 window_.y = 0; | |
| 820 | |
| 821 window_.window = windowed_handle_; | |
| 822 window_.type = NPWindowTypeWindow; | |
| 823 | |
| 824 // Reset this flag before entering the instance in case of side-effects. | |
| 825 windowed_did_set_window_ = true; | |
| 826 | |
| 827 instance()->NPP_SetWindow(&window_); | |
| 828 if (quirks_ & PLUGIN_QUIRK_SETWINDOW_TWICE) | |
| 829 instance()->NPP_SetWindow(&window_); | |
| 830 | |
| 831 WNDPROC current_wnd_proc = reinterpret_cast<WNDPROC>( | |
| 832 GetWindowLongPtr(windowed_handle_, GWLP_WNDPROC)); | |
| 833 if (current_wnd_proc != NativeWndProc) { | |
| 834 plugin_wnd_proc_ = reinterpret_cast<WNDPROC>( | |
| 835 SetWindowLongPtr(windowed_handle_, | |
| 836 GWLP_WNDPROC, | |
| 837 reinterpret_cast<LONG_PTR>(NativeWndProc))); | |
| 838 } | |
| 839 } | |
| 840 | |
| 841 ATOM WebPluginDelegateImpl::RegisterNativeWindowClass() { | |
| 842 static bool have_registered_window_class = false; | |
| 843 if (have_registered_window_class == true) | |
| 844 return true; | |
| 845 | |
| 846 have_registered_window_class = true; | |
| 847 | |
| 848 WNDCLASSEX wcex; | |
| 849 wcex.cbSize = sizeof(WNDCLASSEX); | |
| 850 wcex.style = CS_DBLCLKS; | |
| 851 wcex.lpfnWndProc = WrapperWindowProc; | |
| 852 wcex.cbClsExtra = 0; | |
| 853 wcex.cbWndExtra = 0; | |
| 854 wcex.hInstance = GetModuleHandle(NULL); | |
| 855 wcex.hIcon = 0; | |
| 856 wcex.hCursor = 0; | |
| 857 // Some plugins like windows media player 11 create child windows parented | |
| 858 // by our plugin window, where the media content is rendered. These plugins | |
| 859 // dont implement WM_ERASEBKGND, which causes painting issues, when the | |
| 860 // window where the media is rendered is moved around. DefWindowProc does | |
| 861 // implement WM_ERASEBKGND correctly if we have a valid background brush. | |
| 862 wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW+1); | |
| 863 wcex.lpszMenuName = 0; | |
| 864 wcex.lpszClassName = kNativeWindowClassName; | |
| 865 wcex.hIconSm = 0; | |
| 866 | |
| 867 return RegisterClassEx(&wcex); | |
| 868 } | |
| 869 | |
| 870 LRESULT CALLBACK WebPluginDelegateImpl::WrapperWindowProc( | |
| 871 HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { | |
| 872 // This is another workaround for Issue 2673 in chromium "Flash: IME not | |
| 873 // available". Somehow, the CallWindowProc() function does not dispatch | |
| 874 // window messages when its first parameter is a handle representing the | |
| 875 // DefWindowProc() function. To avoid this problem, this code creates a | |
| 876 // wrapper function which just encapsulates the DefWindowProc() function | |
| 877 // and set it as the window procedure of a windowed plugin. | |
| 878 return DefWindowProc(hWnd, message, wParam, lParam); | |
| 879 } | |
| 880 | |
| 881 // Returns true if the message passed in corresponds to a user gesture. | 641 // Returns true if the message passed in corresponds to a user gesture. |
| 882 static bool IsUserGestureMessage(unsigned int message) { | 642 static bool IsUserGestureMessage(unsigned int message) { |
| 883 switch (message) { | 643 switch (message) { |
| 884 case WM_LBUTTONDOWN: | 644 case WM_LBUTTONDOWN: |
| 885 case WM_LBUTTONUP: | 645 case WM_LBUTTONUP: |
| 886 case WM_RBUTTONDOWN: | 646 case WM_RBUTTONDOWN: |
| 887 case WM_RBUTTONUP: | 647 case WM_RBUTTONUP: |
| 888 case WM_MBUTTONDOWN: | 648 case WM_MBUTTONDOWN: |
| 889 case WM_MBUTTONUP: | 649 case WM_MBUTTONUP: |
| 890 case WM_KEYDOWN: | 650 case WM_KEYDOWN: |
| 891 case WM_KEYUP: | 651 case WM_KEYUP: |
| 892 return true; | 652 return true; |
| 893 | 653 |
| 894 default: | 654 default: |
| 895 break; | 655 break; |
| 896 } | 656 } |
| 897 | 657 |
| 898 return false; | 658 return false; |
| 899 } | 659 } |
| 900 | 660 |
| 901 LRESULT CALLBACK WebPluginDelegateImpl::NativeWndProc( | |
| 902 HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { | |
| 903 WebPluginDelegateImpl* delegate = reinterpret_cast<WebPluginDelegateImpl*>( | |
| 904 GetProp(hwnd, kWebPluginDelegateProperty)); | |
| 905 if (!delegate) { | |
| 906 NOTREACHED(); | |
| 907 return 0; | |
| 908 } | |
| 909 | |
| 910 if (message == delegate->last_message_ && | |
| 911 delegate->GetQuirks() & PLUGIN_QUIRK_DONT_CALL_WND_PROC_RECURSIVELY && | |
| 912 delegate->is_calling_wndproc) { | |
| 913 // Real may go into a state where it recursively dispatches the same event | |
| 914 // when subclassed. See https://bugzilla.mozilla.org/show_bug.cgi?id=192914 | |
| 915 // We only do the recursive check for Real because it's possible and valid | |
| 916 // for a plugin to synchronously dispatch a message to itself such that it | |
| 917 // looks like it's in recursion. | |
| 918 return TRUE; | |
| 919 } | |
| 920 | |
| 921 // Flash may flood the message queue with WM_USER+1 message causing 100% CPU | |
| 922 // usage. See https://bugzilla.mozilla.org/show_bug.cgi?id=132759. We | |
| 923 // prevent this by throttling the messages. | |
| 924 if (message == WM_USER + 1 && | |
| 925 delegate->GetQuirks() & PLUGIN_QUIRK_THROTTLE_WM_USER_PLUS_ONE) { | |
| 926 WebPluginDelegateImpl::ThrottleMessage(delegate->plugin_wnd_proc_, hwnd, | |
| 927 message, wparam, lparam); | |
| 928 return FALSE; | |
| 929 } | |
| 930 | |
| 931 LRESULT result; | |
| 932 uint32_t old_message = delegate->last_message_; | |
| 933 delegate->last_message_ = message; | |
| 934 | |
| 935 static UINT custom_msg = RegisterWindowMessage(kPaintMessageName); | |
| 936 if (message == custom_msg) { | |
| 937 // Get the invalid rect which is in screen coordinates and convert to | |
| 938 // window coordinates. | |
| 939 gfx::Rect invalid_rect; | |
| 940 invalid_rect.set_x(static_cast<short>(LOWORD(wparam))); | |
| 941 invalid_rect.set_y(static_cast<short>(HIWORD(wparam))); | |
| 942 invalid_rect.set_width(static_cast<short>(LOWORD(lparam))); | |
| 943 invalid_rect.set_height(static_cast<short>(HIWORD(lparam))); | |
| 944 | |
| 945 RECT window_rect; | |
| 946 GetWindowRect(hwnd, &window_rect); | |
| 947 invalid_rect.Offset(-window_rect.left, -window_rect.top); | |
| 948 | |
| 949 // The plugin window might have non-client area. If we don't pass in | |
| 950 // RDW_FRAME then the children don't receive WM_NCPAINT messages while | |
| 951 // scrolling, which causes painting problems (http://b/issue?id=923945). | |
| 952 uint32_t flags = RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_FRAME; | |
| 953 | |
| 954 // If a plugin (like Google Earth or Java) has child windows that are hosted | |
| 955 // in a different process, then RedrawWindow with UPDATENOW will | |
| 956 // synchronously wait for this call to complete. Some messages are pumped | |
| 957 // but not others, which could lead to a deadlock. So avoid reentrancy by | |
| 958 // only synchronously calling RedrawWindow once at a time. | |
| 959 if (old_message != custom_msg) | |
| 960 flags |= RDW_UPDATENOW; | |
| 961 RECT rect = invalid_rect.ToRECT(); | |
| 962 RedrawWindow(hwnd, &rect, NULL, flags); | |
| 963 result = FALSE; | |
| 964 } else { | |
| 965 delegate->is_calling_wndproc = true; | |
| 966 | |
| 967 if (!delegate->user_gesture_message_posted_ && | |
| 968 IsUserGestureMessage(message)) { | |
| 969 delegate->user_gesture_message_posted_ = true; | |
| 970 | |
| 971 delegate->instance()->PushPopupsEnabledState(true); | |
| 972 | |
| 973 base::MessageLoop::current()->PostDelayedTask( | |
| 974 FROM_HERE, | |
| 975 base::Bind(&WebPluginDelegateImpl::OnUserGestureEnd, | |
| 976 delegate->user_gesture_msg_factory_.GetWeakPtr()), | |
| 977 base::TimeDelta::FromMilliseconds(kWindowedPluginPopupTimerMs)); | |
| 978 } | |
| 979 | |
| 980 HandleCaptureForMessage(hwnd, message); | |
| 981 | |
| 982 // Maintain a local/global stack for the g_current_plugin_instance variable | |
| 983 // as this may be a nested invocation. | |
| 984 WebPluginDelegateImpl* last_plugin_instance = g_current_plugin_instance; | |
| 985 | |
| 986 g_current_plugin_instance = delegate; | |
| 987 | |
| 988 result = CallWindowProc( | |
| 989 delegate->plugin_wnd_proc_, hwnd, message, wparam, lparam); | |
| 990 | |
| 991 // The plugin instance may have been destroyed in the CallWindowProc call | |
| 992 // above. This will also destroy the plugin window. Before attempting to | |
| 993 // access the WebPluginDelegateImpl instance we validate if the window is | |
| 994 // still valid. | |
| 995 if (::IsWindow(hwnd)) | |
| 996 delegate->is_calling_wndproc = false; | |
| 997 | |
| 998 g_current_plugin_instance = last_plugin_instance; | |
| 999 | |
| 1000 if (message == WM_NCDESTROY) { | |
| 1001 RemoveProp(hwnd, kWebPluginDelegateProperty); | |
| 1002 ClearThrottleQueueForWindow(hwnd); | |
| 1003 } | |
| 1004 } | |
| 1005 if (::IsWindow(hwnd)) | |
| 1006 delegate->last_message_ = old_message; | |
| 1007 return result; | |
| 1008 } | |
| 1009 | |
| 1010 void WebPluginDelegateImpl::WindowlessUpdateGeometry( | 661 void WebPluginDelegateImpl::WindowlessUpdateGeometry( |
| 1011 const gfx::Rect& window_rect, | 662 const gfx::Rect& window_rect, |
| 1012 const gfx::Rect& clip_rect) { | 663 const gfx::Rect& clip_rect) { |
| 1013 bool window_rect_changed = (window_rect_ != window_rect); | 664 bool window_rect_changed = (window_rect_ != window_rect); |
| 1014 // Only resend to the instance if the geometry has changed. | 665 // Only resend to the instance if the geometry has changed. |
| 1015 if (!window_rect_changed && clip_rect == clip_rect_) | 666 if (!window_rect_changed && clip_rect == clip_rect_) |
| 1016 return; | 667 return; |
| 1017 | 668 |
| 1018 clip_rect_ = clip_rect; | 669 clip_rect_ = clip_rect; |
| 1019 window_rect_ = window_rect; | 670 window_rect_ = window_rect; |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1058 window_.window = old_dc; | 709 window_.window = old_dc; |
| 1059 } | 710 } |
| 1060 | 711 |
| 1061 void WebPluginDelegateImpl::WindowlessSetWindow() { | 712 void WebPluginDelegateImpl::WindowlessSetWindow() { |
| 1062 if (!instance()) | 713 if (!instance()) |
| 1063 return; | 714 return; |
| 1064 | 715 |
| 1065 if (window_rect_.IsEmpty()) // wait for geometry to be set. | 716 if (window_rect_.IsEmpty()) // wait for geometry to be set. |
| 1066 return; | 717 return; |
| 1067 | 718 |
| 1068 DCHECK(instance()->windowless()); | |
| 1069 | |
| 1070 window_.clipRect.top = clip_rect_.y(); | 719 window_.clipRect.top = clip_rect_.y(); |
| 1071 window_.clipRect.left = clip_rect_.x(); | 720 window_.clipRect.left = clip_rect_.x(); |
| 1072 window_.clipRect.bottom = clip_rect_.y() + clip_rect_.height(); | 721 window_.clipRect.bottom = clip_rect_.y() + clip_rect_.height(); |
| 1073 window_.clipRect.right = clip_rect_.x() + clip_rect_.width(); | 722 window_.clipRect.right = clip_rect_.x() + clip_rect_.width(); |
| 1074 window_.height = window_rect_.height(); | 723 window_.height = window_rect_.height(); |
| 1075 window_.width = window_rect_.width(); | 724 window_.width = window_rect_.width(); |
| 1076 window_.x = window_rect_.x(); | 725 window_.x = window_rect_.x(); |
| 1077 window_.y = window_rect_.y(); | 726 window_.y = window_rect_.y(); |
| 1078 window_.type = NPWindowTypeDrawable; | 727 window_.type = NPWindowTypeDrawable; |
| 1079 DrawableContextEnforcer enforcer(&window_); | 728 DrawableContextEnforcer enforcer(&window_); |
| 1080 | 729 |
| 1081 NPError err = instance()->NPP_SetWindow(&window_); | 730 NPError err = instance()->NPP_SetWindow(&window_); |
| 1082 DCHECK(err == NPERR_NO_ERROR); | 731 DCHECK(err == NPERR_NO_ERROR); |
| 1083 } | 732 } |
| 1084 | 733 |
| 1085 bool WebPluginDelegateImpl::PlatformSetPluginHasFocus(bool focused) { | 734 bool WebPluginDelegateImpl::PlatformSetPluginHasFocus(bool focused) { |
| 1086 DCHECK(instance()->windowless()); | |
| 1087 | |
| 1088 NPEvent focus_event; | 735 NPEvent focus_event; |
| 1089 focus_event.event = focused ? WM_SETFOCUS : WM_KILLFOCUS; | 736 focus_event.event = focused ? WM_SETFOCUS : WM_KILLFOCUS; |
| 1090 focus_event.wParam = 0; | 737 focus_event.wParam = 0; |
| 1091 focus_event.lParam = 0; | 738 focus_event.lParam = 0; |
| 1092 | 739 |
| 1093 instance()->NPP_HandleEvent(&focus_event); | 740 instance()->NPP_HandleEvent(&focus_event); |
| 1094 return true; | 741 return true; |
| 1095 } | 742 } |
| 1096 | 743 |
| 1097 static bool NPEventFromWebMouseEvent(const WebMouseEvent& event, | 744 static bool NPEventFromWebMouseEvent(const WebMouseEvent& event, |
| (...skipping 248 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1346 UnhookWindowsHookEx(handle_event_message_filter_hook_); | 993 UnhookWindowsHookEx(handle_event_message_filter_hook_); |
| 1347 handle_event_message_filter_hook_ = NULL; | 994 handle_event_message_filter_hook_ = NULL; |
| 1348 } | 995 } |
| 1349 | 996 |
| 1350 bool WebPluginDelegateImpl::ShouldTrackEventForModalLoops(NPEvent* event) { | 997 bool WebPluginDelegateImpl::ShouldTrackEventForModalLoops(NPEvent* event) { |
| 1351 if (event->event == WM_RBUTTONDOWN) | 998 if (event->event == WM_RBUTTONDOWN) |
| 1352 return true; | 999 return true; |
| 1353 return false; | 1000 return false; |
| 1354 } | 1001 } |
| 1355 | 1002 |
| 1356 void WebPluginDelegateImpl::OnUserGestureEnd() { | |
| 1357 user_gesture_message_posted_ = false; | |
| 1358 instance()->PopPopupsEnabledState(); | |
| 1359 } | |
| 1360 | |
| 1361 BOOL WINAPI WebPluginDelegateImpl::TrackPopupMenuPatch( | 1003 BOOL WINAPI WebPluginDelegateImpl::TrackPopupMenuPatch( |
| 1362 HMENU menu, unsigned int flags, int x, int y, int reserved, | 1004 HMENU menu, unsigned int flags, int x, int y, int reserved, |
| 1363 HWND window, const RECT* rect) { | 1005 HWND window, const RECT* rect) { |
| 1364 | 1006 |
| 1365 if (g_current_plugin_instance) { | 1007 if (g_current_plugin_instance) { |
| 1366 unsigned long window_process_id = 0; | 1008 unsigned long window_process_id = 0; |
| 1367 unsigned long window_thread_id = | 1009 unsigned long window_thread_id = |
| 1368 GetWindowThreadProcessId(window, &window_process_id); | 1010 GetWindowThreadProcessId(window, &window_process_id); |
| 1369 // TrackPopupMenu fails if the window passed in belongs to a different | 1011 // TrackPopupMenu fails if the window passed in belongs to a different |
| 1370 // thread. | 1012 // thread. |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1457 HWND window = WindowFromPoint(point); | 1099 HWND window = WindowFromPoint(point); |
| 1458 if (::ScreenToClient(window, &point)) { | 1100 if (::ScreenToClient(window, &point)) { |
| 1459 HWND child = ChildWindowFromPoint(window, point); | 1101 HWND child = ChildWindowFromPoint(window, point); |
| 1460 if (::IsWindow(child) && | 1102 if (::IsWindow(child) && |
| 1461 ::GetProp(child, content::kPluginDummyParentProperty)) | 1103 ::GetProp(child, content::kPluginDummyParentProperty)) |
| 1462 return child; | 1104 return child; |
| 1463 } | 1105 } |
| 1464 return window; | 1106 return window; |
| 1465 } | 1107 } |
| 1466 | 1108 |
| 1467 void WebPluginDelegateImpl::HandleCaptureForMessage(HWND window, | |
| 1468 UINT message) { | |
| 1469 if (gfx::GetClassName(window) != base::string16(kNativeWindowClassName)) | |
| 1470 return; | |
| 1471 | |
| 1472 switch (message) { | |
| 1473 case WM_LBUTTONDOWN: | |
| 1474 case WM_MBUTTONDOWN: | |
| 1475 case WM_RBUTTONDOWN: | |
| 1476 ::SetCapture(window); | |
| 1477 // As per documentation the WM_PARENTNOTIFY message is sent to the parent | |
| 1478 // window chain if mouse input is received by the child window. However | |
| 1479 // the parent receives the WM_PARENTNOTIFY message only if we doubleclick | |
| 1480 // on the window. We send the WM_PARENTNOTIFY message for mouse input | |
| 1481 // messages to the parent to indicate that user action is expected. | |
| 1482 ::SendMessage(::GetParent(window), WM_PARENTNOTIFY, message, 0); | |
| 1483 break; | |
| 1484 | |
| 1485 case WM_LBUTTONUP: | |
| 1486 case WM_MBUTTONUP: | |
| 1487 case WM_RBUTTONUP: | |
| 1488 ::ReleaseCapture(); | |
| 1489 break; | |
| 1490 | |
| 1491 default: | |
| 1492 break; | |
| 1493 } | |
| 1494 } | |
| 1495 | |
| 1496 } // namespace content | 1109 } // namespace content |
| OLD | NEW |