OLD | NEW |
| (Empty) |
1 // Copyright (c) 2010 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 "webkit/glue/plugins/webplugin_delegate_impl.h" | |
6 | |
7 #include <map> | |
8 #include <string> | |
9 #include <vector> | |
10 | |
11 #include "app/win/iat_patch_function.h" | |
12 #include "base/file_util.h" | |
13 #include "base/lazy_instance.h" | |
14 #include "base/message_loop.h" | |
15 #include "base/metrics/stats_counters.h" | |
16 #include "base/scoped_ptr.h" | |
17 #include "base/string_number_conversions.h" | |
18 #include "base/string_split.h" | |
19 #include "base/string_util.h" | |
20 #include "base/stringprintf.h" | |
21 #include "base/win/registry.h" | |
22 #include "base/win/windows_version.h" | |
23 #include "skia/ext/platform_canvas.h" | |
24 #include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" | |
25 #include "webkit/glue/plugins/default_plugin_shared.h" | |
26 #include "webkit/glue/plugins/plugin_constants_win.h" | |
27 #include "webkit/glue/plugins/plugin_instance.h" | |
28 #include "webkit/glue/plugins/plugin_lib.h" | |
29 #include "webkit/glue/plugins/plugin_list.h" | |
30 #include "webkit/glue/plugins/plugin_stream_url.h" | |
31 #include "webkit/glue/plugins/webplugin.h" | |
32 #include "webkit/glue/webkit_glue.h" | |
33 | |
34 using WebKit::WebCursorInfo; | |
35 using WebKit::WebKeyboardEvent; | |
36 using WebKit::WebInputEvent; | |
37 using WebKit::WebMouseEvent; | |
38 | |
39 namespace { | |
40 | |
41 const wchar_t kWebPluginDelegateProperty[] = L"WebPluginDelegateProperty"; | |
42 const wchar_t kPluginNameAtomProperty[] = L"PluginNameAtom"; | |
43 const wchar_t kDummyActivationWindowName[] = L"DummyWindowForActivation"; | |
44 const wchar_t kPluginFlashThrottle[] = L"FlashThrottle"; | |
45 | |
46 // The fastest we are willing to process WM_USER+1 events for Flash. | |
47 // Flash can easily exceed the limits of our CPU if we don't throttle it. | |
48 // The throttle has been chosen by testing various delays and compromising | |
49 // on acceptable Flash performance and reasonable CPU consumption. | |
50 // | |
51 // I'd like to make the throttle delay variable, based on the amount of | |
52 // time currently required to paint Flash plugins. There isn't a good | |
53 // way to count the time spent in aggregate plugin painting, however, so | |
54 // this seems to work well enough. | |
55 const int kFlashWMUSERMessageThrottleDelayMs = 5; | |
56 | |
57 // Flash displays popups in response to user clicks by posting a WM_USER | |
58 // message to the plugin window. The handler for this message displays | |
59 // the popup. To ensure that the popups allowed state is sent correctly | |
60 // to the renderer we reset the popups allowed state in a timer. | |
61 const int kWindowedPluginPopupTimerMs = 50; | |
62 | |
63 // The current instance of the plugin which entered the modal loop. | |
64 WebPluginDelegateImpl* g_current_plugin_instance = NULL; | |
65 | |
66 typedef std::deque<MSG> ThrottleQueue; | |
67 base::LazyInstance<ThrottleQueue> g_throttle_queue(base::LINKER_INITIALIZED); | |
68 base::LazyInstance<std::map<HWND, WNDPROC> > g_window_handle_proc_map( | |
69 base::LINKER_INITIALIZED); | |
70 | |
71 | |
72 // Helper object for patching the TrackPopupMenu API. | |
73 base::LazyInstance<app::win::IATPatchFunction> g_iat_patch_track_popup_menu( | |
74 base::LINKER_INITIALIZED); | |
75 | |
76 // Helper object for patching the SetCursor API. | |
77 base::LazyInstance<app::win::IATPatchFunction> g_iat_patch_set_cursor( | |
78 base::LINKER_INITIALIZED); | |
79 | |
80 // Helper object for patching the RegEnumKeyExW API. | |
81 base::LazyInstance<app::win::IATPatchFunction> g_iat_patch_reg_enum_key_ex_w( | |
82 base::LINKER_INITIALIZED); | |
83 | |
84 // http://crbug.com/16114 | |
85 // Enforces providing a valid device context in NPWindow, so that NPP_SetWindow | |
86 // is never called with NPNWindoTypeDrawable and NPWindow set to NULL. | |
87 // Doing so allows removing NPP_SetWindow call during painting a windowless | |
88 // plugin, which otherwise could trigger layout change while painting by | |
89 // invoking NPN_Evaluate. Which would cause bad, bad crashes. Bad crashes. | |
90 // TODO(dglazkov): If this approach doesn't produce regressions, move class to | |
91 // webplugin_delegate_impl.h and implement for other platforms. | |
92 class DrawableContextEnforcer { | |
93 public: | |
94 explicit DrawableContextEnforcer(NPWindow* window) | |
95 : window_(window), | |
96 disposable_dc_(window && !window->window) { | |
97 // If NPWindow is NULL, create a device context with monochrome 1x1 surface | |
98 // and stuff it to NPWindow. | |
99 if (disposable_dc_) | |
100 window_->window = CreateCompatibleDC(NULL); | |
101 } | |
102 | |
103 ~DrawableContextEnforcer() { | |
104 if (!disposable_dc_) | |
105 return; | |
106 | |
107 DeleteDC(static_cast<HDC>(window_->window)); | |
108 window_->window = NULL; | |
109 } | |
110 | |
111 private: | |
112 NPWindow* window_; | |
113 bool disposable_dc_; | |
114 }; | |
115 | |
116 // These are from ntddk.h | |
117 typedef LONG NTSTATUS; | |
118 | |
119 #ifndef STATUS_SUCCESS | |
120 #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) | |
121 #endif | |
122 | |
123 #ifndef STATUS_BUFFER_TOO_SMALL | |
124 #define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) | |
125 #endif | |
126 | |
127 typedef enum _KEY_INFORMATION_CLASS { | |
128 KeyBasicInformation, | |
129 KeyNodeInformation, | |
130 KeyFullInformation, | |
131 KeyNameInformation, | |
132 KeyCachedInformation, | |
133 KeyVirtualizationInformation | |
134 } KEY_INFORMATION_CLASS; | |
135 | |
136 typedef struct _KEY_NAME_INFORMATION { | |
137 ULONG NameLength; | |
138 WCHAR Name[1]; | |
139 } KEY_NAME_INFORMATION, *PKEY_NAME_INFORMATION; | |
140 | |
141 typedef DWORD (__stdcall *ZwQueryKeyType)( | |
142 HANDLE key_handle, | |
143 int key_information_class, | |
144 PVOID key_information, | |
145 ULONG length, | |
146 PULONG result_length); | |
147 | |
148 // Returns a key's full path. | |
149 std::wstring GetKeyPath(HKEY key) { | |
150 if (key == NULL) | |
151 return L""; | |
152 | |
153 HMODULE dll = GetModuleHandle(L"ntdll.dll"); | |
154 if (dll == NULL) | |
155 return L""; | |
156 | |
157 ZwQueryKeyType func = reinterpret_cast<ZwQueryKeyType>( | |
158 ::GetProcAddress(dll, "ZwQueryKey")); | |
159 if (func == NULL) | |
160 return L""; | |
161 | |
162 DWORD size = 0; | |
163 DWORD result = 0; | |
164 result = func(key, KeyNameInformation, 0, 0, &size); | |
165 if (result != STATUS_BUFFER_TOO_SMALL) | |
166 return L""; | |
167 | |
168 scoped_array<char> buffer(new char[size]); | |
169 if (buffer.get() == NULL) | |
170 return L""; | |
171 | |
172 result = func(key, KeyNameInformation, buffer.get(), size, &size); | |
173 if (result != STATUS_SUCCESS) | |
174 return L""; | |
175 | |
176 KEY_NAME_INFORMATION* info = | |
177 reinterpret_cast<KEY_NAME_INFORMATION*>(buffer.get()); | |
178 return std::wstring(info->Name, info->NameLength / sizeof(wchar_t)); | |
179 } | |
180 | |
181 } // namespace | |
182 | |
183 bool WebPluginDelegateImpl::IsPluginDelegateWindow(HWND window) { | |
184 // We use a buffer that is one char longer than we need to detect cases where | |
185 // kNativeWindowClassName is a prefix of the given window's class name. It | |
186 // happens that GetClassNameW will just silently truncate the class name to | |
187 // fit into the given buffer. | |
188 wchar_t class_name[arraysize(kNativeWindowClassName) + 1]; | |
189 if (!GetClassNameW(window, class_name, arraysize(class_name))) | |
190 return false; | |
191 return wcscmp(class_name, kNativeWindowClassName) == 0; | |
192 } | |
193 | |
194 bool WebPluginDelegateImpl::GetPluginNameFromWindow( | |
195 HWND window, std::wstring *plugin_name) { | |
196 if (NULL == plugin_name) { | |
197 return false; | |
198 } | |
199 if (!IsPluginDelegateWindow(window)) { | |
200 return false; | |
201 } | |
202 ATOM plugin_name_atom = reinterpret_cast<ATOM>( | |
203 GetPropW(window, kPluginNameAtomProperty)); | |
204 if (plugin_name_atom != 0) { | |
205 WCHAR plugin_name_local[MAX_PATH] = {0}; | |
206 GlobalGetAtomNameW(plugin_name_atom, | |
207 plugin_name_local, | |
208 ARRAYSIZE(plugin_name_local)); | |
209 *plugin_name = plugin_name_local; | |
210 return true; | |
211 } | |
212 return false; | |
213 } | |
214 | |
215 bool WebPluginDelegateImpl::IsDummyActivationWindow(HWND window) { | |
216 if (!IsWindow(window)) | |
217 return false; | |
218 | |
219 wchar_t window_title[MAX_PATH + 1] = {0}; | |
220 if (GetWindowText(window, window_title, arraysize(window_title))) { | |
221 return (0 == lstrcmpiW(window_title, kDummyActivationWindowName)); | |
222 } | |
223 return false; | |
224 } | |
225 | |
226 LRESULT CALLBACK WebPluginDelegateImpl::HandleEventMessageFilterHook( | |
227 int code, WPARAM wParam, LPARAM lParam) { | |
228 if (g_current_plugin_instance) { | |
229 g_current_plugin_instance->OnModalLoopEntered(); | |
230 } else { | |
231 NOTREACHED(); | |
232 } | |
233 return CallNextHookEx(NULL, code, wParam, lParam); | |
234 } | |
235 | |
236 LRESULT CALLBACK WebPluginDelegateImpl::MouseHookProc( | |
237 int code, WPARAM wParam, LPARAM lParam) { | |
238 if (code == HC_ACTION) { | |
239 MOUSEHOOKSTRUCT* hook_struct = reinterpret_cast<MOUSEHOOKSTRUCT*>(lParam); | |
240 if (hook_struct) | |
241 HandleCaptureForMessage(hook_struct->hwnd, wParam); | |
242 } | |
243 | |
244 return CallNextHookEx(NULL, code, wParam, lParam); | |
245 } | |
246 | |
247 WebPluginDelegateImpl::WebPluginDelegateImpl( | |
248 gfx::PluginWindowHandle containing_view, | |
249 NPAPI::PluginInstance *instance) | |
250 : parent_(containing_view), | |
251 instance_(instance), | |
252 quirks_(0), | |
253 plugin_(NULL), | |
254 windowless_(false), | |
255 windowed_handle_(NULL), | |
256 windowed_did_set_window_(false), | |
257 plugin_wnd_proc_(NULL), | |
258 last_message_(0), | |
259 is_calling_wndproc(false), | |
260 keyboard_layout_(NULL), | |
261 parent_thread_id_(0), | |
262 dummy_window_for_activation_(NULL), | |
263 handle_event_message_filter_hook_(NULL), | |
264 handle_event_pump_messages_event_(NULL), | |
265 user_gesture_message_posted_(false), | |
266 #pragma warning(suppress: 4355) // can use this | |
267 user_gesture_msg_factory_(this), | |
268 handle_event_depth_(0), | |
269 mouse_hook_(NULL), | |
270 first_set_window_call_(true), | |
271 plugin_has_focus_(false), | |
272 has_webkit_focus_(false), | |
273 containing_view_has_focus_(true), | |
274 creation_succeeded_(false) { | |
275 memset(&window_, 0, sizeof(window_)); | |
276 | |
277 const WebPluginInfo& plugin_info = instance_->plugin_lib()->plugin_info(); | |
278 std::wstring filename = | |
279 StringToLowerASCII(plugin_info.path.BaseName().value()); | |
280 | |
281 if (instance_->mime_type() == "application/x-shockwave-flash" || | |
282 filename == kFlashPlugin) { | |
283 // Flash only requests windowless plugins if we return a Mozilla user | |
284 // agent. | |
285 instance_->set_use_mozilla_user_agent(); | |
286 quirks_ |= PLUGIN_QUIRK_THROTTLE_WM_USER_PLUS_ONE; | |
287 quirks_ |= PLUGIN_QUIRK_PATCH_SETCURSOR; | |
288 quirks_ |= PLUGIN_QUIRK_ALWAYS_NOTIFY_SUCCESS; | |
289 quirks_ |= PLUGIN_QUIRK_HANDLE_MOUSE_CAPTURE; | |
290 } else if (filename == kAcrobatReaderPlugin) { | |
291 // Check for the version number above or equal 9. | |
292 std::vector<std::wstring> version; | |
293 base::SplitString(plugin_info.version, L'.', &version); | |
294 if (version.size() > 0) { | |
295 int major; | |
296 base::StringToInt(version[0], &major); | |
297 if (major >= 9) { | |
298 quirks_ |= PLUGIN_QUIRK_DIE_AFTER_UNLOAD; | |
299 | |
300 // 9.2 needs this. | |
301 quirks_ |= PLUGIN_QUIRK_SETWINDOW_TWICE; | |
302 } | |
303 } | |
304 quirks_ |= PLUGIN_QUIRK_BLOCK_NONSTANDARD_GETURL_REQUESTS; | |
305 } else if (plugin_info.name.find(L"Windows Media Player") != | |
306 std::wstring::npos) { | |
307 // Windows Media Player needs two NPP_SetWindow calls. | |
308 quirks_ |= PLUGIN_QUIRK_SETWINDOW_TWICE; | |
309 | |
310 // Windowless mode doesn't work in the WMP NPAPI plugin. | |
311 quirks_ |= PLUGIN_QUIRK_NO_WINDOWLESS; | |
312 | |
313 // The media player plugin sets its size on the first NPP_SetWindow call | |
314 // and never updates its size. We should call the underlying NPP_SetWindow | |
315 // only when we have the correct size. | |
316 quirks_ |= PLUGIN_QUIRK_IGNORE_FIRST_SETWINDOW_CALL; | |
317 | |
318 if (filename == kOldWMPPlugin) { | |
319 // Non-admin users on XP couldn't modify the key to force the new UI. | |
320 quirks_ |= PLUGIN_QUIRK_PATCH_REGENUMKEYEXW; | |
321 } | |
322 } else if (instance_->mime_type() == "audio/x-pn-realaudio-plugin" || | |
323 filename == kRealPlayerPlugin) { | |
324 quirks_ |= PLUGIN_QUIRK_DONT_CALL_WND_PROC_RECURSIVELY; | |
325 } else if (plugin_info.name.find(L"VLC Multimedia Plugin") != | |
326 std::wstring::npos || | |
327 plugin_info.name.find(L"VLC Multimedia Plug-in") != | |
328 std::wstring::npos) { | |
329 // VLC hangs on NPP_Destroy if we call NPP_SetWindow with a null window | |
330 // handle | |
331 quirks_ |= PLUGIN_QUIRK_DONT_SET_NULL_WINDOW_HANDLE_ON_DESTROY; | |
332 // VLC 0.8.6d and 0.8.6e crash if multiple instances are created. | |
333 quirks_ |= PLUGIN_QUIRK_DONT_ALLOW_MULTIPLE_INSTANCES; | |
334 } else if (filename == kSilverlightPlugin) { | |
335 // Explanation for this quirk can be found in | |
336 // WebPluginDelegateImpl::Initialize. | |
337 quirks_ |= PLUGIN_QUIRK_PATCH_SETCURSOR; | |
338 } else if (plugin_info.name.find(L"DivX Web Player") != | |
339 std::wstring::npos) { | |
340 // The divx plugin sets its size on the first NPP_SetWindow call and never | |
341 // updates its size. We should call the underlying NPP_SetWindow only when | |
342 // we have the correct size. | |
343 quirks_ |= PLUGIN_QUIRK_IGNORE_FIRST_SETWINDOW_CALL; | |
344 } | |
345 } | |
346 | |
347 WebPluginDelegateImpl::~WebPluginDelegateImpl() { | |
348 if (::IsWindow(dummy_window_for_activation_)) { | |
349 ::DestroyWindow(dummy_window_for_activation_); | |
350 } | |
351 | |
352 DestroyInstance(); | |
353 | |
354 if (!windowless_) | |
355 WindowedDestroyWindow(); | |
356 | |
357 if (handle_event_pump_messages_event_) { | |
358 CloseHandle(handle_event_pump_messages_event_); | |
359 } | |
360 } | |
361 | |
362 bool WebPluginDelegateImpl::PlatformInitialize() { | |
363 plugin_->SetWindow(windowed_handle_); | |
364 | |
365 if (windowless_ && !instance_->plugin_lib()->internal()) { | |
366 CreateDummyWindowForActivation(); | |
367 handle_event_pump_messages_event_ = CreateEvent(NULL, TRUE, FALSE, NULL); | |
368 plugin_->SetWindowlessPumpEvent(handle_event_pump_messages_event_); | |
369 } | |
370 | |
371 // We cannot patch internal plugins as they are not shared libraries. | |
372 if (!instance_->plugin_lib()->internal()) { | |
373 // Windowless plugins call the WindowFromPoint API and passes the result of | |
374 // that to the TrackPopupMenu API call as the owner window. This causes the | |
375 // API to fail as the API expects the window handle to live on the same | |
376 // thread as the caller. It works in the other browsers as the plugin lives | |
377 // on the browser thread. Our workaround is to intercept the TrackPopupMenu | |
378 // API and replace the window handle with the dummy activation window. | |
379 if (windowless_ && !g_iat_patch_track_popup_menu.Pointer()->is_patched()) { | |
380 g_iat_patch_track_popup_menu.Pointer()->Patch( | |
381 GetPluginPath().value().c_str(), "user32.dll", "TrackPopupMenu", | |
382 WebPluginDelegateImpl::TrackPopupMenuPatch); | |
383 } | |
384 | |
385 // Windowless plugins can set cursors by calling the SetCursor API. This | |
386 // works because the thread inputs of the browser UI thread and the plugin | |
387 // thread are attached. We intercept the SetCursor API for windowless | |
388 // plugins and remember the cursor being set. This is shipped over to the | |
389 // browser in the HandleEvent call, which ensures that the cursor does not | |
390 // change when a windowless plugin instance changes the cursor | |
391 // in a background tab. | |
392 if (windowless_ && !g_iat_patch_set_cursor.Pointer()->is_patched() && | |
393 (quirks_ & PLUGIN_QUIRK_PATCH_SETCURSOR)) { | |
394 g_iat_patch_set_cursor.Pointer()->Patch( | |
395 GetPluginPath().value().c_str(), "user32.dll", "SetCursor", | |
396 WebPluginDelegateImpl::SetCursorPatch); | |
397 } | |
398 | |
399 // The windowed flash plugin has a bug which occurs when the plugin enters | |
400 // fullscreen mode. It basically captures the mouse on WM_LBUTTONDOWN and | |
401 // does not release capture correctly causing it to stop receiving | |
402 // subsequent mouse events. This problem is also seen in Safari where there | |
403 // is code to handle this in the wndproc. However the plugin subclasses the | |
404 // window again in WM_LBUTTONDOWN before entering full screen. As a result | |
405 // Safari does not receive the WM_LBUTTONUP message. To workaround this | |
406 // issue we use a per thread mouse hook. This bug does not occur in Firefox | |
407 // and opera. Firefox has code similar to Safari. It could well be a bug in | |
408 // the flash plugin, which only occurs in webkit based browsers. | |
409 if (quirks_ & PLUGIN_QUIRK_HANDLE_MOUSE_CAPTURE) { | |
410 mouse_hook_ = SetWindowsHookEx(WH_MOUSE, MouseHookProc, NULL, | |
411 GetCurrentThreadId()); | |
412 } | |
413 } | |
414 | |
415 // On XP, WMP will use its old UI unless a registry key under HKLM has the | |
416 // name of the current process. We do it in the installer for admin users, | |
417 // for the rest patch this function. | |
418 if ((quirks_ & PLUGIN_QUIRK_PATCH_REGENUMKEYEXW) && | |
419 base::win::GetVersion() == base::win::VERSION_XP && | |
420 !base::win::RegKey().Open(HKEY_LOCAL_MACHINE, | |
421 L"SOFTWARE\\Microsoft\\MediaPlayer\\ShimInclusionList\\chrome.exe", | |
422 KEY_READ) && | |
423 !g_iat_patch_reg_enum_key_ex_w.Pointer()->is_patched()) { | |
424 g_iat_patch_reg_enum_key_ex_w.Pointer()->Patch( | |
425 L"wmpdxm.dll", "advapi32.dll", "RegEnumKeyExW", | |
426 WebPluginDelegateImpl::RegEnumKeyExWPatch); | |
427 } | |
428 | |
429 return true; | |
430 } | |
431 | |
432 void WebPluginDelegateImpl::PlatformDestroyInstance() { | |
433 if (!instance_->plugin_lib()) | |
434 return; | |
435 | |
436 // Unpatch if this is the last plugin instance. | |
437 if (instance_->plugin_lib()->instance_count() != 1) | |
438 return; | |
439 | |
440 if (g_iat_patch_set_cursor.Pointer()->is_patched()) | |
441 g_iat_patch_set_cursor.Pointer()->Unpatch(); | |
442 | |
443 if (g_iat_patch_track_popup_menu.Pointer()->is_patched()) | |
444 g_iat_patch_track_popup_menu.Pointer()->Unpatch(); | |
445 | |
446 if (g_iat_patch_reg_enum_key_ex_w.Pointer()->is_patched()) | |
447 g_iat_patch_reg_enum_key_ex_w.Pointer()->Unpatch(); | |
448 | |
449 if (mouse_hook_) { | |
450 UnhookWindowsHookEx(mouse_hook_); | |
451 mouse_hook_ = NULL; | |
452 } | |
453 } | |
454 | |
455 void WebPluginDelegateImpl::Paint(skia::PlatformCanvas* canvas, | |
456 const gfx::Rect& rect) { | |
457 if (windowless_) { | |
458 HDC hdc = canvas->beginPlatformPaint(); | |
459 WindowlessPaint(hdc, rect); | |
460 canvas->endPlatformPaint(); | |
461 } | |
462 } | |
463 | |
464 void WebPluginDelegateImpl::Print(HDC hdc) { | |
465 // Disabling the call to NPP_Print as it causes a crash in | |
466 // flash in some cases. In any case this does not work as expected | |
467 // as the EMF meta file dc passed in needs to be created with the | |
468 // the plugin window dc as its sibling dc and the window rect | |
469 // in .01 mm units. | |
470 #if 0 | |
471 NPPrint npprint; | |
472 npprint.mode = NP_EMBED; | |
473 npprint.print.embedPrint.platformPrint = reinterpret_cast<void*>(hdc); | |
474 npprint.print.embedPrint.window = window_; | |
475 instance()->NPP_Print(&npprint); | |
476 #endif | |
477 } | |
478 | |
479 void WebPluginDelegateImpl::InstallMissingPlugin() { | |
480 NPEvent evt; | |
481 evt.event = default_plugin::kInstallMissingPluginMessage; | |
482 evt.lParam = 0; | |
483 evt.wParam = 0; | |
484 instance()->NPP_HandleEvent(&evt); | |
485 } | |
486 | |
487 bool WebPluginDelegateImpl::WindowedCreatePlugin() { | |
488 DCHECK(!windowed_handle_); | |
489 | |
490 RegisterNativeWindowClass(); | |
491 | |
492 // The window will be sized and shown later. | |
493 windowed_handle_ = CreateWindowEx( | |
494 WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR, | |
495 kNativeWindowClassName, | |
496 0, | |
497 WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, | |
498 0, | |
499 0, | |
500 0, | |
501 0, | |
502 parent_, | |
503 0, | |
504 GetModuleHandle(NULL), | |
505 0); | |
506 if (windowed_handle_ == 0) | |
507 return false; | |
508 | |
509 if (IsWindow(parent_)) { | |
510 // This is a tricky workaround for Issue 2673 in chromium "Flash: IME not | |
511 // available". To use IMEs in this window, we have to make Windows attach | |
512 // IMEs to this window (i.e. load IME DLLs, attach them to this process, | |
513 // and add their message hooks to this window). Windows attaches IMEs while | |
514 // this process creates a top-level window. On the other hand, to layout | |
515 // this window correctly in the given parent window (RenderWidgetHostHWND), | |
516 // this window should be a child window of the parent window. | |
517 // To satisfy both of the above conditions, this code once creates a | |
518 // top-level window and change it to a child window of the parent window. | |
519 SetWindowLongPtr(windowed_handle_, GWL_STYLE, | |
520 WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS); | |
521 SetParent(windowed_handle_, parent_); | |
522 } | |
523 | |
524 BOOL result = SetProp(windowed_handle_, kWebPluginDelegateProperty, this); | |
525 DCHECK(result == TRUE) << "SetProp failed, last error = " << GetLastError(); | |
526 // Get the name of the plugin, create an atom and set that in a window | |
527 // property. Use an atom so that other processes can access the name of | |
528 // the plugin that this window is hosting | |
529 if (instance_ != NULL) { | |
530 NPAPI::PluginLib* plugin_lib = instance()->plugin_lib(); | |
531 if (plugin_lib != NULL) { | |
532 std::wstring plugin_name = plugin_lib->plugin_info().name; | |
533 if (!plugin_name.empty()) { | |
534 ATOM plugin_name_atom = GlobalAddAtomW(plugin_name.c_str()); | |
535 DCHECK(0 != plugin_name_atom); | |
536 result = SetProp(windowed_handle_, | |
537 kPluginNameAtomProperty, | |
538 reinterpret_cast<HANDLE>(plugin_name_atom)); | |
539 DCHECK(result == TRUE) << "SetProp failed, last error = " << | |
540 GetLastError(); | |
541 } | |
542 } | |
543 } | |
544 | |
545 // Calling SetWindowLongPtrA here makes the window proc ASCII, which is | |
546 // required by at least the Shockwave Director plug-in. | |
547 SetWindowLongPtrA( | |
548 windowed_handle_, GWL_WNDPROC, reinterpret_cast<LONG>(DefWindowProcA)); | |
549 | |
550 return true; | |
551 } | |
552 | |
553 void WebPluginDelegateImpl::WindowedDestroyWindow() { | |
554 if (windowed_handle_ != NULL) { | |
555 // Unsubclass the window. | |
556 WNDPROC current_wnd_proc = reinterpret_cast<WNDPROC>( | |
557 GetWindowLongPtr(windowed_handle_, GWLP_WNDPROC)); | |
558 if (current_wnd_proc == NativeWndProc) { | |
559 SetWindowLongPtr(windowed_handle_, | |
560 GWLP_WNDPROC, | |
561 reinterpret_cast<LONG>(plugin_wnd_proc_)); | |
562 } | |
563 | |
564 plugin_->WillDestroyWindow(windowed_handle_); | |
565 | |
566 DestroyWindow(windowed_handle_); | |
567 windowed_handle_ = 0; | |
568 } | |
569 } | |
570 | |
571 // Erase all messages in the queue destined for a particular window. | |
572 // When windows are closing, callers should use this function to clear | |
573 // the queue. | |
574 // static | |
575 void WebPluginDelegateImpl::ClearThrottleQueueForWindow(HWND window) { | |
576 ThrottleQueue* throttle_queue = g_throttle_queue.Pointer(); | |
577 | |
578 ThrottleQueue::iterator it; | |
579 for (it = throttle_queue->begin(); it != throttle_queue->end(); ) { | |
580 if (it->hwnd == window) { | |
581 it = throttle_queue->erase(it); | |
582 } else { | |
583 it++; | |
584 } | |
585 } | |
586 } | |
587 | |
588 // Delayed callback for processing throttled messages. | |
589 // Throttled messages are aggregated globally across all plugins. | |
590 // static | |
591 void WebPluginDelegateImpl::OnThrottleMessage() { | |
592 // The current algorithm walks the list and processes the first | |
593 // message it finds for each plugin. It is important to service | |
594 // all active plugins with each pass through the throttle, otherwise | |
595 // we see video jankiness. Copy the set to notify before notifying | |
596 // since we may re-enter OnThrottleMessage from CallWindowProc! | |
597 ThrottleQueue* throttle_queue = g_throttle_queue.Pointer(); | |
598 ThrottleQueue notify_queue; | |
599 std::set<HWND> processed; | |
600 | |
601 ThrottleQueue::iterator it = throttle_queue->begin(); | |
602 while (it != throttle_queue->end()) { | |
603 const MSG& msg = *it; | |
604 if (processed.find(msg.hwnd) == processed.end()) { | |
605 processed.insert(msg.hwnd); | |
606 notify_queue.push_back(msg); | |
607 it = throttle_queue->erase(it); | |
608 } else { | |
609 it++; | |
610 } | |
611 } | |
612 | |
613 for (it = notify_queue.begin(); it != notify_queue.end(); ++it) { | |
614 const MSG& msg = *it; | |
615 WNDPROC proc = reinterpret_cast<WNDPROC>(msg.time); | |
616 // It is possible that the window was closed after we queued | |
617 // this message. This is a rare event; just verify the window | |
618 // is alive. (see also bug 1259488) | |
619 if (IsWindow(msg.hwnd)) | |
620 CallWindowProc(proc, msg.hwnd, msg.message, msg.wParam, msg.lParam); | |
621 } | |
622 | |
623 if (!throttle_queue->empty()) { | |
624 MessageLoop::current()->PostDelayedTask(FROM_HERE, | |
625 NewRunnableFunction(&WebPluginDelegateImpl::OnThrottleMessage), | |
626 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 MessageLoop::current()->PostDelayedTask(FROM_HERE, | |
648 NewRunnableFunction(&WebPluginDelegateImpl::OnThrottleMessage), | |
649 kFlashWMUSERMessageThrottleDelayMs); | |
650 } | |
651 } | |
652 | |
653 // We go out of our way to find the hidden windows created by Flash for | |
654 // windowless plugins. We throttle the rate at which they deliver messages | |
655 // so that they will not consume outrageous amounts of CPU. | |
656 // static | |
657 LRESULT CALLBACK WebPluginDelegateImpl::FlashWindowlessWndProc(HWND hwnd, | |
658 UINT message, WPARAM wparam, LPARAM lparam) { | |
659 std::map<HWND, WNDPROC>::iterator index = | |
660 g_window_handle_proc_map.Get().find(hwnd); | |
661 | |
662 WNDPROC old_proc = (*index).second; | |
663 DCHECK(old_proc); | |
664 | |
665 switch (message) { | |
666 case WM_NCDESTROY: { | |
667 WebPluginDelegateImpl::ClearThrottleQueueForWindow(hwnd); | |
668 g_window_handle_proc_map.Get().erase(index); | |
669 break; | |
670 } | |
671 // Flash may flood the message queue with WM_USER+1 message causing 100% CPU | |
672 // usage. See https://bugzilla.mozilla.org/show_bug.cgi?id=132759. We | |
673 // prevent this by throttling the messages. | |
674 case WM_USER + 1: { | |
675 WebPluginDelegateImpl::ThrottleMessage(old_proc, hwnd, message, wparam, | |
676 lparam); | |
677 return TRUE; | |
678 } | |
679 default: { | |
680 break; | |
681 } | |
682 } | |
683 return CallWindowProc(old_proc, hwnd, message, wparam, lparam); | |
684 } | |
685 | |
686 // Callback for enumerating the Flash windows. | |
687 BOOL CALLBACK EnumFlashWindows(HWND window, LPARAM arg) { | |
688 WNDPROC wnd_proc = reinterpret_cast<WNDPROC>(arg); | |
689 TCHAR class_name[1024]; | |
690 if (!RealGetWindowClass(window, class_name, | |
691 sizeof(class_name)/sizeof(TCHAR))) { | |
692 LOG(ERROR) << "RealGetWindowClass failure: " << GetLastError(); | |
693 return FALSE; | |
694 } | |
695 | |
696 if (wcscmp(class_name, L"SWFlash_PlaceholderX")) | |
697 return TRUE; | |
698 | |
699 WNDPROC current_wnd_proc = reinterpret_cast<WNDPROC>( | |
700 GetWindowLongPtr(window, GWLP_WNDPROC)); | |
701 if (current_wnd_proc != wnd_proc) { | |
702 WNDPROC old_flash_proc = reinterpret_cast<WNDPROC>(SetWindowLongPtr( | |
703 window, GWLP_WNDPROC, | |
704 reinterpret_cast<LONG>(wnd_proc))); | |
705 DCHECK(old_flash_proc); | |
706 g_window_handle_proc_map.Get()[window] = old_flash_proc; | |
707 } | |
708 | |
709 return TRUE; | |
710 } | |
711 | |
712 bool WebPluginDelegateImpl::CreateDummyWindowForActivation() { | |
713 DCHECK(!dummy_window_for_activation_); | |
714 dummy_window_for_activation_ = CreateWindowEx( | |
715 0, | |
716 L"Static", | |
717 kDummyActivationWindowName, | |
718 WS_CHILD, | |
719 0, | |
720 0, | |
721 0, | |
722 0, | |
723 parent_, | |
724 0, | |
725 GetModuleHandle(NULL), | |
726 0); | |
727 | |
728 if (dummy_window_for_activation_ == 0) | |
729 return false; | |
730 | |
731 // Flash creates background windows which use excessive CPU in our | |
732 // environment; we wrap these windows and throttle them so that they don't | |
733 // get out of hand. | |
734 if (!EnumThreadWindows(::GetCurrentThreadId(), EnumFlashWindows, | |
735 reinterpret_cast<LPARAM>( | |
736 &WebPluginDelegateImpl::FlashWindowlessWndProc))) { | |
737 // Log that this happened. Flash will still work; it just means the | |
738 // throttle isn't installed (and Flash will use more CPU). | |
739 NOTREACHED(); | |
740 LOG(ERROR) << "Failed to wrap all windowless Flash windows"; | |
741 } | |
742 return true; | |
743 } | |
744 | |
745 bool WebPluginDelegateImpl::WindowedReposition( | |
746 const gfx::Rect& window_rect, | |
747 const gfx::Rect& clip_rect) { | |
748 if (!windowed_handle_) { | |
749 NOTREACHED(); | |
750 return false; | |
751 } | |
752 | |
753 if (window_rect_ == window_rect && clip_rect_ == clip_rect) | |
754 return false; | |
755 | |
756 // We only set the plugin's size here. Its position is moved elsewhere, which | |
757 // allows the window moves/scrolling/clipping to be synchronized with the page | |
758 // and other windows. | |
759 // If the plugin window has no parent, then don't focus it because it isn't | |
760 // being displayed anywhere. See: | |
761 // http://code.google.com/p/chromium/issues/detail?id=32658 | |
762 if (window_rect.size() != window_rect_.size()) { | |
763 UINT flags = SWP_NOMOVE | SWP_NOZORDER; | |
764 if (!GetParent(windowed_handle_)) | |
765 flags |= SWP_NOACTIVATE; | |
766 ::SetWindowPos(windowed_handle_, | |
767 NULL, | |
768 0, | |
769 0, | |
770 window_rect.width(), | |
771 window_rect.height(), | |
772 flags); | |
773 } | |
774 | |
775 window_rect_ = window_rect; | |
776 clip_rect_ = clip_rect; | |
777 | |
778 // Ensure that the entire window gets repainted. | |
779 ::InvalidateRect(windowed_handle_, NULL, FALSE); | |
780 | |
781 return true; | |
782 } | |
783 | |
784 void WebPluginDelegateImpl::WindowedSetWindow() { | |
785 if (!instance_) | |
786 return; | |
787 | |
788 if (!windowed_handle_) { | |
789 NOTREACHED(); | |
790 return; | |
791 } | |
792 | |
793 instance()->set_window_handle(windowed_handle_); | |
794 | |
795 DCHECK(!instance()->windowless()); | |
796 | |
797 window_.clipRect.top = std::max(0, clip_rect_.y()); | |
798 window_.clipRect.left = std::max(0, clip_rect_.x()); | |
799 window_.clipRect.bottom = std::max(0, clip_rect_.y() + clip_rect_.height()); | |
800 window_.clipRect.right = std::max(0, clip_rect_.x() + clip_rect_.width()); | |
801 window_.height = window_rect_.height(); | |
802 window_.width = window_rect_.width(); | |
803 window_.x = 0; | |
804 window_.y = 0; | |
805 | |
806 window_.window = windowed_handle_; | |
807 window_.type = NPWindowTypeWindow; | |
808 | |
809 // Reset this flag before entering the instance in case of side-effects. | |
810 windowed_did_set_window_ = true; | |
811 | |
812 NPError err = instance()->NPP_SetWindow(&window_); | |
813 if (quirks_ & PLUGIN_QUIRK_SETWINDOW_TWICE) | |
814 instance()->NPP_SetWindow(&window_); | |
815 | |
816 WNDPROC current_wnd_proc = reinterpret_cast<WNDPROC>( | |
817 GetWindowLongPtr(windowed_handle_, GWLP_WNDPROC)); | |
818 if (current_wnd_proc != NativeWndProc) { | |
819 plugin_wnd_proc_ = reinterpret_cast<WNDPROC>(SetWindowLongPtr( | |
820 windowed_handle_, GWLP_WNDPROC, reinterpret_cast<LONG>(NativeWndProc))); | |
821 } | |
822 } | |
823 | |
824 ATOM WebPluginDelegateImpl::RegisterNativeWindowClass() { | |
825 static bool have_registered_window_class = false; | |
826 if (have_registered_window_class == true) | |
827 return true; | |
828 | |
829 have_registered_window_class = true; | |
830 | |
831 WNDCLASSEX wcex; | |
832 wcex.cbSize = sizeof(WNDCLASSEX); | |
833 wcex.style = CS_DBLCLKS; | |
834 wcex.lpfnWndProc = DummyWindowProc; | |
835 wcex.cbClsExtra = 0; | |
836 wcex.cbWndExtra = 0; | |
837 wcex.hInstance = GetModuleHandle(NULL); | |
838 wcex.hIcon = 0; | |
839 wcex.hCursor = 0; | |
840 // Some plugins like windows media player 11 create child windows parented | |
841 // by our plugin window, where the media content is rendered. These plugins | |
842 // dont implement WM_ERASEBKGND, which causes painting issues, when the | |
843 // window where the media is rendered is moved around. DefWindowProc does | |
844 // implement WM_ERASEBKGND correctly if we have a valid background brush. | |
845 wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW+1); | |
846 wcex.lpszMenuName = 0; | |
847 wcex.lpszClassName = kNativeWindowClassName; | |
848 wcex.hIconSm = 0; | |
849 | |
850 return RegisterClassEx(&wcex); | |
851 } | |
852 | |
853 LRESULT CALLBACK WebPluginDelegateImpl::DummyWindowProc( | |
854 HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { | |
855 // This is another workaround for Issue 2673 in chromium "Flash: IME not | |
856 // available". Somehow, the CallWindowProc() function does not dispatch | |
857 // window messages when its first parameter is a handle representing the | |
858 // DefWindowProc() function. To avoid this problem, this code creates a | |
859 // wrapper function which just encapsulates the DefWindowProc() function | |
860 // and set it as the window procedure of a windowed plug-in. | |
861 return DefWindowProc(hWnd, message, wParam, lParam); | |
862 } | |
863 | |
864 // Returns true if the message passed in corresponds to a user gesture. | |
865 static bool IsUserGestureMessage(unsigned int message) { | |
866 switch (message) { | |
867 case WM_LBUTTONUP: | |
868 case WM_RBUTTONUP: | |
869 case WM_MBUTTONUP: | |
870 case WM_KEYUP: | |
871 return true; | |
872 | |
873 default: | |
874 break; | |
875 } | |
876 | |
877 return false; | |
878 } | |
879 | |
880 LRESULT CALLBACK WebPluginDelegateImpl::NativeWndProc( | |
881 HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { | |
882 WebPluginDelegateImpl* delegate = reinterpret_cast<WebPluginDelegateImpl*>( | |
883 GetProp(hwnd, kWebPluginDelegateProperty)); | |
884 if (!delegate) { | |
885 NOTREACHED(); | |
886 return 0; | |
887 } | |
888 | |
889 if (message == delegate->last_message_ && | |
890 delegate->GetQuirks() & PLUGIN_QUIRK_DONT_CALL_WND_PROC_RECURSIVELY && | |
891 delegate->is_calling_wndproc) { | |
892 // Real may go into a state where it recursively dispatches the same event | |
893 // when subclassed. See https://bugzilla.mozilla.org/show_bug.cgi?id=192914 | |
894 // We only do the recursive check for Real because it's possible and valid | |
895 // for a plugin to synchronously dispatch a message to itself such that it | |
896 // looks like it's in recursion. | |
897 return TRUE; | |
898 } | |
899 | |
900 // Flash may flood the message queue with WM_USER+1 message causing 100% CPU | |
901 // usage. See https://bugzilla.mozilla.org/show_bug.cgi?id=132759. We | |
902 // prevent this by throttling the messages. | |
903 if (message == WM_USER + 1 && | |
904 delegate->GetQuirks() & PLUGIN_QUIRK_THROTTLE_WM_USER_PLUS_ONE) { | |
905 WebPluginDelegateImpl::ThrottleMessage(delegate->plugin_wnd_proc_, hwnd, | |
906 message, wparam, lparam); | |
907 return FALSE; | |
908 } | |
909 | |
910 LRESULT result; | |
911 uint32 old_message = delegate->last_message_; | |
912 delegate->last_message_ = message; | |
913 | |
914 static UINT custom_msg = RegisterWindowMessage(kPaintMessageName); | |
915 if (message == custom_msg) { | |
916 // Get the invalid rect which is in screen coordinates and convert to | |
917 // window coordinates. | |
918 gfx::Rect invalid_rect; | |
919 invalid_rect.set_x(wparam >> 16); | |
920 invalid_rect.set_y(wparam & 0xFFFF); | |
921 invalid_rect.set_width(lparam >> 16); | |
922 invalid_rect.set_height(lparam & 0xFFFF); | |
923 | |
924 RECT window_rect; | |
925 GetWindowRect(hwnd, &window_rect); | |
926 invalid_rect.Offset(-window_rect.left, -window_rect.top); | |
927 | |
928 // The plugin window might have non-client area. If we don't pass in | |
929 // RDW_FRAME then the children don't receive WM_NCPAINT messages while | |
930 // scrolling, which causes painting problems (http://b/issue?id=923945). | |
931 uint32 flags = RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_FRAME; | |
932 | |
933 // If a plugin (like Google Earth or Java) has child windows that are hosted | |
934 // in a different process, then RedrawWindow with UPDATENOW will | |
935 // synchronously wait for this call to complete. Some messages are pumped | |
936 // but not others, which could lead to a deadlock. So avoid reentrancy by | |
937 // only synchronously calling RedrawWindow once at a time. | |
938 if (old_message != custom_msg) | |
939 flags |= RDW_UPDATENOW; | |
940 | |
941 RedrawWindow(hwnd, &invalid_rect.ToRECT(), NULL, flags); | |
942 result = FALSE; | |
943 } else { | |
944 delegate->is_calling_wndproc = true; | |
945 | |
946 if (!delegate->user_gesture_message_posted_ && | |
947 IsUserGestureMessage(message)) { | |
948 delegate->user_gesture_message_posted_ = true; | |
949 | |
950 delegate->instance()->PushPopupsEnabledState(true); | |
951 | |
952 MessageLoop::current()->PostDelayedTask(FROM_HERE, | |
953 delegate->user_gesture_msg_factory_.NewRunnableMethod( | |
954 &WebPluginDelegateImpl::OnUserGestureEnd), | |
955 kWindowedPluginPopupTimerMs); | |
956 } | |
957 | |
958 HandleCaptureForMessage(hwnd, message); | |
959 | |
960 // Maintain a local/global stack for the g_current_plugin_instance variable | |
961 // as this may be a nested invocation. | |
962 WebPluginDelegateImpl* last_plugin_instance = g_current_plugin_instance; | |
963 | |
964 g_current_plugin_instance = delegate; | |
965 | |
966 result = CallWindowProc( | |
967 delegate->plugin_wnd_proc_, hwnd, message, wparam, lparam); | |
968 | |
969 delegate->is_calling_wndproc = false; | |
970 g_current_plugin_instance = last_plugin_instance; | |
971 | |
972 if (message == WM_NCDESTROY) { | |
973 RemoveProp(hwnd, kWebPluginDelegateProperty); | |
974 ATOM plugin_name_atom = reinterpret_cast<ATOM>( | |
975 RemoveProp(hwnd, kPluginNameAtomProperty)); | |
976 if (plugin_name_atom != 0) | |
977 GlobalDeleteAtom(plugin_name_atom); | |
978 ClearThrottleQueueForWindow(hwnd); | |
979 } | |
980 } | |
981 delegate->last_message_ = old_message; | |
982 return result; | |
983 } | |
984 | |
985 void WebPluginDelegateImpl::WindowlessUpdateGeometry( | |
986 const gfx::Rect& window_rect, | |
987 const gfx::Rect& clip_rect) { | |
988 bool window_rect_changed = (window_rect_ != window_rect); | |
989 // Only resend to the instance if the geometry has changed. | |
990 if (!window_rect_changed && clip_rect == clip_rect_) | |
991 return; | |
992 | |
993 clip_rect_ = clip_rect; | |
994 window_rect_ = window_rect; | |
995 | |
996 WindowlessSetWindow(); | |
997 | |
998 if (window_rect_changed) { | |
999 WINDOWPOS win_pos = {0}; | |
1000 win_pos.x = window_rect_.x(); | |
1001 win_pos.y = window_rect_.y(); | |
1002 win_pos.cx = window_rect_.width(); | |
1003 win_pos.cy = window_rect_.height(); | |
1004 | |
1005 NPEvent pos_changed_event; | |
1006 pos_changed_event.event = WM_WINDOWPOSCHANGED; | |
1007 pos_changed_event.wParam = 0; | |
1008 pos_changed_event.lParam = PtrToUlong(&win_pos); | |
1009 | |
1010 instance()->NPP_HandleEvent(&pos_changed_event); | |
1011 } | |
1012 } | |
1013 | |
1014 void WebPluginDelegateImpl::WindowlessPaint(HDC hdc, | |
1015 const gfx::Rect& damage_rect) { | |
1016 DCHECK(hdc); | |
1017 | |
1018 RECT damage_rect_win; | |
1019 damage_rect_win.left = damage_rect.x(); // + window_rect_.x(); | |
1020 damage_rect_win.top = damage_rect.y(); // + window_rect_.y(); | |
1021 damage_rect_win.right = damage_rect_win.left + damage_rect.width(); | |
1022 damage_rect_win.bottom = damage_rect_win.top + damage_rect.height(); | |
1023 | |
1024 // Save away the old HDC as this could be a nested invocation. | |
1025 void* old_dc = window_.window; | |
1026 window_.window = hdc; | |
1027 | |
1028 NPEvent paint_event; | |
1029 paint_event.event = WM_PAINT; | |
1030 // NOTE: NPAPI is not 64bit safe. It puts pointers into 32bit values. | |
1031 paint_event.wParam = PtrToUlong(hdc); | |
1032 paint_event.lParam = PtrToUlong(&damage_rect_win); | |
1033 static base::StatsRate plugin_paint("Plugin.Paint"); | |
1034 base::StatsScope<base::StatsRate> scope(plugin_paint); | |
1035 instance()->NPP_HandleEvent(&paint_event); | |
1036 window_.window = old_dc; | |
1037 } | |
1038 | |
1039 void WebPluginDelegateImpl::WindowlessSetWindow() { | |
1040 if (!instance()) | |
1041 return; | |
1042 | |
1043 if (window_rect_.IsEmpty()) // wait for geometry to be set. | |
1044 return; | |
1045 | |
1046 DCHECK(instance()->windowless()); | |
1047 | |
1048 window_.clipRect.top = clip_rect_.y(); | |
1049 window_.clipRect.left = clip_rect_.x(); | |
1050 window_.clipRect.bottom = clip_rect_.y() + clip_rect_.height(); | |
1051 window_.clipRect.right = clip_rect_.x() + clip_rect_.width(); | |
1052 window_.height = window_rect_.height(); | |
1053 window_.width = window_rect_.width(); | |
1054 window_.x = window_rect_.x(); | |
1055 window_.y = window_rect_.y(); | |
1056 window_.type = NPWindowTypeDrawable; | |
1057 DrawableContextEnforcer enforcer(&window_); | |
1058 | |
1059 NPError err = instance()->NPP_SetWindow(&window_); | |
1060 DCHECK(err == NPERR_NO_ERROR); | |
1061 } | |
1062 | |
1063 bool WebPluginDelegateImpl::PlatformSetPluginHasFocus(bool focused) { | |
1064 DCHECK(instance()->windowless()); | |
1065 | |
1066 NPEvent focus_event; | |
1067 focus_event.event = focused ? WM_SETFOCUS : WM_KILLFOCUS; | |
1068 focus_event.wParam = 0; | |
1069 focus_event.lParam = 0; | |
1070 | |
1071 instance()->NPP_HandleEvent(&focus_event); | |
1072 return true; | |
1073 } | |
1074 | |
1075 static bool NPEventFromWebMouseEvent(const WebMouseEvent& event, | |
1076 NPEvent *np_event) { | |
1077 np_event->lParam = static_cast<uint32>(MAKELPARAM(event.windowX, | |
1078 event.windowY)); | |
1079 np_event->wParam = 0; | |
1080 | |
1081 if (event.modifiers & WebInputEvent::ControlKey) | |
1082 np_event->wParam |= MK_CONTROL; | |
1083 if (event.modifiers & WebInputEvent::ShiftKey) | |
1084 np_event->wParam |= MK_SHIFT; | |
1085 if (event.modifiers & WebInputEvent::LeftButtonDown) | |
1086 np_event->wParam |= MK_LBUTTON; | |
1087 if (event.modifiers & WebInputEvent::MiddleButtonDown) | |
1088 np_event->wParam |= MK_MBUTTON; | |
1089 if (event.modifiers & WebInputEvent::RightButtonDown) | |
1090 np_event->wParam |= MK_RBUTTON; | |
1091 | |
1092 switch (event.type) { | |
1093 case WebInputEvent::MouseMove: | |
1094 case WebInputEvent::MouseLeave: | |
1095 case WebInputEvent::MouseEnter: | |
1096 np_event->event = WM_MOUSEMOVE; | |
1097 return true; | |
1098 case WebInputEvent::MouseDown: | |
1099 switch (event.button) { | |
1100 case WebMouseEvent::ButtonLeft: | |
1101 np_event->event = WM_LBUTTONDOWN; | |
1102 break; | |
1103 case WebMouseEvent::ButtonMiddle: | |
1104 np_event->event = WM_MBUTTONDOWN; | |
1105 break; | |
1106 case WebMouseEvent::ButtonRight: | |
1107 np_event->event = WM_RBUTTONDOWN; | |
1108 break; | |
1109 } | |
1110 return true; | |
1111 case WebInputEvent::MouseUp: | |
1112 switch (event.button) { | |
1113 case WebMouseEvent::ButtonLeft: | |
1114 np_event->event = WM_LBUTTONUP; | |
1115 break; | |
1116 case WebMouseEvent::ButtonMiddle: | |
1117 np_event->event = WM_MBUTTONUP; | |
1118 break; | |
1119 case WebMouseEvent::ButtonRight: | |
1120 np_event->event = WM_RBUTTONUP; | |
1121 break; | |
1122 } | |
1123 return true; | |
1124 default: | |
1125 NOTREACHED(); | |
1126 return false; | |
1127 } | |
1128 } | |
1129 | |
1130 static bool NPEventFromWebKeyboardEvent(const WebKeyboardEvent& event, | |
1131 NPEvent *np_event) { | |
1132 np_event->wParam = event.windowsKeyCode; | |
1133 | |
1134 switch (event.type) { | |
1135 case WebInputEvent::KeyDown: | |
1136 np_event->event = WM_KEYDOWN; | |
1137 np_event->lParam = 0; | |
1138 return true; | |
1139 case WebInputEvent::Char: | |
1140 np_event->event = WM_CHAR; | |
1141 np_event->lParam = 0; | |
1142 return true; | |
1143 case WebInputEvent::KeyUp: | |
1144 np_event->event = WM_KEYUP; | |
1145 np_event->lParam = 0x8000; | |
1146 return true; | |
1147 default: | |
1148 NOTREACHED(); | |
1149 return false; | |
1150 } | |
1151 } | |
1152 | |
1153 static bool NPEventFromWebInputEvent(const WebInputEvent& event, | |
1154 NPEvent* np_event) { | |
1155 switch (event.type) { | |
1156 case WebInputEvent::MouseMove: | |
1157 case WebInputEvent::MouseLeave: | |
1158 case WebInputEvent::MouseEnter: | |
1159 case WebInputEvent::MouseDown: | |
1160 case WebInputEvent::MouseUp: | |
1161 if (event.size < sizeof(WebMouseEvent)) { | |
1162 NOTREACHED(); | |
1163 return false; | |
1164 } | |
1165 return NPEventFromWebMouseEvent( | |
1166 *static_cast<const WebMouseEvent*>(&event), np_event); | |
1167 case WebInputEvent::KeyDown: | |
1168 case WebInputEvent::Char: | |
1169 case WebInputEvent::KeyUp: | |
1170 if (event.size < sizeof(WebKeyboardEvent)) { | |
1171 NOTREACHED(); | |
1172 return false; | |
1173 } | |
1174 return NPEventFromWebKeyboardEvent( | |
1175 *static_cast<const WebKeyboardEvent*>(&event), np_event); | |
1176 default: | |
1177 return false; | |
1178 } | |
1179 } | |
1180 | |
1181 bool WebPluginDelegateImpl::PlatformHandleInputEvent( | |
1182 const WebInputEvent& event, WebCursorInfo* cursor_info) { | |
1183 DCHECK(cursor_info != NULL); | |
1184 | |
1185 NPEvent np_event; | |
1186 if (!NPEventFromWebInputEvent(event, &np_event)) { | |
1187 return false; | |
1188 } | |
1189 | |
1190 // Synchronize the keyboard layout with the one of the browser process. Flash | |
1191 // uses the keyboard layout of this window to verify a WM_CHAR message is | |
1192 // valid. That is, Flash discards a WM_CHAR message unless its character is | |
1193 // the one translated with ToUnicode(). (Since a plug-in is running on a | |
1194 // separate process from the browser process, we need to syncronize it | |
1195 // manually.) | |
1196 if (np_event.event == WM_CHAR) { | |
1197 if (!keyboard_layout_) | |
1198 keyboard_layout_ = GetKeyboardLayout(GetCurrentThreadId()); | |
1199 if (!parent_thread_id_) | |
1200 parent_thread_id_ = GetWindowThreadProcessId(parent_, NULL); | |
1201 HKL parent_layout = GetKeyboardLayout(parent_thread_id_); | |
1202 if (keyboard_layout_ != parent_layout) { | |
1203 std::wstring layout_name(base::StringPrintf(L"%08x", parent_layout)); | |
1204 LoadKeyboardLayout(layout_name.c_str(), KLF_ACTIVATE); | |
1205 keyboard_layout_ = parent_layout; | |
1206 } | |
1207 } | |
1208 | |
1209 if (ShouldTrackEventForModalLoops(&np_event)) { | |
1210 // A windowless plugin can enter a modal loop in a NPP_HandleEvent call. | |
1211 // For e.g. Flash puts up a context menu when we right click on the | |
1212 // windowless plugin area. We detect this by setting up a message filter | |
1213 // hook pror to calling NPP_HandleEvent on the plugin and unhook on | |
1214 // return from NPP_HandleEvent. If the plugin does enter a modal loop | |
1215 // in that context we unhook on receiving the first notification in | |
1216 // the message filter hook. | |
1217 handle_event_message_filter_hook_ = | |
1218 SetWindowsHookEx(WH_MSGFILTER, HandleEventMessageFilterHook, NULL, | |
1219 GetCurrentThreadId()); | |
1220 } | |
1221 | |
1222 bool old_task_reentrancy_state = | |
1223 MessageLoop::current()->NestableTasksAllowed(); | |
1224 | |
1225 | |
1226 // Maintain a local/global stack for the g_current_plugin_instance variable | |
1227 // as this may be a nested invocation. | |
1228 WebPluginDelegateImpl* last_plugin_instance = g_current_plugin_instance; | |
1229 | |
1230 g_current_plugin_instance = this; | |
1231 | |
1232 handle_event_depth_++; | |
1233 | |
1234 bool ret = instance()->NPP_HandleEvent(&np_event) != 0; | |
1235 | |
1236 // Flash and SilverLight always return false, even when they swallow the | |
1237 // event. Flash does this because it passes the event to its window proc, | |
1238 // which is supposed to return 0 if an event was handled. There are few | |
1239 // exceptions, such as IME, where it sometimes returns true. | |
1240 ret = true; | |
1241 | |
1242 if (np_event.event == WM_MOUSEMOVE) { | |
1243 // Snag a reference to the current cursor ASAP in case the plugin modified | |
1244 // it. There is a nasty race condition here with the multiprocess browser | |
1245 // as someone might be setting the cursor in the main process as well. | |
1246 current_windowless_cursor_.GetCursorInfo(cursor_info); | |
1247 } | |
1248 | |
1249 handle_event_depth_--; | |
1250 | |
1251 g_current_plugin_instance = last_plugin_instance; | |
1252 | |
1253 MessageLoop::current()->SetNestableTasksAllowed(old_task_reentrancy_state); | |
1254 | |
1255 // We could have multiple NPP_HandleEvent calls nested together in case | |
1256 // the plugin enters a modal loop. Reset the pump messages event when | |
1257 // the outermost NPP_HandleEvent call unwinds. | |
1258 if (handle_event_depth_ == 0) { | |
1259 ResetEvent(handle_event_pump_messages_event_); | |
1260 } | |
1261 | |
1262 return ret; | |
1263 } | |
1264 | |
1265 | |
1266 void WebPluginDelegateImpl::OnModalLoopEntered() { | |
1267 DCHECK(handle_event_pump_messages_event_ != NULL); | |
1268 SetEvent(handle_event_pump_messages_event_); | |
1269 | |
1270 MessageLoop::current()->SetNestableTasksAllowed(true); | |
1271 | |
1272 UnhookWindowsHookEx(handle_event_message_filter_hook_); | |
1273 handle_event_message_filter_hook_ = NULL; | |
1274 } | |
1275 | |
1276 bool WebPluginDelegateImpl::ShouldTrackEventForModalLoops(NPEvent* event) { | |
1277 if (event->event == WM_RBUTTONDOWN) | |
1278 return true; | |
1279 return false; | |
1280 } | |
1281 | |
1282 void WebPluginDelegateImpl::OnUserGestureEnd() { | |
1283 user_gesture_message_posted_ = false; | |
1284 instance()->PopPopupsEnabledState(); | |
1285 } | |
1286 | |
1287 BOOL WINAPI WebPluginDelegateImpl::TrackPopupMenuPatch( | |
1288 HMENU menu, unsigned int flags, int x, int y, int reserved, | |
1289 HWND window, const RECT* rect) { | |
1290 | |
1291 HWND last_focus_window = NULL; | |
1292 | |
1293 if (g_current_plugin_instance) { | |
1294 unsigned long window_process_id = 0; | |
1295 unsigned long window_thread_id = | |
1296 GetWindowThreadProcessId(window, &window_process_id); | |
1297 // TrackPopupMenu fails if the window passed in belongs to a different | |
1298 // thread. | |
1299 if (::GetCurrentThreadId() != window_thread_id) { | |
1300 window = g_current_plugin_instance->dummy_window_for_activation_; | |
1301 } | |
1302 | |
1303 // To ensure that the plugin receives keyboard events we set focus to the | |
1304 // dummy window. | |
1305 // TODO(iyengar) We need a framework in the renderer to identify which | |
1306 // windowless plugin is under the mouse and to handle this. This would | |
1307 // also require some changes in RenderWidgetHost to detect this in the | |
1308 // WM_MOUSEACTIVATE handler and inform the renderer accordingly. | |
1309 if (g_current_plugin_instance->dummy_window_for_activation_) { | |
1310 last_focus_window = | |
1311 ::SetFocus(g_current_plugin_instance->dummy_window_for_activation_); | |
1312 } | |
1313 } | |
1314 | |
1315 BOOL result = TrackPopupMenu(menu, flags, x, y, reserved, window, rect); | |
1316 | |
1317 if (IsWindow(last_focus_window)) { | |
1318 // The Flash plugin at times sets focus to its hidden top level window | |
1319 // with class name SWFlash_PlaceholderX. This causes the chrome browser | |
1320 // window to receive a WM_ACTIVATEAPP message as a top level window from | |
1321 // another thread is now active. We end up in a state where the chrome | |
1322 // browser window is not active even though the user clicked on it. | |
1323 // Our workaround for this is to send over a raw | |
1324 // WM_LBUTTONDOWN/WM_LBUTTONUP combination to the last focus window, which | |
1325 // does the trick. | |
1326 if (g_current_plugin_instance->dummy_window_for_activation_ != | |
1327 ::GetFocus()) { | |
1328 INPUT input_info = {0}; | |
1329 input_info.type = INPUT_MOUSE; | |
1330 input_info.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; | |
1331 ::SendInput(1, &input_info, sizeof(INPUT)); | |
1332 | |
1333 input_info.type = INPUT_MOUSE; | |
1334 input_info.mi.dwFlags = MOUSEEVENTF_LEFTUP; | |
1335 ::SendInput(1, &input_info, sizeof(INPUT)); | |
1336 } else { | |
1337 ::SetFocus(last_focus_window); | |
1338 } | |
1339 } | |
1340 | |
1341 return result; | |
1342 } | |
1343 | |
1344 HCURSOR WINAPI WebPluginDelegateImpl::SetCursorPatch(HCURSOR cursor) { | |
1345 // The windowless flash plugin periodically calls SetCursor in a wndproc | |
1346 // instantiated on the plugin thread. This causes annoying cursor flicker | |
1347 // when the mouse is moved on a foreground tab, with a windowless plugin | |
1348 // instance in a background tab. We just ignore the call here. | |
1349 if (!g_current_plugin_instance) { | |
1350 HCURSOR current_cursor = GetCursor(); | |
1351 if (current_cursor != cursor) { | |
1352 ::SetCursor(cursor); | |
1353 } | |
1354 return current_cursor; | |
1355 } | |
1356 | |
1357 if (!g_current_plugin_instance->IsWindowless()) { | |
1358 return ::SetCursor(cursor); | |
1359 } | |
1360 | |
1361 // It is ok to pass NULL here to GetCursor as we are not looking for cursor | |
1362 // types defined by Webkit. | |
1363 HCURSOR previous_cursor = | |
1364 g_current_plugin_instance->current_windowless_cursor_.GetCursor(NULL); | |
1365 | |
1366 g_current_plugin_instance->current_windowless_cursor_.InitFromExternalCursor( | |
1367 cursor); | |
1368 return previous_cursor; | |
1369 } | |
1370 | |
1371 LONG WINAPI WebPluginDelegateImpl::RegEnumKeyExWPatch( | |
1372 HKEY key, DWORD index, LPWSTR name, LPDWORD name_size, LPDWORD reserved, | |
1373 LPWSTR class_name, LPDWORD class_size, PFILETIME last_write_time) { | |
1374 DWORD orig_size = *name_size; | |
1375 LONG rv = RegEnumKeyExW(key, index, name, name_size, reserved, class_name, | |
1376 class_size, last_write_time); | |
1377 if (rv == ERROR_SUCCESS && | |
1378 GetKeyPath(key).find(L"Microsoft\\MediaPlayer\\ShimInclusionList") != | |
1379 std::wstring::npos) { | |
1380 static const wchar_t kChromeExeName[] = L"chrome.exe"; | |
1381 wcsncpy_s(name, orig_size, kChromeExeName, arraysize(kChromeExeName)); | |
1382 *name_size = | |
1383 std::min(orig_size, static_cast<DWORD>(arraysize(kChromeExeName))); | |
1384 } | |
1385 | |
1386 return rv; | |
1387 } | |
1388 | |
1389 void WebPluginDelegateImpl::HandleCaptureForMessage(HWND window, | |
1390 UINT message) { | |
1391 if (!WebPluginDelegateImpl::IsPluginDelegateWindow(window)) | |
1392 return; | |
1393 | |
1394 switch (message) { | |
1395 case WM_LBUTTONDOWN: | |
1396 case WM_MBUTTONDOWN: | |
1397 case WM_RBUTTONDOWN: | |
1398 ::SetCapture(window); | |
1399 break; | |
1400 | |
1401 case WM_LBUTTONUP: | |
1402 case WM_MBUTTONUP: | |
1403 case WM_RBUTTONUP: | |
1404 ::ReleaseCapture(); | |
1405 break; | |
1406 | |
1407 default: | |
1408 break; | |
1409 } | |
1410 } | |
OLD | NEW |