1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "content/child/npapi/webplugin_delegate_impl.h" 6 7 #import <Cocoa/Cocoa.h> 8 #import <QuartzCore/QuartzCore.h> 9 #include <unistd.h> 10 11 #include <set> 12 #include <string> 13 14 #include "base/mac/mac_util.h" 15 #include "base/memory/scoped_ptr.h" 16 #include "base/metrics/stats_counters.h" 17 #include "base/strings/string_util.h" 18 #include "base/strings/sys_string_conversions.h" 19 #include "base/strings/utf_string_conversions.h" 20 #include "content/child/npapi/plugin_instance.h" 21 #include "content/child/npapi/plugin_lib.h" 22 #include "content/child/npapi/plugin_web_event_converter_mac.h" 23 #include "content/child/npapi/webplugin.h" 24 #include "content/child/npapi/webplugin_accelerated_surface_mac.h" 25 #include "skia/ext/skia_utils_mac.h" 26 #include "third_party/WebKit/public/web/WebInputEvent.h" 27 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" 28 #include "webkit/common/cursors/webcursor.h" 29 30 using blink::WebKeyboardEvent; 31 using blink::WebInputEvent; 32 using blink::WebMouseEvent; 33 using blink::WebMouseWheelEvent; 34 35 // Important implementation notes: The Mac definition of NPAPI, particularly 36 // the distinction between windowed and windowless modes, differs from the 37 // Windows and Linux definitions. Most of those differences are 38 // accomodated by the WebPluginDelegate class. 39 40 namespace content { 41 42 namespace { 43 44 const int kCoreAnimationRedrawPeriodMs = 10; // 100 Hz 45 46 WebPluginDelegateImpl* g_active_delegate; 47 48 // Helper to simplify correct usage of g_active_delegate. Instantiating will 49 // set the active delegate to |delegate| for the lifetime of the object, then 50 // NULL when it goes out of scope. 51 class ScopedActiveDelegate { 52 public: 53 explicit ScopedActiveDelegate(WebPluginDelegateImpl* delegate) { 54 g_active_delegate = delegate; 55 } 56 ~ScopedActiveDelegate() { 57 g_active_delegate = NULL; 58 } 59 60 private: 61 DISALLOW_COPY_AND_ASSIGN(ScopedActiveDelegate); 62 }; 63 64 } // namespace 65 66 // Helper to build and maintain a model of a drag entering the plugin but not 67 // starting there. See explanation in PlatformHandleInputEvent. 68 class ExternalDragTracker { 69 public: 70 ExternalDragTracker() : pressed_buttons_(0) {} 71 72 // Returns true if an external drag is in progress. 73 bool IsDragInProgress() { return pressed_buttons_ != 0; }; 74 75 // Returns true if the given event appears to be related to an external drag. 76 bool EventIsRelatedToDrag(const WebInputEvent& event); 77 78 // Updates the tracking of whether an external drag is in progress--and if 79 // so what buttons it involves--based on the given event. 80 void UpdateDragStateFromEvent(const WebInputEvent& event); 81 82 private: 83 // Returns the mask for just the button state in a WebInputEvent's modifiers. 84 static int WebEventButtonModifierMask(); 85 86 // The WebInputEvent modifier flags for any buttons that were down when an 87 // external drag entered the plugin, and which and are still down now. 88 int pressed_buttons_; 89 90 DISALLOW_COPY_AND_ASSIGN(ExternalDragTracker); 91 }; 92 93 void ExternalDragTracker::UpdateDragStateFromEvent(const WebInputEvent& event) { 94 switch (event.type) { 95 case WebInputEvent::MouseEnter: 96 pressed_buttons_ = event.modifiers & WebEventButtonModifierMask(); 97 break; 98 case WebInputEvent::MouseUp: { 99 const WebMouseEvent* mouse_event = 100 static_cast<const WebMouseEvent*>(&event); 101 if (mouse_event->button == WebMouseEvent::ButtonLeft) 102 pressed_buttons_ &= ~WebInputEvent::LeftButtonDown; 103 if (mouse_event->button == WebMouseEvent::ButtonMiddle) 104 pressed_buttons_ &= ~WebInputEvent::MiddleButtonDown; 105 if (mouse_event->button == WebMouseEvent::ButtonRight) 106 pressed_buttons_ &= ~WebInputEvent::RightButtonDown; 107 break; 108 } 109 default: 110 break; 111 } 112 } 113 114 bool ExternalDragTracker::EventIsRelatedToDrag(const WebInputEvent& event) { 115 const WebMouseEvent* mouse_event = static_cast<const WebMouseEvent*>(&event); 116 switch (event.type) { 117 case WebInputEvent::MouseUp: 118 // We only care about release of buttons that were part of the drag. 119 return ((mouse_event->button == WebMouseEvent::ButtonLeft && 120 (pressed_buttons_ & WebInputEvent::LeftButtonDown)) || 121 (mouse_event->button == WebMouseEvent::ButtonMiddle && 122 (pressed_buttons_ & WebInputEvent::MiddleButtonDown)) || 123 (mouse_event->button == WebMouseEvent::ButtonRight && 124 (pressed_buttons_ & WebInputEvent::RightButtonDown))); 125 case WebInputEvent::MouseEnter: 126 return (event.modifiers & WebEventButtonModifierMask()) != 0; 127 case WebInputEvent::MouseLeave: 128 case WebInputEvent::MouseMove: { 129 int event_buttons = (event.modifiers & WebEventButtonModifierMask()); 130 return (pressed_buttons_ && 131 pressed_buttons_ == event_buttons); 132 } 133 default: 134 return false; 135 } 136 return false; 137 } 138 139 int ExternalDragTracker::WebEventButtonModifierMask() { 140 return WebInputEvent::LeftButtonDown | 141 WebInputEvent::RightButtonDown | 142 WebInputEvent::MiddleButtonDown; 143 } 144 145 #pragma mark - 146 #pragma mark Core WebPluginDelegate implementation 147 148 WebPluginDelegateImpl::WebPluginDelegateImpl( 149 WebPlugin* plugin, 150 PluginInstance* instance) 151 : windowed_handle_(gfx::kNullPluginWindow), 152 // all Mac plugins are "windowless" in the Windows/X11 sense 153 windowless_(true), 154 plugin_(plugin), 155 instance_(instance), 156 quirks_(0), 157 use_buffer_context_(true), 158 buffer_context_(NULL), 159 layer_(nil), 160 surface_(NULL), 161 renderer_(nil), 162 containing_window_has_focus_(false), 163 initial_window_focus_(false), 164 container_is_visible_(false), 165 have_called_set_window_(false), 166 ime_enabled_(false), 167 keyup_ignore_count_(0), 168 external_drag_tracker_(new ExternalDragTracker()), 169 handle_event_depth_(0), 170 first_set_window_call_(true), 171 plugin_has_focus_(false), 172 has_webkit_focus_(false), 173 containing_view_has_focus_(true), 174 creation_succeeded_(false) { 175 memset(&window_, 0, sizeof(window_)); 176 instance->set_windowless(true); 177 } 178 179 WebPluginDelegateImpl::~WebPluginDelegateImpl() { 180 DestroyInstance(); 181 } 182 183 bool WebPluginDelegateImpl::PlatformInitialize() { 184 // Don't set a NULL window handle on destroy for Mac plugins. This matches 185 // Safari and other Mac browsers (see PluginView::stop() in PluginView.cpp, 186 // where code to do so is surrounded by an #ifdef that excludes Mac OS X, or 187 // destroyPlugin in WebNetscapePluginView.mm, for examples). 188 quirks_ |= PLUGIN_QUIRK_DONT_SET_NULL_WINDOW_HANDLE_ON_DESTROY; 189 190 // Mac plugins don't expect to be unloaded, and they don't always do so 191 // cleanly, so don't unload them at shutdown. 192 instance()->plugin_lib()->PreventLibraryUnload(); 193 194 #ifndef NP_NO_CARBON 195 if (instance()->event_model() == NPEventModelCarbon) 196 return false; 197 #endif 198 199 window_.type = NPWindowTypeDrawable; 200 NPDrawingModel drawing_model = instance()->drawing_model(); 201 switch (drawing_model) { 202 #ifndef NP_NO_QUICKDRAW 203 case NPDrawingModelQuickDraw: 204 return false; 205 #endif 206 case NPDrawingModelCoreGraphics: 207 break; 208 case NPDrawingModelCoreAnimation: 209 case NPDrawingModelInvalidatingCoreAnimation: { 210 // Ask the plug-in for the CALayer it created for rendering content. 211 // Create a surface to host it, and request a "window" handle to identify 212 // the surface. 213 CALayer* layer = nil; 214 NPError err = instance()->NPP_GetValue(NPPVpluginCoreAnimationLayer, 215 reinterpret_cast<void*>(&layer)); 216 if (!err) { 217 if (drawing_model == NPDrawingModelCoreAnimation) { 218 // Create the timer; it will be started when we get a window handle. 219 redraw_timer_.reset(new base::RepeatingTimer<WebPluginDelegateImpl>); 220 } 221 layer_ = layer; 222 223 gfx::GpuPreference gpu_preference = gfx::PreferDiscreteGpu; 224 // On dual GPU systems, force the use of the discrete GPU for 225 // the CARenderer underlying our Core Animation backend for 226 // all plugins except Flash. For some reason Unity3D's output 227 // doesn't show up if the integrated GPU is used. Safari keeps 228 // even Flash 11 with Stage3D on the integrated GPU, so mirror 229 // that behavior here. 230 const WebPluginInfo& plugin_info = 231 instance_->plugin_lib()->plugin_info(); 232 if (plugin_info.name.find(ASCIIToUTF16("Flash")) != 233 base::string16::npos) 234 gpu_preference = gfx::PreferIntegratedGpu; 235 surface_ = plugin_->GetAcceleratedSurface(gpu_preference); 236 237 // If surface initialization fails for some reason, just continue 238 // without any drawing; returning false would be a more confusing user 239 // experience (since it triggers a missing plugin placeholder). 240 if (surface_ && surface_->context()) { 241 renderer_ = [[CARenderer rendererWithCGLContext:surface_->context() 242 options:NULL] retain]; 243 [renderer_ setLayer:layer_]; 244 plugin_->AcceleratedPluginEnabledRendering(); 245 } 246 } 247 break; 248 } 249 default: 250 NOTREACHED(); 251 break; 252 } 253 254 // Let the WebPlugin know that we are windowless, unless this is a Core 255 // Animation plugin, in which case AcceleratedPluginEnabledRendering 256 // calls SetWindow. Rendering breaks if SetWindow is called before 257 // accelerated rendering is enabled. 258 if (!layer_) 259 plugin_->SetWindow(gfx::kNullPluginWindow); 260 261 return true; 262 } 263 264 void WebPluginDelegateImpl::PlatformDestroyInstance() { 265 if (redraw_timer_) 266 redraw_timer_->Stop(); 267 [renderer_ release]; 268 renderer_ = nil; 269 layer_ = nil; 270 } 271 272 void WebPluginDelegateImpl::UpdateGeometryAndContext( 273 const gfx::Rect& window_rect, const gfx::Rect& clip_rect, 274 CGContextRef context) { 275 buffer_context_ = context; 276 UpdateGeometry(window_rect, clip_rect); 277 } 278 279 void WebPluginDelegateImpl::Paint(SkCanvas* canvas, const gfx::Rect& rect) { 280 gfx::SkiaBitLocker bit_locker(canvas); 281 CGContextRef context = bit_locker.cgContext(); 282 CGPaint(context, rect); 283 } 284 285 void WebPluginDelegateImpl::CGPaint(CGContextRef context, 286 const gfx::Rect& rect) { 287 WindowlessPaint(context, rect); 288 } 289 290 bool WebPluginDelegateImpl::PlatformHandleInputEvent( 291 const WebInputEvent& event, WebCursor::CursorInfo* cursor_info) { 292 DCHECK(cursor_info != NULL); 293 294 // If an event comes in before the plugin has been set up, bail. 295 if (!have_called_set_window_) 296 return false; 297 298 // WebKit sometimes sends spurious mouse move events when the window doesn't 299 // have focus; Cocoa event model plugins don't expect to receive mouse move 300 // events when they are in a background window, so drop those events. 301 if (!containing_window_has_focus_ && 302 (event.type == WebInputEvent::MouseMove || 303 event.type == WebInputEvent::MouseEnter || 304 event.type == WebInputEvent::MouseLeave)) { 305 return false; 306 } 307 308 if (WebInputEvent::isMouseEventType(event.type) || 309 event.type == WebInputEvent::MouseWheel) { 310 // Check our plugin location before we send the event to the plugin, just 311 // in case we somehow missed a plugin frame change. 312 const WebMouseEvent* mouse_event = 313 static_cast<const WebMouseEvent*>(&event); 314 gfx::Point content_origin( 315 mouse_event->globalX - mouse_event->x - window_rect_.x(), 316 mouse_event->globalY - mouse_event->y - window_rect_.y()); 317 if (content_origin.x() != content_area_origin_.x() || 318 content_origin.y() != content_area_origin_.y()) { 319 DLOG(WARNING) << "Stale plugin content area location: " 320 << content_area_origin_.ToString() << " instead of " 321 << content_origin.ToString(); 322 SetContentAreaOrigin(content_origin); 323 } 324 325 current_windowless_cursor_.GetCursorInfo(cursor_info); 326 } 327 328 // Per the Cocoa Plugin IME spec, plugins shoudn't receive keydown or keyup 329 // events while composition is in progress. Treat them as handled, however, 330 // since IME is consuming them on behalf of the plugin. 331 if ((event.type == WebInputEvent::KeyDown && ime_enabled_) || 332 (event.type == WebInputEvent::KeyUp && keyup_ignore_count_)) { 333 // Composition ends on a keydown, so ime_enabled_ will be false at keyup; 334 // because the keydown wasn't sent to the plugin, the keyup shouldn't be 335 // either (per the spec). 336 if (event.type == WebInputEvent::KeyDown) 337 ++keyup_ignore_count_; 338 else 339 --keyup_ignore_count_; 340 return true; 341 } 342 343 ScopedActiveDelegate active_delegate(this); 344 345 // Create the plugin event structure. 346 scoped_ptr<PluginWebEventConverter> event_converter( 347 new PluginWebEventConverter); 348 if (!event_converter->InitWithEvent(event)) { 349 // Silently consume any keyboard event types that aren't handled, so that 350 // they don't fall through to the page. 351 if (WebInputEvent::isKeyboardEventType(event.type)) 352 return true; 353 return false; 354 } 355 NPCocoaEvent* plugin_event = event_converter->plugin_event(); 356 357 // The plugin host recieves events related to drags starting outside the 358 // plugin, but the NPAPI Cocoa event model spec says plugins shouldn't receive 359 // them, so filter them out. 360 // If WebKit adds a page capture mode (like the plugin capture mode that 361 // handles drags starting inside) this can be removed. 362 bool drag_related = external_drag_tracker_->EventIsRelatedToDrag(event); 363 external_drag_tracker_->UpdateDragStateFromEvent(event); 364 if (drag_related) { 365 if (event.type == WebInputEvent::MouseUp && 366 !external_drag_tracker_->IsDragInProgress()) { 367 // When an external drag ends, we need to synthesize a MouseEntered. 368 NPCocoaEvent enter_event = *plugin_event; 369 enter_event.type = NPCocoaEventMouseEntered; 370 ScopedCurrentPluginEvent event_scope(instance(), &enter_event); 371 instance()->NPP_HandleEvent(&enter_event); 372 } 373 return false; 374 } 375 376 // Send the plugin the event. 377 scoped_ptr<ScopedCurrentPluginEvent> event_scope( 378 new ScopedCurrentPluginEvent(instance(), plugin_event)); 379 int16_t handle_response = instance()->NPP_HandleEvent(plugin_event); 380 bool handled = handle_response != kNPEventNotHandled; 381 382 // Start IME if requested by the plugin. 383 if (handled && handle_response == kNPEventStartIME && 384 event.type == WebInputEvent::KeyDown) { 385 StartIme(); 386 ++keyup_ignore_count_; 387 } 388 389 // Plugins don't give accurate information about whether or not they handled 390 // events, so browsers on the Mac ignore the return value. 391 // Scroll events are the exception, since the Cocoa spec defines a meaning 392 // for the return value. 393 if (WebInputEvent::isMouseEventType(event.type)) { 394 handled = true; 395 } else if (WebInputEvent::isKeyboardEventType(event.type)) { 396 // For Command-key events, trust the return value since eating all menu 397 // shortcuts is not ideal. 398 // TODO(stuartmorgan): Implement the advanced key handling spec, and trust 399 // trust the key event return value from plugins that implement it. 400 if (!(event.modifiers & WebInputEvent::MetaKey)) 401 handled = true; 402 } 403 404 return handled; 405 } 406 407 #pragma mark - 408 409 void WebPluginDelegateImpl::WindowlessUpdateGeometry( 410 const gfx::Rect& window_rect, 411 const gfx::Rect& clip_rect) { 412 gfx::Rect old_clip_rect = clip_rect_; 413 cached_clip_rect_ = clip_rect; 414 if (container_is_visible_) // Remove check when cached_clip_rect_ is removed. 415 clip_rect_ = clip_rect; 416 bool clip_rect_changed = (clip_rect_ != old_clip_rect); 417 bool window_size_changed = (window_rect.size() != window_rect_.size()); 418 419 if (window_rect == window_rect_ && !clip_rect_changed) 420 return; 421 422 if (old_clip_rect.IsEmpty() != clip_rect_.IsEmpty()) { 423 PluginVisibilityChanged(); 424 } 425 426 SetPluginRect(window_rect); 427 428 if (window_size_changed || clip_rect_changed) 429 WindowlessSetWindow(); 430 } 431 432 void WebPluginDelegateImpl::WindowlessPaint(gfx::NativeDrawingContext context, 433 const gfx::Rect& damage_rect) { 434 // If we get a paint event before we are completely set up (e.g., a nested 435 // call while the plugin is still in NPP_SetWindow), bail. 436 if (!have_called_set_window_ || (use_buffer_context_ && !buffer_context_)) 437 return; 438 DCHECK(!use_buffer_context_ || buffer_context_ == context); 439 440 base::StatsRate plugin_paint("Plugin.Paint"); 441 base::StatsScope<base::StatsRate> scope(plugin_paint); 442 443 gfx::Rect paint_rect = damage_rect; 444 if (use_buffer_context_) { 445 // Plugin invalidates trigger asynchronous paints with the original 446 // invalidation rect; the plugin may be resized before the paint is handled, 447 // so we need to ensure that the damage rect is still sane. 448 paint_rect.Intersect( 449 gfx::Rect(0, 0, window_rect_.width(), window_rect_.height())); 450 } else { 451 // Use the actual window region when drawing directly to the window context. 452 paint_rect.Intersect(window_rect_); 453 } 454 455 ScopedActiveDelegate active_delegate(this); 456 457 CGContextSaveGState(context); 458 459 if (!use_buffer_context_) { 460 // Reposition the context origin so that plugins will draw at the correct 461 // location in the window. 462 CGContextClipToRect(context, paint_rect.ToCGRect()); 463 CGContextTranslateCTM(context, window_rect_.x(), window_rect_.y()); 464 } 465 466 NPCocoaEvent paint_event; 467 memset(&paint_event, 0, sizeof(NPCocoaEvent)); 468 paint_event.type = NPCocoaEventDrawRect; 469 paint_event.data.draw.context = context; 470 paint_event.data.draw.x = paint_rect.x(); 471 paint_event.data.draw.y = paint_rect.y(); 472 paint_event.data.draw.width = paint_rect.width(); 473 paint_event.data.draw.height = paint_rect.height(); 474 instance()->NPP_HandleEvent(&paint_event); 475 476 if (use_buffer_context_) { 477 // The backing buffer can change during the call to NPP_HandleEvent, in 478 // which case the old context is (or is about to be) invalid. 479 if (context == buffer_context_) 480 CGContextRestoreGState(context); 481 } else { 482 // Always restore the context to the saved state. 483 CGContextRestoreGState(context); 484 } 485 } 486 487 void WebPluginDelegateImpl::WindowlessSetWindow() { 488 if (!instance()) 489 return; 490 491 window_.x = 0; 492 window_.y = 0; 493 window_.height = window_rect_.height(); 494 window_.width = window_rect_.width(); 495 window_.clipRect.left = clip_rect_.x(); 496 window_.clipRect.top = clip_rect_.y(); 497 window_.clipRect.right = clip_rect_.x() + clip_rect_.width(); 498 window_.clipRect.bottom = clip_rect_.y() + clip_rect_.height(); 499 500 NPError err = instance()->NPP_SetWindow(&window_); 501 502 // Send an appropriate window focus event after the first SetWindow. 503 if (!have_called_set_window_) { 504 have_called_set_window_ = true; 505 SetWindowHasFocus(initial_window_focus_); 506 } 507 508 DCHECK(err == NPERR_NO_ERROR); 509 } 510 511 #pragma mark - 512 513 bool WebPluginDelegateImpl::WindowedCreatePlugin() { 514 NOTREACHED(); 515 return false; 516 } 517 518 void WebPluginDelegateImpl::WindowedDestroyWindow() { 519 NOTREACHED(); 520 } 521 522 bool WebPluginDelegateImpl::WindowedReposition(const gfx::Rect& window_rect, 523 const gfx::Rect& clip_rect) { 524 NOTREACHED(); 525 return false; 526 } 527 528 void WebPluginDelegateImpl::WindowedSetWindow() { 529 NOTREACHED(); 530 } 531 532 #pragma mark - 533 #pragma mark Mac Extensions 534 535 void WebPluginDelegateImpl::PluginDidInvalidate() { 536 if (instance()->drawing_model() == NPDrawingModelInvalidatingCoreAnimation) 537 DrawLayerInSurface(); 538 } 539 540 WebPluginDelegateImpl* WebPluginDelegateImpl::GetActiveDelegate() { 541 return g_active_delegate; 542 } 543 544 void WebPluginDelegateImpl::SetWindowHasFocus(bool has_focus) { 545 // If we get a window focus event before calling SetWindow, just remember the 546 // states (WindowlessSetWindow will then send it on the first call). 547 if (!have_called_set_window_) { 548 initial_window_focus_ = has_focus; 549 return; 550 } 551 552 if (has_focus == containing_window_has_focus_) 553 return; 554 containing_window_has_focus_ = has_focus; 555 556 ScopedActiveDelegate active_delegate(this); 557 NPCocoaEvent focus_event; 558 memset(&focus_event, 0, sizeof(focus_event)); 559 focus_event.type = NPCocoaEventWindowFocusChanged; 560 focus_event.data.focus.hasFocus = has_focus; 561 instance()->NPP_HandleEvent(&focus_event); 562 } 563 564 bool WebPluginDelegateImpl::PlatformSetPluginHasFocus(bool focused) { 565 if (!have_called_set_window_) 566 return false; 567 568 plugin_->FocusChanged(focused); 569 570 ScopedActiveDelegate active_delegate(this); 571 572 NPCocoaEvent focus_event; 573 memset(&focus_event, 0, sizeof(focus_event)); 574 focus_event.type = NPCocoaEventFocusChanged; 575 focus_event.data.focus.hasFocus = focused; 576 instance()->NPP_HandleEvent(&focus_event); 577 578 return true; 579 } 580 581 void WebPluginDelegateImpl::SetContainerVisibility(bool is_visible) { 582 if (is_visible == container_is_visible_) 583 return; 584 container_is_visible_ = is_visible; 585 586 // TODO(stuartmorgan): This is a temporary workarond for 587 // <http://crbug.com/34266>. When that is fixed, the cached_clip_rect_ code 588 // should all be removed. 589 if (is_visible) { 590 clip_rect_ = cached_clip_rect_; 591 } else { 592 clip_rect_.set_width(0); 593 clip_rect_.set_height(0); 594 } 595 596 // If the plugin is changing visibility, let the plugin know. If it's scrolled 597 // off screen (i.e., cached_clip_rect_ is empty), then container visibility 598 // doesn't change anything. 599 if (!cached_clip_rect_.IsEmpty()) { 600 PluginVisibilityChanged(); 601 WindowlessSetWindow(); 602 } 603 604 // When the plugin become visible, send an empty invalidate. If there were any 605 // pending invalidations this will trigger a paint event for the damaged 606 // region, and if not it's a no-op. This is necessary since higher levels 607 // that would normally do this weren't responsible for the clip_rect_ change). 608 if (!clip_rect_.IsEmpty()) 609 instance()->webplugin()->InvalidateRect(gfx::Rect()); 610 } 611 612 void WebPluginDelegateImpl::WindowFrameChanged(const gfx::Rect& window_frame, 613 const gfx::Rect& view_frame) { 614 instance()->set_window_frame(window_frame); 615 SetContentAreaOrigin(gfx::Point(view_frame.x(), view_frame.y())); 616 } 617 618 void WebPluginDelegateImpl::ImeCompositionCompleted( 619 const base::string16& text) { 620 ime_enabled_ = false; 621 622 // If |text| is empty this was just called to tell us composition was 623 // cancelled externally (e.g., the user pressed esc). 624 if (!text.empty()) { 625 NPCocoaEvent text_event; 626 memset(&text_event, 0, sizeof(NPCocoaEvent)); 627 text_event.type = NPCocoaEventTextInput; 628 text_event.data.text.text = 629 reinterpret_cast<NPNSString*>(base::SysUTF16ToNSString(text)); 630 instance()->NPP_HandleEvent(&text_event); 631 } 632 } 633 634 void WebPluginDelegateImpl::SetNSCursor(NSCursor* cursor) { 635 current_windowless_cursor_.InitFromNSCursor(cursor); 636 } 637 638 void WebPluginDelegateImpl::SetNoBufferContext() { 639 use_buffer_context_ = false; 640 } 641 642 #pragma mark - 643 #pragma mark Internal Tracking 644 645 void WebPluginDelegateImpl::SetPluginRect(const gfx::Rect& rect) { 646 bool plugin_size_changed = rect.width() != window_rect_.width() || 647 rect.height() != window_rect_.height(); 648 window_rect_ = rect; 649 PluginScreenLocationChanged(); 650 if (plugin_size_changed) 651 UpdateAcceleratedSurface(); 652 } 653 654 void WebPluginDelegateImpl::SetContentAreaOrigin(const gfx::Point& origin) { 655 content_area_origin_ = origin; 656 PluginScreenLocationChanged(); 657 } 658 659 void WebPluginDelegateImpl::PluginScreenLocationChanged() { 660 gfx::Point plugin_origin(content_area_origin_.x() + window_rect_.x(), 661 content_area_origin_.y() + window_rect_.y()); 662 instance()->set_plugin_origin(plugin_origin); 663 } 664 665 void WebPluginDelegateImpl::PluginVisibilityChanged() { 666 if (instance()->drawing_model() == NPDrawingModelCoreAnimation) { 667 bool plugin_visible = container_is_visible_ && !clip_rect_.IsEmpty(); 668 if (plugin_visible && !redraw_timer_->IsRunning()) { 669 redraw_timer_->Start(FROM_HERE, 670 base::TimeDelta::FromMilliseconds(kCoreAnimationRedrawPeriodMs), 671 this, &WebPluginDelegateImpl::DrawLayerInSurface); 672 } else if (!plugin_visible) { 673 redraw_timer_->Stop(); 674 } 675 } 676 } 677 678 void WebPluginDelegateImpl::StartIme() { 679 if (ime_enabled_) 680 return; 681 ime_enabled_ = true; 682 plugin_->StartIme(); 683 } 684 685 #pragma mark - 686 #pragma mark Core Animation Support 687 688 void WebPluginDelegateImpl::DrawLayerInSurface() { 689 // If we haven't plumbed up the surface yet, don't try to draw. 690 if (!renderer_) 691 return; 692 693 [renderer_ beginFrameAtTime:CACurrentMediaTime() timeStamp:NULL]; 694 if (CGRectIsEmpty([renderer_ updateBounds])) { 695 // If nothing has changed, we are done. 696 [renderer_ endFrame]; 697 return; 698 } 699 700 surface_->StartDrawing(); 701 702 CGRect layerRect = [layer_ bounds]; 703 [renderer_ addUpdateRect:layerRect]; 704 [renderer_ render]; 705 [renderer_ endFrame]; 706 707 surface_->EndDrawing(); 708 } 709 710 // Update the size of the surface to match the current size of the plug-in. 711 void WebPluginDelegateImpl::UpdateAcceleratedSurface() { 712 if (!surface_ || !layer_) 713 return; 714 715 [CATransaction begin]; 716 [CATransaction setValue:[NSNumber numberWithInt:0] 717 forKey:kCATransactionAnimationDuration]; 718 [layer_ setFrame:CGRectMake(0, 0, 719 window_rect_.width(), window_rect_.height())]; 720 [CATransaction commit]; 721 722 [renderer_ setBounds:[layer_ bounds]]; 723 surface_->SetSize(window_rect_.size()); 724 // Kick off the drawing timer, if necessary. 725 PluginVisibilityChanged(); 726 } 727 728 } // namespace content 729