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 WebKit::WebKeyboardEvent; 31 using WebKit::WebInputEvent; 32 using WebKit::WebMouseEvent; 33 using WebKit::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 PluginInstance* instance) 150 : windowed_handle_(gfx::kNullPluginWindow), 151 // all Mac plugins are "windowless" in the Windows/X11 sense 152 windowless_(true), 153 plugin_(NULL), 154 instance_(instance), 155 quirks_(0), 156 use_buffer_context_(true), 157 buffer_context_(NULL), 158 layer_(nil), 159 surface_(NULL), 160 renderer_(nil), 161 containing_window_has_focus_(false), 162 initial_window_focus_(false), 163 container_is_visible_(false), 164 have_called_set_window_(false), 165 ime_enabled_(false), 166 keyup_ignore_count_(0), 167 external_drag_tracker_(new ExternalDragTracker()), 168 handle_event_depth_(0), 169 first_set_window_call_(true), 170 plugin_has_focus_(false), 171 has_webkit_focus_(false), 172 containing_view_has_focus_(true), 173 creation_succeeded_(false) { 174 memset(&window_, 0, sizeof(window_)); 175 instance->set_windowless(true); 176 } 177 178 WebPluginDelegateImpl::~WebPluginDelegateImpl() { 179 DestroyInstance(); 180 } 181 182 bool WebPluginDelegateImpl::PlatformInitialize() { 183 // Don't set a NULL window handle on destroy for Mac plugins. This matches 184 // Safari and other Mac browsers (see PluginView::stop() in PluginView.cpp, 185 // where code to do so is surrounded by an #ifdef that excludes Mac OS X, or 186 // destroyPlugin in WebNetscapePluginView.mm, for examples). 187 quirks_ |= PLUGIN_QUIRK_DONT_SET_NULL_WINDOW_HANDLE_ON_DESTROY; 188 189 // Mac plugins don't expect to be unloaded, and they don't always do so 190 // cleanly, so don't unload them at shutdown. 191 instance()->plugin_lib()->PreventLibraryUnload(); 192 193 #ifndef NP_NO_CARBON 194 if (instance()->event_model() == NPEventModelCarbon) 195 return false; 196 #endif 197 198 window_.type = NPWindowTypeDrawable; 199 NPDrawingModel drawing_model = instance()->drawing_model(); 200 switch (drawing_model) { 201 #ifndef NP_NO_QUICKDRAW 202 case NPDrawingModelQuickDraw: 203 return false; 204 #endif 205 case NPDrawingModelCoreGraphics: 206 break; 207 case NPDrawingModelCoreAnimation: 208 case NPDrawingModelInvalidatingCoreAnimation: { 209 // Ask the plug-in for the CALayer it created for rendering content. 210 // Create a surface to host it, and request a "window" handle to identify 211 // the surface. 212 CALayer* layer = nil; 213 NPError err = instance()->NPP_GetValue(NPPVpluginCoreAnimationLayer, 214 reinterpret_cast<void*>(&layer)); 215 if (!err) { 216 if (drawing_model == NPDrawingModelCoreAnimation) { 217 // Create the timer; it will be started when we get a window handle. 218 redraw_timer_.reset(new base::RepeatingTimer<WebPluginDelegateImpl>); 219 } 220 layer_ = layer; 221 222 gfx::GpuPreference gpu_preference = gfx::PreferDiscreteGpu; 223 // On dual GPU systems, force the use of the discrete GPU for 224 // the CARenderer underlying our Core Animation backend for 225 // all plugins except Flash. For some reason Unity3D's output 226 // doesn't show up if the integrated GPU is used. Safari keeps 227 // even Flash 11 with Stage3D on the integrated GPU, so mirror 228 // that behavior here. 229 const WebPluginInfo& plugin_info = 230 instance_->plugin_lib()->plugin_info(); 231 if (plugin_info.name.find(ASCIIToUTF16("Flash")) != 232 base::string16::npos) 233 gpu_preference = gfx::PreferIntegratedGpu; 234 surface_ = plugin_->GetAcceleratedSurface(gpu_preference); 235 236 // If surface initialization fails for some reason, just continue 237 // without any drawing; returning false would be a more confusing user 238 // experience (since it triggers a missing plugin placeholder). 239 if (surface_ && surface_->context()) { 240 renderer_ = [[CARenderer rendererWithCGLContext:surface_->context() 241 options:NULL] retain]; 242 [renderer_ setLayer:layer_]; 243 plugin_->AcceleratedPluginEnabledRendering(); 244 } 245 } 246 break; 247 } 248 default: 249 NOTREACHED(); 250 break; 251 } 252 253 // Let the WebPlugin know that we are windowless, unless this is a Core 254 // Animation plugin, in which case AcceleratedPluginEnabledRendering 255 // calls SetWindow. Rendering breaks if SetWindow is called before 256 // accelerated rendering is enabled. 257 if (!layer_) 258 plugin_->SetWindow(gfx::kNullPluginWindow); 259 260 return true; 261 } 262 263 void WebPluginDelegateImpl::PlatformDestroyInstance() { 264 if (redraw_timer_) 265 redraw_timer_->Stop(); 266 [renderer_ release]; 267 renderer_ = nil; 268 layer_ = nil; 269 } 270 271 void WebPluginDelegateImpl::UpdateGeometryAndContext( 272 const gfx::Rect& window_rect, const gfx::Rect& clip_rect, 273 CGContextRef context) { 274 buffer_context_ = context; 275 UpdateGeometry(window_rect, clip_rect); 276 } 277 278 void WebPluginDelegateImpl::Paint(WebKit::WebCanvas* canvas, 279 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