Home | History | Annotate | Download | only in npapi
      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 #include <gtk/gtk.h>
      8 #include <gdk/gdkx.h>
      9 
     10 #include <string>
     11 #include <vector>
     12 
     13 #include "base/metrics/stats_counters.h"
     14 #include "content/child/npapi/plugin_instance.h"
     15 #include "content/child/npapi/webplugin.h"
     16 #include "content/public/common/content_constants.h"
     17 #include "skia/ext/platform_canvas.h"
     18 #include "third_party/WebKit/public/web/WebInputEvent.h"
     19 #include "ui/base/gtk/gtk_compat.h"
     20 #include "ui/gfx/blit.h"
     21 #include "webkit/common/cursors/webcursor.h"
     22 
     23 #include "third_party/npapi/bindings/npapi_x11.h"
     24 
     25 using WebKit::WebKeyboardEvent;
     26 using WebKit::WebInputEvent;
     27 using WebKit::WebMouseEvent;
     28 
     29 namespace content {
     30 
     31 WebPluginDelegateImpl::WebPluginDelegateImpl(
     32     PluginInstance* instance)
     33     : windowed_handle_(0),
     34       windowed_did_set_window_(false),
     35       windowless_(false),
     36       plugin_(NULL),
     37       instance_(instance),
     38       windowless_shm_pixmap_(None),
     39       pixmap_(NULL),
     40       first_event_time_(-1.0),
     41       plug_(NULL),
     42       socket_(NULL),
     43       quirks_(0),
     44       handle_event_depth_(0),
     45       first_set_window_call_(true),
     46       plugin_has_focus_(false),
     47       has_webkit_focus_(false),
     48       containing_view_has_focus_(true),
     49       creation_succeeded_(false) {
     50   memset(&window_, 0, sizeof(window_));
     51   if (instance_->mime_type() == kFlashPluginSwfMimeType) {
     52     // Flash is tied to Firefox's whacky behavior with windowless plugins. See
     53     // comments in WindowlessPaint.
     54     // TODO(viettrungluu): PLUGIN_QUIRK_WINDOWLESS_NO_RIGHT_CLICK: Don't allow
     55     // right-clicks in windowless content since Flash 10.1 (initial release, at
     56     // least) hangs in that case. Remove this once Flash is fixed.
     57     quirks_ |= PLUGIN_QUIRK_WINDOWLESS_OFFSET_WINDOW_TO_DRAW
     58         | PLUGIN_QUIRK_WINDOWLESS_INVALIDATE_AFTER_SET_WINDOW
     59         | PLUGIN_QUIRK_WINDOWLESS_NO_RIGHT_CLICK;
     60   }
     61 
     62   // TODO(evanm): I played with this for quite a while but couldn't
     63   // figure out a way to make Flash not crash unless I didn't call
     64   // NPP_SetWindow.
     65   // However, after piman's grand refactor of windowed plugins, maybe
     66   // this is no longer necessary.
     67   quirks_ |= PLUGIN_QUIRK_DONT_SET_NULL_WINDOW_HANDLE_ON_DESTROY;
     68 }
     69 
     70 WebPluginDelegateImpl::~WebPluginDelegateImpl() {
     71   DestroyInstance();
     72 
     73   if (!windowless_)
     74     WindowedDestroyWindow();
     75 
     76   if (window_.ws_info) {
     77     // We only ever use ws_info as an NPSetWindowCallbackStruct.
     78     delete static_cast<NPSetWindowCallbackStruct*>(window_.ws_info);
     79   }
     80 
     81   if (pixmap_) {
     82     g_object_unref(pixmap_);
     83     pixmap_ = NULL;
     84   }
     85 }
     86 
     87 bool WebPluginDelegateImpl::PlatformInitialize() {
     88   gfx::PluginWindowHandle handle =
     89       windowless_ ? 0 : gtk_plug_get_id(GTK_PLUG(plug_));
     90   plugin_->SetWindow(handle);
     91   return true;
     92 }
     93 
     94 void WebPluginDelegateImpl::PlatformDestroyInstance() {
     95   // Nothing to do here.
     96 }
     97 
     98 void WebPluginDelegateImpl::Paint(WebKit::WebCanvas* canvas,
     99                                   const gfx::Rect& rect) {
    100   if (!windowless_ || !skia::SupportsPlatformPaint(canvas))
    101     return;
    102   skia::ScopedPlatformPaint scoped_platform_paint(canvas);
    103   cairo_t* context = scoped_platform_paint.GetPlatformSurface();
    104   WindowlessPaint(context, rect);
    105 }
    106 
    107 bool WebPluginDelegateImpl::WindowedCreatePlugin() {
    108   DCHECK(!windowed_handle_);
    109   DCHECK(!plug_);
    110 
    111   // NPP_GetValue() might write 4 bytes of data to this variable.  Don't use a
    112   // single byte bool, use an int instead and make sure it is initialized.
    113   int xembed = 0;
    114   NPError err = instance_->NPP_GetValue(NPPVpluginNeedsXEmbed, &xembed);
    115   if (err != NPERR_NO_ERROR || !xembed) {
    116     NOTIMPLEMENTED() << " windowed plugin but without xembed. "
    117       "See http://code.google.com/p/chromium/issues/detail?id=38229";
    118     return false;
    119   }
    120 
    121   // Passing 0 as the socket XID creates a plug without plugging it in a socket
    122   // yet, so that it can be latter added with gtk_socket_add_id().
    123   plug_ = gtk_plug_new(0);
    124   gtk_widget_show(plug_);
    125   socket_ = gtk_socket_new();
    126   gtk_widget_show(socket_);
    127   gtk_container_add(GTK_CONTAINER(plug_), socket_);
    128   gtk_widget_show_all(plug_);
    129 
    130   // Prevent the plug from being destroyed if the browser kills the container
    131   // window.
    132   g_signal_connect(plug_, "delete-event", G_CALLBACK(gtk_true), NULL);
    133   // Prevent the socket from being destroyed when the plugin removes itself.
    134   g_signal_connect(socket_, "plug_removed", G_CALLBACK(gtk_true), NULL);
    135 
    136   windowed_handle_ = gtk_socket_get_id(GTK_SOCKET(socket_));
    137 
    138   window_.window = reinterpret_cast<void*>(windowed_handle_);
    139 
    140   if (!window_.ws_info)
    141     window_.ws_info = new NPSetWindowCallbackStruct;
    142   NPSetWindowCallbackStruct* extra =
    143       static_cast<NPSetWindowCallbackStruct*>(window_.ws_info);
    144   extra->type = NP_SETWINDOW;
    145   extra->display = GDK_DISPLAY();
    146   int screen = DefaultScreen(GDK_DISPLAY());
    147   extra->visual = DefaultVisual(GDK_DISPLAY(), screen);
    148   extra->depth = DefaultDepth(GDK_DISPLAY(), screen);
    149   extra->colormap = DefaultColormap(GDK_DISPLAY(), screen);
    150 
    151   return true;
    152 }
    153 
    154 void WebPluginDelegateImpl::WindowedDestroyWindow() {
    155   if (plug_) {
    156     plugin_->WillDestroyWindow(gtk_plug_get_id(GTK_PLUG(plug_)));
    157 
    158     gtk_widget_destroy(plug_);
    159     plug_ = NULL;
    160     socket_ = NULL;
    161     windowed_handle_ = 0;
    162   }
    163 }
    164 
    165 bool WebPluginDelegateImpl::WindowedReposition(
    166     const gfx::Rect& window_rect,
    167     const gfx::Rect& clip_rect) {
    168   if (window_rect == window_rect_ && clip_rect == clip_rect_)
    169     return false;
    170 
    171   window_rect_ = window_rect;
    172   clip_rect_ = clip_rect;
    173 
    174   return true;
    175 }
    176 
    177 void WebPluginDelegateImpl::WindowedSetWindow() {
    178   if (!instance_.get())
    179     return;
    180 
    181   if (!windowed_handle_) {
    182     NOTREACHED();
    183     return;
    184   }
    185 
    186   // See https://bugzilla.mozilla.org/show_bug.cgi?id=108347
    187   // If we call NPP_SetWindow with a <= 0 width or height, problems arise in
    188   // Flash (and possibly other plugins).
    189   // TODO(piman): the Mozilla code suggests that for the Java plugin, we should
    190   // still call NPP_SetWindow in that case. We need to verify that.
    191   if (window_rect_.width() <= 0 || window_rect_.height() <= 0) {
    192     return;
    193   }
    194 
    195   instance()->set_window_handle(windowed_handle_);
    196 
    197   DCHECK(!instance()->windowless());
    198 
    199   window_.clipRect.top = clip_rect_.y();
    200   window_.clipRect.left = clip_rect_.x();
    201   window_.clipRect.bottom = clip_rect_.y() + clip_rect_.height();
    202   window_.clipRect.right = clip_rect_.x() + clip_rect_.width();
    203   window_.height = window_rect_.height();
    204   window_.width = window_rect_.width();
    205   window_.x = window_rect_.x();
    206   window_.y = window_rect_.y();
    207   window_.type = NPWindowTypeWindow;
    208 
    209   // Reset this flag before entering the instance in case of side-effects.
    210   windowed_did_set_window_ = true;
    211 
    212   NPError err = instance()->NPP_SetWindow(&window_);
    213   DCHECK(err == NPERR_NO_ERROR);
    214 }
    215 
    216 void WebPluginDelegateImpl::WindowlessUpdateGeometry(
    217     const gfx::Rect& window_rect,
    218     const gfx::Rect& clip_rect) {
    219   // Only resend to the instance if the geometry has changed.
    220   if (window_rect == window_rect_ && clip_rect == clip_rect_)
    221     return;
    222 
    223   clip_rect_ = clip_rect;
    224   window_rect_ = window_rect;
    225   WindowlessSetWindow();
    226 }
    227 
    228 void WebPluginDelegateImpl::EnsurePixmapAtLeastSize(int width, int height) {
    229   if (pixmap_) {
    230     gint cur_width, cur_height;
    231     gdk_pixmap_get_size(pixmap_, &cur_width, &cur_height);
    232     if (cur_width >= width && cur_height >= height)
    233       return;  // We are already the appropriate size.
    234 
    235     // Otherwise, we need to recreate ourselves.
    236     g_object_unref(pixmap_);
    237     pixmap_ = NULL;
    238   }
    239 
    240   // |sys_visual| is owned by gdk; we shouldn't free it.
    241   GdkVisual* sys_visual = gdk_visual_get_system();
    242   pixmap_ = gdk_pixmap_new(NULL,  // use width/height/depth params
    243                            std::max(1, width), std::max(1, height),
    244                            sys_visual->depth);
    245   // TODO(erg): Replace this with GdkVisual when we move to GTK3.
    246   GdkColormap* colormap = gdk_colormap_new(gdk_visual_get_system(),
    247                                            FALSE);
    248   gdk_drawable_set_colormap(pixmap_, colormap);
    249   // The GdkDrawable now owns the GdkColormap.
    250   g_object_unref(colormap);
    251 }
    252 
    253 #ifdef DEBUG_RECTANGLES
    254 namespace {
    255 
    256 // Draw a rectangle on a Cairo context.
    257 // Useful for debugging various rectangles involved in drawing plugins.
    258 void DrawDebugRectangle(cairo_t* cairo,
    259                         const gfx::Rect& rect,
    260                         float r, float g, float b) {
    261   cairo_set_source_rgba(cairo, r, g, b, 0.5);
    262   cairo_rectangle(cairo, rect.x(), rect.y(),
    263                   rect.width(), rect.height());
    264   cairo_stroke(cairo);
    265 }
    266 
    267 }  // namespace
    268 #endif
    269 
    270 void WebPluginDelegateImpl::WindowlessPaint(cairo_t* context,
    271                                             const gfx::Rect& damage_rect) {
    272   // Compare to:
    273   // http://mxr.mozilla.org/firefox/source/layout/generic/nsObjectFrame.cpp:
    274   // nsPluginInstanceOwner::Renderer::NativeDraw().
    275 
    276   DCHECK(context);
    277 
    278   // TODO(darin): we should avoid calling NPP_SetWindow here since it may
    279   // cause page layout to be invalidated.
    280 
    281   // The actual dirty region is just the intersection of the plugin window and
    282   // the clip window with the damage region. However, the plugin wants to draw
    283   // relative to the containing window's origin, so our pixmap must be from the
    284   // window's origin down to the bottom-right edge of the dirty region.
    285   //
    286   // Typical case:
    287   // X-----------------------------------+-----------------------------+
    288   // |                                   |                             |
    289   // |    pixmap     +-------------------+                             |
    290   // |               |   damage          |                window       |
    291   // |               |                   |                             |
    292   // |           +---+-------------------+-------------+               |
    293   // |           |   |                   |   clip      |               |
    294   // |       +---+---+-------------------+----------+  |               |
    295   // |       |   |   |                   |          |  |               |
    296   // |       |   |   | draw              |          |  |               |
    297   // |       |   |   |                   |          |  |               |
    298   // +-------+---+---+-------------------+----------+--+               |
    299   // |       |       |                   |          |                  |
    300   // |       |       +-------------------+          |                  |
    301   // |       |                                      |                  |
    302   // |       |        plugin                        |                  |
    303   // |       +--------------------------------------+                  |
    304   // |                                                                 |
    305   // |                                                                 |
    306   // +-----------------------------------------------------------------+
    307   // X = origin
    308   //
    309   // NPAPI doesn't properly define which coordinates each of
    310   // - window.clipRect, window.x and window.y in the SetWindow call
    311   // - x and y in GraphicsExpose HandleEvent call
    312   // are relative to, nor does it define what the pixmap is relative to.
    313   //
    314   // Any sane values for them just don't work with the flash plugin. Firefox
    315   // has some interesting behavior. Experiments showed that:
    316   // - window.clipRect is always in the same space as window.x and window.y
    317   // - in the first SetWindow call, or when scrolling, window.x and window.y are
    318   // the coordinates of the plugin relative to the window.
    319   // - whenever only a part of the plugin is drawn, Firefox issues a SetWindow
    320   // call before each GraphicsExpose event, that sets the drawing origin to
    321   // (0, 0) as if the plugin was scrolled to be partially out of the view. The
    322   // GraphicsExpose event has coordinates relative to the "window" (assuming
    323   // that virtual scroll). The pixmap is also relative to the window. It always
    324   // sets the clip rect to the draw rect.
    325   //
    326   // Attempts to deviate from that makes Flash render at the wrong place in the
    327   // pixmap, or render the wrong pixels.
    328   //
    329   // Flash plugin:
    330   // X-----------------------------------------------------------------+
    331   // |                                                                 |
    332   // |               +-------------------+        "real" window        |
    333   // |               |   damage          |                             |
    334   // |               |                   |                             |
    335   // |           +---+-------------------+-------------+               |
    336   // |           |   |                   | "real" clip |               |
    337   // |       +---+---O===================#==========#==#===============#
    338   // |       |   |   H draw              |          |  |               H
    339   // |       |   |   H = pixmap          |          |  |               H
    340   // |       |   |   H = "apparent" clip |          |  |               H
    341   // |       +   +---#-------------------+----------+--+               H
    342   // |       |       H                   |          |                  H
    343   // |       |       H-------------------+          |                  H
    344   // |       |       H                              |                  H
    345   // |       |       H  plugin                      |                  H
    346   // |       +-------#------------------------------+                  H
    347   // |               H                                                 H
    348   // |               H                  "apparent" window              H
    349   // +---------------#=================================================#
    350   // X = "real" origin
    351   // O = "apparent" origin
    352   // "real" means as seen by Chrome
    353   // "apparent" means as seen by the plugin.
    354 
    355   gfx::Rect draw_rect = gfx::IntersectRects(window_rect_, damage_rect);
    356 
    357   // clip_rect_ is relative to the plugin
    358   gfx::Rect clip_rect_window = clip_rect_;
    359   clip_rect_window.Offset(window_rect_.x(), window_rect_.y());
    360   draw_rect.Intersect(clip_rect_window);
    361 
    362   // These offsets represent by how much the view is shifted to accomodate
    363   // Flash (the coordinates of X relative to O in the diagram above).
    364   int offset_x = 0;
    365   int offset_y = 0;
    366   if (quirks_ & PLUGIN_QUIRK_WINDOWLESS_OFFSET_WINDOW_TO_DRAW) {
    367     offset_x = -draw_rect.x();
    368     offset_y = -draw_rect.y();
    369     window_.clipRect.top = 0;
    370     window_.clipRect.left = 0;
    371     window_.clipRect.bottom = draw_rect.height();
    372     window_.clipRect.right = draw_rect.width();
    373     window_.height = window_rect_.height();
    374     window_.width = window_rect_.width();
    375     window_.x = window_rect_.x() - draw_rect.x();
    376     window_.y = window_rect_.y() - draw_rect.y();
    377     window_.type = NPWindowTypeDrawable;
    378     DCHECK(window_.ws_info);
    379     NPError err = instance()->NPP_SetWindow(&window_);
    380     DCHECK_EQ(err, NPERR_NO_ERROR);
    381   }
    382 
    383   gfx::Rect pixmap_draw_rect = draw_rect;
    384   pixmap_draw_rect.Offset(offset_x, offset_y);
    385 
    386   gfx::Rect pixmap_rect(0, 0,
    387                         pixmap_draw_rect.right(),
    388                         pixmap_draw_rect.bottom());
    389 
    390   // Construct the paint message, targeting the pixmap.
    391   NPEvent np_event = {0};
    392   XGraphicsExposeEvent& event = np_event.xgraphicsexpose;
    393   event.type = GraphicsExpose;
    394   event.x = pixmap_draw_rect.x();
    395   event.y = pixmap_draw_rect.y();
    396   event.width = pixmap_draw_rect.width();
    397   event.height = pixmap_draw_rect.height();
    398   event.display = GDK_DISPLAY();
    399 
    400   if (windowless_shm_pixmap_ != None) {
    401     Pixmap pixmap = None;
    402     GC xgc = NULL;
    403     Display* display = event.display;
    404     gfx::Rect plugin_draw_rect = draw_rect;
    405 
    406     // Make plugin_draw_rect relative to the plugin window.
    407     plugin_draw_rect.Offset(-window_rect_.x(), -window_rect_.y());
    408 
    409     // In case the drawing area does not start with the plugin window origin,
    410     // we can not let the plugin directly draw over the shared memory pixmap.
    411     if (plugin_draw_rect.x() != pixmap_draw_rect.x() ||
    412         plugin_draw_rect.y() != pixmap_draw_rect.y()) {
    413       pixmap = XCreatePixmap(display, windowless_shm_pixmap_,
    414                              std::max(1, pixmap_rect.width()),
    415                              std::max(1, pixmap_rect.height()),
    416                              DefaultDepth(display, DefaultScreen(display)));
    417       xgc = XCreateGC(display, windowless_shm_pixmap_, 0, NULL);
    418       // Copy the current image into the pixmap, so the plugin can draw over it.
    419       XCopyArea(display, windowless_shm_pixmap_, pixmap, xgc,
    420                 plugin_draw_rect.x(), plugin_draw_rect.y(),
    421                 pixmap_draw_rect.width(), pixmap_draw_rect.height(),
    422                 pixmap_draw_rect.x(), pixmap_draw_rect.y());
    423 
    424       event.drawable = pixmap;
    425     } else {
    426       event.drawable = windowless_shm_pixmap_;
    427     }
    428 
    429     // Tell the plugin to paint into the pixmap.
    430     base::StatsRate plugin_paint("Plugin.Paint");
    431     base::StatsScope<base::StatsRate> scope(plugin_paint);
    432     instance()->NPP_HandleEvent(&np_event);
    433 
    434     if (pixmap != None) {
    435       // Copy the rendered image pixmap back into the shm pixmap
    436       // and thus the drawing buffer.
    437       XCopyArea(display, pixmap, windowless_shm_pixmap_, xgc,
    438                 pixmap_draw_rect.x(), pixmap_draw_rect.y(),
    439                 pixmap_draw_rect.width(), pixmap_draw_rect.height(),
    440                 plugin_draw_rect.x(), plugin_draw_rect.y());
    441       XSync(display, FALSE);
    442       if (xgc)
    443         XFreeGC(display, xgc);
    444       XFreePixmap(display, pixmap);
    445     } else {
    446       XSync(display, FALSE);
    447     }
    448   } else {
    449     EnsurePixmapAtLeastSize(pixmap_rect.width(), pixmap_rect.height());
    450 
    451     // Copy the current image into the pixmap, so the plugin can draw over
    452     // this background.
    453     cairo_t* cairo = gdk_cairo_create(pixmap_);
    454     BlitContextToContext(cairo, pixmap_draw_rect, context, draw_rect.origin());
    455     cairo_destroy(cairo);
    456 
    457     event.drawable = GDK_PIXMAP_XID(pixmap_);
    458 
    459     // Tell the plugin to paint into the pixmap.
    460     base::StatsRate plugin_paint("Plugin.Paint");
    461     base::StatsScope<base::StatsRate> scope(plugin_paint);
    462     instance()->NPP_HandleEvent(&np_event);
    463 
    464     cairo_save(context);
    465     // Now copy the rendered image pixmap back into the drawing buffer.
    466     gdk_cairo_set_source_pixmap(context, pixmap_, -offset_x, -offset_y);
    467     cairo_rectangle(context, draw_rect.x(), draw_rect.y(),
    468                     draw_rect.width(), draw_rect.height());
    469     cairo_clip(context);
    470     cairo_paint(context);
    471 
    472 #ifdef DEBUG_RECTANGLES
    473     // Draw some debugging rectangles.
    474     // Pixmap rect = blue.
    475     DrawDebugRectangle(context, pixmap_rect, 0, 0, 1);
    476     // Drawing rect = red.
    477     DrawDebugRectangle(context, draw_rect, 1, 0, 0);
    478 #endif
    479     cairo_restore(context);
    480   }
    481 }
    482 
    483 void WebPluginDelegateImpl::WindowlessSetWindow() {
    484   if (!instance())
    485     return;
    486 
    487   if (window_rect_.IsEmpty())  // wait for geometry to be set.
    488     return;
    489 
    490   DCHECK(instance()->windowless());
    491   // Mozilla docs say that this window param is not used for windowless
    492   // plugins; rather, the window is passed during the GraphicsExpose event.
    493   DCHECK_EQ(window_.window, static_cast<void*>(NULL));
    494 
    495   window_.clipRect.top = clip_rect_.y() + window_rect_.y();
    496   window_.clipRect.left = clip_rect_.x() + window_rect_.x();
    497   window_.clipRect.bottom =
    498       clip_rect_.y() + clip_rect_.height() + window_rect_.y();
    499   window_.clipRect.right =
    500       clip_rect_.x() + clip_rect_.width() + window_rect_.x();
    501   window_.height = window_rect_.height();
    502   window_.width = window_rect_.width();
    503   window_.x = window_rect_.x();
    504   window_.y = window_rect_.y();
    505   window_.type = NPWindowTypeDrawable;
    506 
    507   if (!window_.ws_info)
    508     window_.ws_info = new NPSetWindowCallbackStruct;
    509   NPSetWindowCallbackStruct* extra =
    510       static_cast<NPSetWindowCallbackStruct*>(window_.ws_info);
    511   extra->display = GDK_DISPLAY();
    512   int screen = DefaultScreen(GDK_DISPLAY());
    513   extra->visual = DefaultVisual(GDK_DISPLAY(), screen);
    514   extra->depth = DefaultDepth(GDK_DISPLAY(), screen);
    515   extra->colormap = DefaultColormap(GDK_DISPLAY(), screen);
    516 
    517   NPError err = instance()->NPP_SetWindow(&window_);
    518   DCHECK(err == NPERR_NO_ERROR);
    519   if (quirks_ & PLUGIN_QUIRK_WINDOWLESS_INVALIDATE_AFTER_SET_WINDOW) {
    520     // After a NPP_SetWindow, Flash cancels its timer that generates the
    521     // invalidates until it gets a paint event, but doesn't explicitly call
    522     // NPP_InvalidateRect.
    523     plugin_->InvalidateRect(clip_rect_);
    524   }
    525 }
    526 
    527 bool WebPluginDelegateImpl::PlatformSetPluginHasFocus(bool focused) {
    528   DCHECK(instance()->windowless());
    529 
    530   NPEvent np_event = {0};
    531   XFocusChangeEvent& event = np_event.xfocus;
    532   event.type = focused ? FocusIn : FocusOut;
    533   event.display = GDK_DISPLAY();
    534   // Same values as Firefox. .serial and .window stay 0.
    535   event.mode = -1;
    536   event.detail = NotifyDetailNone;
    537   instance()->NPP_HandleEvent(&np_event);
    538   return true;
    539 }
    540 
    541 // Converts a WebInputEvent::Modifiers bitfield into a
    542 // corresponding X modifier state.
    543 static int GetXModifierState(int modifiers) {
    544   int x_state = 0;
    545   if (modifiers & WebInputEvent::ControlKey)
    546     x_state |= ControlMask;
    547   if (modifiers & WebInputEvent::ShiftKey)
    548     x_state |= ShiftMask;
    549   if (modifiers & WebInputEvent::AltKey)
    550     x_state |= Mod1Mask;
    551   if (modifiers & WebInputEvent::MetaKey)
    552     x_state |= Mod2Mask;
    553   if (modifiers & WebInputEvent::LeftButtonDown)
    554     x_state |= Button1Mask;
    555   if (modifiers & WebInputEvent::MiddleButtonDown)
    556     x_state |= Button2Mask;
    557   if (modifiers & WebInputEvent::RightButtonDown)
    558     x_state |= Button3Mask;
    559   // TODO(piman (at) google.com): There are other modifiers, e.g. Num Lock, that
    560   // should be set (and Firefox does), but we didn't keep the information in
    561   // the WebKit event.
    562   return x_state;
    563 }
    564 
    565 static bool NPEventFromWebMouseEvent(const WebMouseEvent& event,
    566                                      Time timestamp,
    567                                      NPEvent* np_event) {
    568   np_event->xany.display = GDK_DISPLAY();
    569   // NOTE: Firefox keeps xany.serial and xany.window as 0.
    570 
    571   int modifier_state = GetXModifierState(event.modifiers);
    572 
    573   Window root = GDK_ROOT_WINDOW();
    574   switch (event.type) {
    575     case WebInputEvent::MouseMove: {
    576       np_event->type = MotionNotify;
    577       XMotionEvent& motion_event = np_event->xmotion;
    578       motion_event.root = root;
    579       motion_event.time = timestamp;
    580       motion_event.x = event.x;
    581       motion_event.y = event.y;
    582       motion_event.x_root = event.globalX;
    583       motion_event.y_root = event.globalY;
    584       motion_event.state = modifier_state;
    585       motion_event.is_hint = NotifyNormal;
    586       motion_event.same_screen = True;
    587       break;
    588     }
    589     case WebInputEvent::MouseLeave:
    590     case WebInputEvent::MouseEnter: {
    591       if (event.type == WebInputEvent::MouseEnter) {
    592         np_event->type = EnterNotify;
    593       } else {
    594         np_event->type = LeaveNotify;
    595       }
    596       XCrossingEvent& crossing_event = np_event->xcrossing;
    597       crossing_event.root = root;
    598       crossing_event.time = timestamp;
    599       crossing_event.x = event.x;
    600       crossing_event.y = event.y;
    601       crossing_event.x_root = event.globalX;
    602       crossing_event.y_root = event.globalY;
    603       crossing_event.mode = -1;  // This is what Firefox sets it to.
    604       crossing_event.detail = NotifyDetailNone;
    605       crossing_event.same_screen = True;
    606       // TODO(piman (at) google.com): set this to the correct value. Firefox does. I
    607       // don't know where to get the information though, we get focus
    608       // notifications, but no unfocus.
    609       crossing_event.focus = 0;
    610       crossing_event.state = modifier_state;
    611       break;
    612     }
    613     case WebInputEvent::MouseUp:
    614     case WebInputEvent::MouseDown: {
    615       if (event.type == WebInputEvent::MouseDown) {
    616         np_event->type = ButtonPress;
    617       } else {
    618         np_event->type = ButtonRelease;
    619       }
    620       XButtonEvent& button_event = np_event->xbutton;
    621       button_event.root = root;
    622       button_event.time = timestamp;
    623       button_event.x = event.x;
    624       button_event.y = event.y;
    625       button_event.x_root = event.globalX;
    626       button_event.y_root = event.globalY;
    627       button_event.state = modifier_state;
    628       switch (event.button) {
    629         case WebMouseEvent::ButtonLeft:
    630           button_event.button = Button1;
    631           break;
    632         case WebMouseEvent::ButtonMiddle:
    633           button_event.button = Button2;
    634           break;
    635         case WebMouseEvent::ButtonRight:
    636           button_event.button = Button3;
    637           break;
    638         default:
    639           NOTREACHED();
    640       }
    641       button_event.same_screen = True;
    642       break;
    643     }
    644     default:
    645       NOTREACHED();
    646       return false;
    647   }
    648   return true;
    649 }
    650 
    651 static bool NPEventFromWebKeyboardEvent(const WebKeyboardEvent& event,
    652                                         Time timestamp,
    653                                         NPEvent* np_event) {
    654   np_event->xany.display = GDK_DISPLAY();
    655   // NOTE: Firefox keeps xany.serial and xany.window as 0.
    656 
    657   switch (event.type) {
    658     case WebKeyboardEvent::KeyDown:
    659       np_event->type = KeyPress;
    660       break;
    661     case WebKeyboardEvent::KeyUp:
    662       np_event->type = KeyRelease;
    663       break;
    664     default:
    665       NOTREACHED();
    666       return false;
    667   }
    668   XKeyEvent& key_event = np_event->xkey;
    669   key_event.send_event = False;
    670   key_event.display = GDK_DISPLAY();
    671   // NOTE: Firefox keeps xany.serial and xany.window as 0.
    672   // TODO(piman (at) google.com): is this right for multiple screens ?
    673   key_event.root = DefaultRootWindow(key_event.display);
    674   key_event.time = timestamp;
    675   // NOTE: We don't have the correct information for x/y/x_root/y_root. Firefox
    676   // doesn't have it either, so we pass the same values.
    677   key_event.x = 0;
    678   key_event.y = 0;
    679   key_event.x_root = -1;
    680   key_event.y_root = -1;
    681   key_event.state = GetXModifierState(event.modifiers);
    682   key_event.keycode = event.nativeKeyCode;
    683   key_event.same_screen = True;
    684   return true;
    685 }
    686 
    687 static bool NPEventFromWebInputEvent(const WebInputEvent& event,
    688                                      Time timestamp,
    689                                      NPEvent* np_event) {
    690   switch (event.type) {
    691     case WebInputEvent::MouseMove:
    692     case WebInputEvent::MouseLeave:
    693     case WebInputEvent::MouseEnter:
    694     case WebInputEvent::MouseDown:
    695     case WebInputEvent::MouseUp:
    696       if (event.size < sizeof(WebMouseEvent)) {
    697         NOTREACHED();
    698         return false;
    699       }
    700       return NPEventFromWebMouseEvent(
    701           *static_cast<const WebMouseEvent*>(&event), timestamp, np_event);
    702     case WebInputEvent::KeyDown:
    703     case WebInputEvent::KeyUp:
    704       if (event.size < sizeof(WebKeyboardEvent)) {
    705         NOTREACHED();
    706         return false;
    707       }
    708       return NPEventFromWebKeyboardEvent(
    709           *static_cast<const WebKeyboardEvent*>(&event), timestamp, np_event);
    710     default:
    711       return false;
    712   }
    713 }
    714 
    715 bool WebPluginDelegateImpl::PlatformHandleInputEvent(
    716     const WebInputEvent& event, WebCursor::CursorInfo* cursor_info) {
    717 
    718   if (first_event_time_ < 0.0)
    719     first_event_time_ = event.timeStampSeconds;
    720   Time timestamp = static_cast<Time>(
    721       (event.timeStampSeconds - first_event_time_) * 1.0e3);
    722   NPEvent np_event = {0};
    723   if (!NPEventFromWebInputEvent(event, timestamp, &np_event)) {
    724     return false;
    725   }
    726   // See comment about PLUGIN_QUIRK_WINDOWLESS_NO_RIGHT_CLICK in constructor.
    727   if (windowless_ &&
    728       (quirks_ & PLUGIN_QUIRK_WINDOWLESS_NO_RIGHT_CLICK) &&
    729       (np_event.type == ButtonPress || np_event.type == ButtonRelease) &&
    730       (np_event.xbutton.button == Button3)) {
    731     return false;
    732   }
    733 
    734   bool ret = instance()->NPP_HandleEvent(&np_event) != 0;
    735 
    736   // Flash always returns false, even when the event is handled.
    737   ret = true;
    738 
    739 #if 0
    740   if (event->event == WM_MOUSEMOVE) {
    741     // Snag a reference to the current cursor ASAP in case the plugin modified
    742     // it. There is a nasty race condition here with the multiprocess browser
    743     // as someone might be setting the cursor in the main process as well.
    744     *cursor = current_windowless_cursor_;
    745   }
    746 #endif
    747 
    748   return ret;
    749 }
    750 
    751 }  // namespace content
    752