Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(305)

Side by Side Diff: webkit/glue/plugins/webplugin_delegate_impl_mac.mm

Issue 165250: Merge 22799 - Set up a interposing library for Carbon calls made by plugins.... (Closed) Base URL: svn://chrome-svn/chrome/branches/197/src/
Patch Set: Created 11 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « webkit/glue/plugins/fake_plugin_window_tracker_mac.cc ('k') | webkit/webkit.gyp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:mergeinfo
Merged /branches/chrome_webkit_merge_branch/src/webkit/glue/plugins/webplugin_delegate_impl_mac.mm:r3734-4217,4606-5108,5177-5263
Merged /trunk/src/webkit/glue/plugins/webplugin_delegate_impl_mac.mm:r22799
OLDNEW
(Empty)
1 // Copyright (c) 2006-2008 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 #import <Cocoa/Cocoa.h>
6
7 #include "webkit/glue/plugins/webplugin_delegate_impl.h"
8
9 #include <string>
10 #include <vector>
11
12 #include "base/file_util.h"
13 #include "base/lazy_instance.h"
14 #include "base/message_loop.h"
15 #include "base/stats_counters.h"
16 #include "base/string_util.h"
17 #include "webkit/api/public/WebInputEvent.h"
18 #include "webkit/default_plugin/plugin_impl.h"
19 #include "webkit/glue/glue_util.h"
20 #include "webkit/glue/webplugin.h"
21 #include "webkit/glue/plugins/plugin_constants_win.h"
22 #include "webkit/glue/plugins/plugin_instance.h"
23 #include "webkit/glue/plugins/plugin_lib.h"
24 #include "webkit/glue/plugins/plugin_list.h"
25 #include "webkit/glue/plugins/plugin_stream_url.h"
26 #include "webkit/glue/webkit_glue.h"
27
28 using WebKit::WebCursorInfo;
29 using WebKit::WebKeyboardEvent;
30 using WebKit::WebInputEvent;
31 using WebKit::WebMouseEvent;
32
33 // Important implementation notes: The Mac definition of NPAPI, particularly
34 // the distinction between windowed and windowless modes, differs from the
35 // Windows and Linux definitions. Most of those differences are
36 // accomodated by the WebPluginDelegate class.
37
38 namespace {
39
40 // The fastest we are willing to process idle events for plugins.
41 // Some can easily exceed the limits of our CPU if we don't throttle them.
42 // The throttle has been chosen by using the same value as Apple's WebKit port.
43 //
44 // We'd like to make the throttle delay variable, based on the amount of
45 // time currently required to paint plugins. There isn't a good
46 // way to count the time spent in aggregate plugin painting, however, so
47 // this seems to work well enough.
48 const int kPluginIdleThrottleDelayMs = 20; // 20ms (50Hz)
49
50 // The most recently seen offset between global and local coordinates. We use
51 // this to keep the placeholder Carbon WindowRef's origin in sync with the
52 // actual browser window, without having to pass that geometry over IPC. If we
53 // end up needing to interpose on Carbon APIs in the plugin process (in order
54 // to simulate window activation, for example), this could be replaced by
55 // interposing on GlobalToLocal and/or LocalToGlobal (see related TODO comments
56 // below in WebPluginDelegateImpl::OnNullEvent()).
57
58 int g_current_x_offset = 0;
59 int g_current_y_offset = 0;
60
61 WindowRef g_last_front_window = NULL;
62 ProcessSerialNumber g_saved_front_process;
63
64 } // namespace
65
66 WebPluginDelegate* WebPluginDelegate::Create(
67 const FilePath& filename,
68 const std::string& mime_type,
69 gfx::PluginWindowHandle containing_view) {
70 scoped_refptr<NPAPI::PluginLib> plugin =
71 NPAPI::PluginLib::CreatePluginLib(filename);
72 if (plugin.get() == NULL)
73 return NULL;
74
75 NPError err = plugin->NP_Initialize();
76 if (err != NPERR_NO_ERROR)
77 return NULL;
78
79 scoped_refptr<NPAPI::PluginInstance> instance =
80 plugin->CreateInstance(mime_type);
81 return new WebPluginDelegateImpl(containing_view, instance.get());
82 }
83
84 WebPluginDelegateImpl::WebPluginDelegateImpl(
85 gfx::PluginWindowHandle containing_view,
86 NPAPI::PluginInstance *instance)
87 : parent_(containing_view),
88 instance_(instance),
89 quirks_(0),
90 plugin_(NULL),
91 // all Mac plugins are "windowless" in the Windows/X11 sense
92 windowless_(true),
93 windowless_needs_set_window_(true),
94 handle_event_depth_(0),
95 user_gesture_message_posted_(this),
96 user_gesture_msg_factory_(this),
97 null_event_factory_(this),
98 last_mouse_x_(0),
99 last_mouse_y_(0) {
100 memset(&window_, 0, sizeof(window_));
101 }
102
103 WebPluginDelegateImpl::~WebPluginDelegateImpl() {
104 DestroyInstance();
105
106 if (cg_context_.window)
107 DisposeWindow(cg_context_.window);
108 }
109
110 void WebPluginDelegateImpl::PluginDestroyed() {
111 delete this;
112 }
113
114 bool WebPluginDelegateImpl::Initialize(const GURL& url,
115 char** argn,
116 char** argv,
117 int argc,
118 WebPlugin* plugin,
119 bool load_manually) {
120 plugin_ = plugin;
121
122 instance_->set_web_plugin(plugin);
123 NPAPI::PluginInstance* old_instance =
124 NPAPI::PluginInstance::SetInitializingInstance(instance_);
125
126
127 bool start_result = instance_->Start(url, argn, argv, argc, load_manually);
128
129 NPAPI::PluginInstance::SetInitializingInstance(old_instance);
130
131 if (!start_result)
132 return false;
133
134 cg_context_.window = NULL;
135 window_.window = &cg_context_;
136 window_.type = NPWindowTypeWindow;
137
138 plugin->SetWindow(NULL);
139 plugin_url_ = url.spec();
140
141 MessageLoop::current()->PostDelayedTask(FROM_HERE,
142 null_event_factory_.NewRunnableMethod(
143 &WebPluginDelegateImpl::OnNullEvent),
144 kPluginIdleThrottleDelayMs);
145 return true;
146 }
147
148 void WebPluginDelegateImpl::DestroyInstance() {
149 if (instance_ && (instance_->npp()->ndata != NULL)) {
150 // Shutdown all streams before destroying so that
151 // no streams are left "in progress". Need to do
152 // this before calling set_web_plugin(NULL) because the
153 // instance uses the helper to do the download.
154 instance_->CloseStreams();
155 instance_->NPP_Destroy();
156 instance_->set_web_plugin(NULL);
157 instance_ = 0;
158 }
159 }
160
161 void WebPluginDelegateImpl::UpdateGeometry(
162 const gfx::Rect& window_rect,
163 const gfx::Rect& clip_rect) {
164
165 DCHECK(windowless_);
166 WindowlessUpdateGeometry(window_rect, clip_rect);
167 }
168
169 void WebPluginDelegateImpl::Paint(CGContextRef context, const gfx::Rect& rect) {
170 DCHECK(windowless_);
171 WindowlessPaint(context, rect);
172 }
173
174 void WebPluginDelegateImpl::Print(CGContextRef context) {
175 // Disabling the call to NPP_Print as it causes a crash in
176 // flash in some cases. In any case this does not work as expected
177 // as the EMF meta file dc passed in needs to be created with the
178 // the plugin window dc as its sibling dc and the window rect
179 // in .01 mm units.
180 }
181
182 NPObject* WebPluginDelegateImpl::GetPluginScriptableObject() {
183 return instance_->GetPluginScriptableObject();
184 }
185
186 void WebPluginDelegateImpl::DidFinishLoadWithReason(NPReason reason) {
187 instance()->DidFinishLoadWithReason(reason);
188 }
189
190 int WebPluginDelegateImpl::GetProcessId() {
191 // We are in process, so the plugin pid is this current process pid.
192 return getpid();
193 }
194
195 void WebPluginDelegateImpl::SendJavaScriptStream(const std::string& url,
196 const std::wstring& result,
197 bool success,
198 bool notify_needed,
199 intptr_t notify_data) {
200 instance()->SendJavaScriptStream(url, result, success, notify_needed,
201 notify_data);
202 }
203
204 void WebPluginDelegateImpl::DidReceiveManualResponse(
205 const std::string& url, const std::string& mime_type,
206 const std::string& headers, uint32 expected_length, uint32 last_modified) {
207 instance()->DidReceiveManualResponse(url, mime_type, headers,
208 expected_length, last_modified);
209 }
210
211 void WebPluginDelegateImpl::DidReceiveManualData(const char* buffer,
212 int length) {
213 instance()->DidReceiveManualData(buffer, length);
214 }
215
216 void WebPluginDelegateImpl::DidFinishManualLoading() {
217 instance()->DidFinishManualLoading();
218 }
219
220 void WebPluginDelegateImpl::DidManualLoadFail() {
221 instance()->DidManualLoadFail();
222 }
223
224 FilePath WebPluginDelegateImpl::GetPluginPath() {
225 return instance()->plugin_lib()->plugin_info().path;
226 }
227
228 void WebPluginDelegateImpl::InstallMissingPlugin() {
229 NPEvent evt;
230 instance()->NPP_HandleEvent(&evt);
231 }
232
233 void WebPluginDelegateImpl::WindowlessUpdateGeometry(
234 const gfx::Rect& window_rect,
235 const gfx::Rect& clip_rect) {
236 // Only resend to the instance if the geometry has changed.
237 if (window_rect == window_rect_ && clip_rect == clip_rect_)
238 return;
239
240 // We will inform the instance of this change when we call NPP_SetWindow.
241 clip_rect_ = clip_rect;
242
243 if (window_rect_ != window_rect) {
244 window_rect_ = window_rect;
245
246 WindowlessSetWindow(true);
247 }
248 }
249
250 void WebPluginDelegateImpl::WindowlessPaint(gfx::NativeDrawingContext context,
251 const gfx::Rect& damage_rect) {
252 static StatsRate plugin_paint("Plugin.Paint");
253 StatsScope<StatsRate> scope(plugin_paint);
254
255 // We save and restore the NSGraphicsContext state in case the plugin uses
256 // Cocoa drawing.
257 [NSGraphicsContext saveGraphicsState];
258 [NSGraphicsContext setCurrentContext:[NSGraphicsContext
259 graphicsContextWithGraphicsPort:context
260 flipped:YES]];
261 CGContextSaveGState(context);
262
263 cg_context_.context = context;
264 if (window_.window == NULL)
265 windowless_needs_set_window_ = true;
266
267 window_.window = &cg_context_;
268
269 if (windowless_needs_set_window_)
270 WindowlessSetWindow(false);
271
272 NPEvent paint_event;
273 paint_event.what = updateEvt;
274 paint_event.message = reinterpret_cast<uint32>(cg_context_.window);
275 paint_event.when = TickCount();
276 paint_event.where.h = 0;
277 paint_event.where.v = 0;
278 paint_event.modifiers = 0;
279 instance()->NPP_HandleEvent(&paint_event);
280
281 CGContextRestoreGState(context);
282 [NSGraphicsContext restoreGraphicsState];
283 }
284
285 // Moves our dummy window to the given offset relative to the last known
286 // location of the real renderer window's content view.
287 static void UpdateDummyWindowLocationWithOffset(WindowRef window,
288 int x_offset, int y_offset) {
289 int target_x = g_current_x_offset + x_offset;
290 int target_y = g_current_y_offset + y_offset;
291 Rect window_bounds;
292 GetWindowBounds(window, kWindowContentRgn, &window_bounds);
293 if ((window_bounds.left != target_x) ||
294 (window_bounds.top != target_y)) {
295 int height = window_bounds.bottom - window_bounds.top;
296 int width = window_bounds.right - window_bounds.left;
297 window_bounds.left = target_x;
298 window_bounds.top = target_y;
299 window_bounds.right = window_bounds.left + width;
300 window_bounds.bottom = window_bounds.top + height;
301 SetWindowBounds(window, kWindowContentRgn, &window_bounds);
302 }
303 }
304
305 void WebPluginDelegateImpl::WindowlessSetWindow(bool force_set_window) {
306 if (!instance())
307 return;
308
309 if (window_rect_.IsEmpty()) // wait for geometry to be set.
310 return;
311
312 window_.clipRect.top = 0;
313 window_.clipRect.left = 0;
314 window_.clipRect.bottom = window_rect_.height();
315 window_.clipRect.right = window_rect_.width();
316 window_.height = window_rect_.height();
317 window_.width = window_rect_.width();
318 window_.x = 0;
319 window_.y = 0;
320 window_.type = NPWindowTypeWindow;
321
322 if (!force_set_window)
323 // Reset this flag before entering the instance in case of side-effects.
324 windowless_needs_set_window_ = false;
325
326 if (!cg_context_.window) {
327 // For all plugins we create a placeholder offscreen window for the use
328 // of NPWindow. NPAPI on the Mac requires a Carbon WindowRef for the
329 // "browser window", even if we're not using the Quickdraw drawing model.
330 // Not having a valid window reference causes subtle bugs with plugins
331 // which retreive the NPWindow and validate the same. The NPWindow
332 // can be retreived via NPN_GetValue of NPNVnetscapeWindow.
333 Rect window_bounds = { 0, 0, window_rect_.height(), window_rect_.width() };
334 WindowRef window_ref;
335 if (CreateNewWindow(kDocumentWindowClass,
336 kWindowStandardDocumentAttributes,
337 &window_bounds,
338 &window_ref) == noErr) {
339 cg_context_.window = window_ref;
340 SelectWindow(window_ref);
341 g_last_front_window = window_ref;
342 }
343 }
344 UpdateDummyWindowLocationWithOffset(cg_context_.window, window_rect_.x(),
345 window_rect_.y());
346
347 if (!force_set_window)
348 windowless_needs_set_window_ = false;
349 NPError err = instance()->NPP_SetWindow(&window_);
350 DCHECK(err == NPERR_NO_ERROR);
351 }
352
353 void WebPluginDelegateImpl::SetFocus() {
354 NPEvent focus_event = { 0 };
355 focus_event.what = NPEventType_GetFocusEvent;
356 focus_event.when = TickCount();
357 instance()->NPP_HandleEvent(&focus_event);
358 }
359
360 static bool WebInputEventIsWebMouseEvent(const WebInputEvent& event) {
361 switch (event.type) {
362 case WebInputEvent::MouseMove:
363 case WebInputEvent::MouseLeave:
364 case WebInputEvent::MouseEnter:
365 case WebInputEvent::MouseDown:
366 case WebInputEvent::MouseUp:
367 if (event.size < sizeof(WebMouseEvent)) {
368 NOTREACHED();
369 return false;
370 }
371 return true;
372 default:
373 return false;
374 }
375 }
376
377 static bool WebInputEventIsWebKeyboardEvent(const WebInputEvent& event) {
378 switch (event.type) {
379 case WebInputEvent::KeyDown:
380 case WebInputEvent::KeyUp:
381 if (event.size < sizeof(WebKeyboardEvent)) {
382 NOTREACHED();
383 return false;
384 }
385 return true;
386 default:
387 return false;
388 }
389 }
390
391 static bool NPEventFromWebMouseEvent(const WebMouseEvent& event,
392 NPEvent *np_event) {
393 np_event->where.h = event.globalX;
394 np_event->where.v = event.globalY;
395
396 if (event.modifiers & WebInputEvent::ControlKey)
397 np_event->modifiers |= controlKey;
398 if (event.modifiers & WebInputEvent::ShiftKey)
399 np_event->modifiers |= shiftKey;
400
401 // default to "button up"; override this for mouse down events below.
402 np_event->modifiers |= btnState;
403
404 switch (event.button) {
405 case WebMouseEvent::ButtonLeft:
406 break;
407 case WebMouseEvent::ButtonMiddle:
408 np_event->modifiers |= cmdKey;
409 break;
410 case WebMouseEvent::ButtonRight:
411 np_event->modifiers |= controlKey;
412 break;
413 }
414 switch (event.type) {
415 case WebInputEvent::MouseMove:
416 np_event->what = nullEvent;
417 return true;
418 case WebInputEvent::MouseLeave:
419 case WebInputEvent::MouseEnter:
420 np_event->what = NPEventType_AdjustCursorEvent;
421 return true;
422 case WebInputEvent::MouseDown:
423 np_event->modifiers &= ~btnState;
424 np_event->what = mouseDown;
425 return true;
426 case WebInputEvent::MouseUp:
427 np_event->what = mouseUp;
428 return true;
429 default:
430 NOTREACHED();
431 return false;
432 }
433 }
434
435 static bool NPEventFromWebKeyboardEvent(const WebKeyboardEvent& event,
436 NPEvent *np_event) {
437 // TODO: figure out how to handle Unicode input to plugins, if that's
438 // even possible in the NPAPI Carbon event model.
439 np_event->message = (event.nativeKeyCode << 8) & keyCodeMask;
440 np_event->message |= event.text[0] & charCodeMask;
441 np_event->modifiers |= btnState;
442 if (event.modifiers & WebInputEvent::ControlKey)
443 np_event->modifiers |= controlKey;
444 if (event.modifiers & WebInputEvent::ShiftKey)
445 np_event->modifiers |= shiftKey;
446 if (event.modifiers & WebInputEvent::AltKey)
447 np_event->modifiers |= cmdKey;
448 if (event.modifiers & WebInputEvent::MetaKey)
449 np_event->modifiers |= optionKey;
450
451 switch (event.type) {
452 case WebInputEvent::KeyDown:
453 if (event.modifiers & WebInputEvent::IsAutoRepeat)
454 np_event->what = autoKey;
455 else
456 np_event->what = keyDown;
457 return true;
458 case WebInputEvent::KeyUp:
459 np_event->what = keyUp;
460 return true;
461 default:
462 NOTREACHED();
463 return false;
464 }
465 }
466
467 static bool NPEventFromWebInputEvent(const WebInputEvent& event,
468 NPEvent* np_event) {
469 if (WebInputEventIsWebMouseEvent(event)) {
470 return NPEventFromWebMouseEvent(*static_cast<const WebMouseEvent*>(&event),
471 np_event);
472 } else if (WebInputEventIsWebKeyboardEvent(event)) {
473 return NPEventFromWebKeyboardEvent(
474 *static_cast<const WebKeyboardEvent*>(&event), np_event);
475 }
476 DLOG(WARNING) << "unknown event type" << event.type;
477 return false;
478 }
479
480 static void UpdateWindowLocation(WindowRef window, const WebMouseEvent& event) {
481 // TODO: figure out where the vertical offset of 22 comes from (and if 22 is
482 // exactly right) and replace with an appropriate calculation. It feels like
483 // window structure or the menu bar, but neither should be involved here.
484 g_current_x_offset = event.globalX - event.windowX;
485 g_current_y_offset = event.globalY - event.windowY + 22;
486
487 UpdateDummyWindowLocationWithOffset(window, event.windowX - event.x,
488 event.windowY - event.y);
489 }
490
491 bool WebPluginDelegateImpl::HandleInputEvent(const WebInputEvent& event,
492 WebCursorInfo* cursor) {
493 DCHECK(windowless_) << "events should only be received in windowless mode";
494 DCHECK(cursor != NULL);
495
496 NPEvent np_event = {0};
497 if (!NPEventFromWebInputEvent(event, &np_event)) {
498 return false;
499 }
500 np_event.when = TickCount();
501 if (np_event.what == nullEvent) {
502 last_mouse_x_ = np_event.where.h;
503 last_mouse_y_ = np_event.where.v;
504 return true; // Let the recurring task actually send the event.
505 }
506
507 // If this is a mouse event, we need to make sure our dummy window has the
508 // correct location before we send the event to the plugin, so that any
509 // coordinate conversion the plugin does will work out.
510 if (WebInputEventIsWebMouseEvent(event)) {
511 const WebMouseEvent* mouse_event =
512 static_cast<const WebMouseEvent*>(&event);
513 UpdateWindowLocation(cg_context_.window, *mouse_event);
514 }
515 CGContextSaveGState(cg_context_.context);
516 bool ret = instance()->NPP_HandleEvent(&np_event) != 0;
517 CGContextRestoreGState(cg_context_.context);
518 return ret;
519 }
520
521 WebPluginResourceClient* WebPluginDelegateImpl::CreateResourceClient(
522 int resource_id, const std::string &url, bool notify_needed,
523 intptr_t notify_data, intptr_t existing_stream) {
524 // Stream already exists. This typically happens for range requests
525 // initiated via NPN_RequestRead.
526 if (existing_stream) {
527 NPAPI::PluginStream* plugin_stream =
528 reinterpret_cast<NPAPI::PluginStream*>(existing_stream);
529
530 plugin_stream->CancelRequest();
531
532 return plugin_stream->AsResourceClient();
533 }
534
535 if (notify_needed) {
536 instance()->SetURLLoadData(GURL(url.c_str()), notify_data);
537 }
538 std::string mime_type;
539 NPAPI::PluginStreamUrl *stream = instance()->CreateStream(
540 resource_id, url, mime_type, notify_needed,
541 reinterpret_cast<void*>(notify_data));
542 return stream;
543 }
544
545 void WebPluginDelegateImpl::URLRequestRouted(const std::string&url,
546 bool notify_needed,
547 intptr_t notify_data) {
548 if (notify_needed) {
549 instance()->SetURLLoadData(GURL(url.c_str()), notify_data);
550 }
551 }
552
553 void WebPluginDelegateImpl::OnNullEvent() {
554 NPEvent np_event = {0};
555 np_event.what = nullEvent;
556 np_event.when = TickCount();
557 np_event.modifiers = GetCurrentKeyModifiers();
558 if (!Button())
559 np_event.modifiers |= btnState;
560 np_event.where.h = last_mouse_x_;
561 np_event.where.v = last_mouse_y_;
562 instance()->NPP_HandleEvent(&np_event);
563
564 WindowRef front_window = FrontWindow();
565 if (front_window == cg_context_.window) {
566 if (front_window != g_last_front_window) {
567 // If our dummy window is now the front window, but was not previously,
568 // it means that a plugin window has been destroyed. Make sure our fake
569 // browser window is selected.
570 // TODO: Use DYLD_INSERT_LIBRARIES to interpose on Carbon window
571 // APIs within the plugin process. This will allow us to (a) get rid of
572 // the dummy window, and (b) explicitly track the creation and
573 // destruction of windows by the plugin.
574 g_last_front_window = front_window;
575 SelectWindow(cg_context_.window);
576
577 // If the plugin process is still the front process, bring the prior
578 // front process (normally this will be the browser process) back to
579 // the front.
580 // TODO: make this an IPC message so that the browser can properly
581 // reactivate the window.
582 ProcessSerialNumber this_process, front_process;
583 GetCurrentProcess(&this_process);
584 GetFrontProcess(&front_process);
585 Boolean matched = false;
586 SameProcess(&this_process, &front_process, &matched);
587 if (matched)
588 SetFrontProcess(&g_saved_front_process);
589 g_last_front_window = FrontWindow();
590 }
591 } else if (front_window != g_last_front_window) {
592 // The plugin has just created a new window and brought it to the front.
593 // bring the plugin process to the front so that the user can see it (for
594 // example, an alert or file selection dialog).
595 // TODO: make this an IPC to order the plugin process above the browser
596 // process but not necessarily the frontmost.
597 ProcessSerialNumber this_process;
598 GetCurrentProcess(&this_process);
599 GetFrontProcess(&g_saved_front_process);
600 SetFrontProcess(&this_process);
601 g_last_front_window = front_window;
602 SelectWindow(front_window);
603 }
604
605 MessageLoop::current()->PostDelayedTask(FROM_HERE,
606 null_event_factory_.NewRunnableMethod(
607 &WebPluginDelegateImpl::OnNullEvent),
608 kPluginIdleThrottleDelayMs);
609 }
OLDNEW
« no previous file with comments | « webkit/glue/plugins/fake_plugin_window_tracker_mac.cc ('k') | webkit/webkit.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698