• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
30using blink::WebKeyboardEvent;
31using blink::WebInputEvent;
32using blink::WebMouseEvent;
33using 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
40namespace content {
41
42namespace {
43
44const int kCoreAnimationRedrawPeriodMs = 10;  // 100 Hz
45
46WebPluginDelegateImpl* 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.
51class 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.
68class 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
93void 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
114bool 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
139int ExternalDragTracker::WebEventButtonModifierMask() {
140  return WebInputEvent::LeftButtonDown |
141         WebInputEvent::RightButtonDown |
142         WebInputEvent::MiddleButtonDown;
143}
144
145#pragma mark -
146#pragma mark Core WebPluginDelegate implementation
147
148WebPluginDelegateImpl::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
179WebPluginDelegateImpl::~WebPluginDelegateImpl() {
180  DestroyInstance();
181}
182
183bool 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
264void WebPluginDelegateImpl::PlatformDestroyInstance() {
265  if (redraw_timer_)
266    redraw_timer_->Stop();
267  [renderer_ release];
268  renderer_ = nil;
269  layer_ = nil;
270}
271
272void 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
279void 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
285void WebPluginDelegateImpl::CGPaint(CGContextRef context,
286                                    const gfx::Rect& rect) {
287  WindowlessPaint(context, rect);
288}
289
290bool 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
409void 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
432void 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
487void 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
513bool WebPluginDelegateImpl::WindowedCreatePlugin() {
514  NOTREACHED();
515  return false;
516}
517
518void WebPluginDelegateImpl::WindowedDestroyWindow() {
519  NOTREACHED();
520}
521
522bool WebPluginDelegateImpl::WindowedReposition(const gfx::Rect& window_rect,
523                                               const gfx::Rect& clip_rect) {
524  NOTREACHED();
525  return false;
526}
527
528void WebPluginDelegateImpl::WindowedSetWindow() {
529  NOTREACHED();
530}
531
532#pragma mark -
533#pragma mark Mac Extensions
534
535void WebPluginDelegateImpl::PluginDidInvalidate() {
536  if (instance()->drawing_model() == NPDrawingModelInvalidatingCoreAnimation)
537    DrawLayerInSurface();
538}
539
540WebPluginDelegateImpl* WebPluginDelegateImpl::GetActiveDelegate() {
541  return g_active_delegate;
542}
543
544void 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
564bool 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
581void 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
612void 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
618void 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
634void WebPluginDelegateImpl::SetNSCursor(NSCursor* cursor) {
635  current_windowless_cursor_.InitFromNSCursor(cursor);
636}
637
638void WebPluginDelegateImpl::SetNoBufferContext() {
639  use_buffer_context_ = false;
640}
641
642#pragma mark -
643#pragma mark Internal Tracking
644
645void 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
654void WebPluginDelegateImpl::SetContentAreaOrigin(const gfx::Point& origin) {
655  content_area_origin_ = origin;
656  PluginScreenLocationChanged();
657}
658
659void 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
665void 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
678void 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
688void 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.
711void 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