1/*
2 * Copyright (C) 2010 Apple Inc. All rights reserved.
3 * Copyright (C) 2010 University of Szeged
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24 * THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#if PLUGIN_ARCHITECTURE(X11) && ENABLE(NETSCAPE_PLUGIN_API)
29
30#include "NetscapePlugin.h"
31
32#include "PluginController.h"
33#include "WebEvent.h"
34#include <WebCore/GraphicsContext.h>
35#include <WebCore/NotImplemented.h>
36
37#if PLATFORM(GTK)
38#include <gtk/gtk.h>
39#ifndef GTK_API_VERSION_2
40#include <gtk/gtkx.h>
41#endif
42#include <gdk/gdkx.h>
43#include <WebCore/GtkVersioning.h>
44#elif PLATFORM(EFL) && defined(HAVE_ECORE_X)
45#include <Ecore_X.h>
46#endif
47
48#if USE(CAIRO)
49#include "PlatformContextCairo.h"
50#include "RefPtrCairo.h"
51#include <cairo/cairo-xlib.h>
52#endif
53
54using namespace WebCore;
55
56namespace WebKit {
57
58static Display* getPluginDisplay()
59{
60#if PLATFORM(GTK)
61    // Since we're a gdk/gtk app, we'll (probably?) have the same X connection as any gdk-based
62    // plugins, so we can return that. We might want to add other implementations here later.
63    return GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
64#elif PLATFORM(EFL) && defined(HAVE_ECORE_X)
65    return static_cast<Display*>(ecore_x_display_get());
66#else
67    return 0;
68#endif
69}
70
71static inline int x11Screen()
72{
73#if PLATFORM(GTK)
74    return gdk_screen_get_number(gdk_screen_get_default());
75#elif PLATFORM(EFL) && defined(HAVE_ECORE_X)
76    return ecore_x_screen_index_get(ecore_x_default_screen_get());
77#else
78    return 0;
79#endif
80}
81
82static inline int displayDepth()
83{
84#if PLATFORM(GTK)
85    return gdk_visual_get_depth(gdk_screen_get_system_visual(gdk_screen_get_default()));
86#elif PLATFORM(EFL) && defined(HAVE_ECORE_X)
87    return ecore_x_default_depth_get(NetscapePlugin::x11HostDisplay(), ecore_x_default_screen_get());
88#else
89    return 0;
90#endif
91}
92
93static inline unsigned long rootWindowID()
94{
95#if PLATFORM(GTK)
96    return GDK_ROOT_WINDOW();
97#elif PLATFORM(EFL) && defined(HAVE_ECORE_X)
98    return ecore_x_window_root_first_get();
99#else
100    return 0;
101#endif
102}
103
104#if PLATFORM(GTK)
105static bool moduleMixesGtkSymbols(Module* module)
106{
107#ifdef GTK_API_VERSION_2
108    return module->functionPointer<gpointer>("gtk_application_get_type");
109#else
110    return module->functionPointer<gpointer>("gtk_object_get_type");
111#endif
112}
113#endif
114
115Display* NetscapePlugin::x11HostDisplay()
116{
117#if PLATFORM(GTK)
118    return GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
119#elif PLATFORM(EFL) && defined(HAVE_ECORE_X)
120    return static_cast<Display*>(ecore_x_display_get());
121#else
122    return 0;
123#endif
124}
125
126#if PLATFORM(GTK)
127static gboolean socketPlugRemovedCallback(GtkSocket*)
128{
129    // Default action is to destroy the GtkSocket, so we just return TRUE here
130    // to be able to reuse the socket. For some obscure reason, newer versions
131    // of flash plugin remove the plug from the socket, probably because the plug
132    // created by the plugin is re-parented.
133    return TRUE;
134}
135#endif
136
137bool NetscapePlugin::platformPostInitializeWindowed(bool needsXEmbed, uint64_t windowID)
138{
139    m_npWindow.type = NPWindowTypeWindow;
140    if (!needsXEmbed) {
141        notImplemented();
142        return false;
143    }
144
145    Display* display = x11HostDisplay();
146
147#if PLATFORM(GTK)
148    // It seems flash needs the socket to be in the same process,
149    // I guess it uses gdk_window_lookup(), so we create a new socket here
150    // containing a plug with the UI process socket embedded.
151    m_platformPluginWidget = gtk_plug_new(static_cast<Window>(windowID));
152    GtkWidget* socket = gtk_socket_new();
153    // Do not show the plug widget until the socket is connected.
154    g_signal_connect_swapped(socket, "plug-added", G_CALLBACK(gtk_widget_show), m_platformPluginWidget);
155    g_signal_connect(socket, "plug-removed", G_CALLBACK(socketPlugRemovedCallback), nullptr);
156    gtk_container_add(GTK_CONTAINER(m_platformPluginWidget), socket);
157    gtk_widget_show(socket);
158
159    m_npWindow.window = GINT_TO_POINTER(gtk_socket_get_id(GTK_SOCKET(socket)));
160    GdkWindow* window = gtk_widget_get_window(socket);
161    NPSetWindowCallbackStruct* callbackStruct = static_cast<NPSetWindowCallbackStruct*>(m_npWindow.ws_info);
162    callbackStruct->display = GDK_WINDOW_XDISPLAY(window);
163    callbackStruct->visual = GDK_VISUAL_XVISUAL(gdk_window_get_visual(window));
164    callbackStruct->depth = gdk_visual_get_depth(gdk_window_get_visual(window));
165    callbackStruct->colormap = XCreateColormap(display, GDK_ROOT_WINDOW(), callbackStruct->visual, AllocNone);
166#else
167    UNUSED_PARAM(windowID);
168#endif
169
170    XFlush(display);
171
172    callSetWindow();
173
174    return true;
175}
176
177bool NetscapePlugin::platformPostInitializeWindowless()
178{
179    Display* display = x11HostDisplay();
180    m_npWindow.type = NPWindowTypeDrawable;
181    m_npWindow.window = 0;
182
183    int depth = displayDepth();
184
185    NPSetWindowCallbackStruct* callbackStruct = static_cast<NPSetWindowCallbackStruct*>(m_npWindow.ws_info);
186    callbackStruct->display = display;
187    callbackStruct->depth = depth;
188
189    XVisualInfo visualTemplate;
190    visualTemplate.screen = x11Screen();
191    visualTemplate.depth = depth;
192    visualTemplate.c_class = TrueColor;
193    int numMatching;
194    XVisualInfo* visualInfo = XGetVisualInfo(display, VisualScreenMask | VisualDepthMask | VisualClassMask,
195                                             &visualTemplate, &numMatching);
196    ASSERT(visualInfo);
197    Visual* visual = visualInfo[0].visual;
198    ASSERT(visual);
199    XFree(visualInfo);
200
201    callbackStruct->visual = visual;
202    callbackStruct->colormap = XCreateColormap(display, rootWindowID(), visual, AllocNone);
203
204    callSetWindow();
205
206    return true;
207}
208
209void NetscapePlugin::platformPreInitialize()
210{
211}
212
213bool NetscapePlugin::platformPostInitialize()
214{
215#if PLATFORM(GTK)
216    if (moduleMixesGtkSymbols(m_pluginModule->module()))
217        return false;
218#endif
219
220    uint64_t windowID = 0;
221    bool needsXEmbed = false;
222    if (m_isWindowed) {
223        NPP_GetValue(NPPVpluginNeedsXEmbed, &needsXEmbed);
224        if (needsXEmbed) {
225            windowID = controller()->createPluginContainer();
226            if (!windowID)
227                return false;
228        } else {
229            notImplemented();
230            return false;
231        }
232    }
233
234    if (!(m_pluginDisplay = getPluginDisplay()))
235        return false;
236
237    NPSetWindowCallbackStruct* callbackStruct = new NPSetWindowCallbackStruct;
238    callbackStruct->type = 0;
239    m_npWindow.ws_info = callbackStruct;
240
241    if (m_isWindowed)
242        return platformPostInitializeWindowed(needsXEmbed, windowID);
243
244    return platformPostInitializeWindowless();
245}
246
247void NetscapePlugin::platformDestroy()
248{
249    NPSetWindowCallbackStruct* callbackStruct = static_cast<NPSetWindowCallbackStruct*>(m_npWindow.ws_info);
250    Display* hostDisplay = x11HostDisplay();
251    XFreeColormap(hostDisplay, callbackStruct->colormap);
252    delete callbackStruct;
253
254    if (m_drawable) {
255        XFreePixmap(hostDisplay, m_drawable);
256        m_drawable = 0;
257    }
258
259#if PLATFORM(GTK)
260    if (m_platformPluginWidget) {
261        gtk_widget_destroy(m_platformPluginWidget);
262        m_platformPluginWidget = 0;
263    }
264#endif
265}
266
267bool NetscapePlugin::platformInvalidate(const IntRect&)
268{
269    notImplemented();
270    return false;
271}
272
273void NetscapePlugin::platformGeometryDidChange()
274{
275    if (m_isWindowed) {
276        uint64_t windowID = 0;
277#if PLATFORM(GTK)
278        windowID = static_cast<uint64_t>(GDK_WINDOW_XID(gtk_plug_get_socket_window(GTK_PLUG(m_platformPluginWidget))));
279#endif
280        controller()->windowedPluginGeometryDidChange(m_frameRectInWindowCoordinates, m_clipRect, windowID);
281        return;
282    }
283
284    Display* display = x11HostDisplay();
285    if (m_drawable)
286        XFreePixmap(display, m_drawable);
287
288    if (m_pluginSize.isEmpty()) {
289        m_drawable = 0;
290        return;
291    }
292
293    m_drawable = XCreatePixmap(display, rootWindowID(), m_pluginSize.width(), m_pluginSize.height(), displayDepth());
294
295    XSync(display, false); // Make sure that the server knows about the Drawable.
296}
297
298void NetscapePlugin::platformVisibilityDidChange()
299{
300    if (!m_isWindowed)
301        return;
302
303    uint64_t windowID = 0;
304#if PLATFORM(GTK)
305    windowID = static_cast<uint64_t>(GDK_WINDOW_XID(gtk_plug_get_socket_window(GTK_PLUG(m_platformPluginWidget))));
306#endif
307    controller()->windowedPluginVisibilityDidChange(m_isVisible, windowID);
308    controller()->windowedPluginGeometryDidChange(m_frameRectInWindowCoordinates, m_clipRect, windowID);
309}
310
311void NetscapePlugin::platformPaint(GraphicsContext* context, const IntRect& dirtyRect, bool /*isSnapshot*/)
312{
313    if (m_isWindowed)
314        return;
315
316    if (!m_isStarted) {
317        // FIXME: we should paint a missing plugin icon.
318        return;
319    }
320
321    if (context->paintingDisabled() || !m_drawable)
322        return;
323
324    XEvent xevent;
325    memset(&xevent, 0, sizeof(XEvent));
326    XGraphicsExposeEvent& exposeEvent = xevent.xgraphicsexpose;
327    exposeEvent.type = GraphicsExpose;
328    exposeEvent.display = x11HostDisplay();
329    exposeEvent.drawable = m_drawable;
330
331    IntRect exposedRect(dirtyRect);
332    exposeEvent.x = exposedRect.x();
333    exposeEvent.y = exposedRect.y();
334
335    // Note: in transparent mode Flash thinks width is the right and height is the bottom.
336    // We should take it into account if we want to support transparency.
337    exposeEvent.width = exposedRect.width();
338    exposeEvent.height = exposedRect.height();
339
340    NPP_HandleEvent(&xevent);
341
342    if (m_pluginDisplay != x11HostDisplay())
343        XSync(m_pluginDisplay, false);
344
345#if PLATFORM(GTK) || (PLATFORM(EFL) && USE(CAIRO))
346    RefPtr<cairo_surface_t> drawableSurface = adoptRef(cairo_xlib_surface_create(m_pluginDisplay,
347                                                                                 m_drawable,
348                                                                                 static_cast<NPSetWindowCallbackStruct*>(m_npWindow.ws_info)->visual,
349                                                                                 m_pluginSize.width(),
350                                                                                 m_pluginSize.height()));
351    cairo_t* cr = context->platformContext()->cr();
352    cairo_save(cr);
353
354    cairo_set_source_surface(cr, drawableSurface.get(), 0, 0);
355
356    cairo_rectangle(cr, exposedRect.x(), exposedRect.y(), exposedRect.width(), exposedRect.height());
357    cairo_clip(cr);
358    cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
359    cairo_paint(cr);
360
361    cairo_restore(cr);
362#else
363    notImplemented();
364#endif
365}
366
367static inline void initializeXEvent(XEvent& event)
368{
369    memset(&event, 0, sizeof(XEvent));
370    event.xany.serial = 0;
371    event.xany.send_event = false;
372    event.xany.display = NetscapePlugin::x11HostDisplay();
373    event.xany.window = 0;
374}
375
376static inline uint64_t xTimeStamp(double timestampInSeconds)
377{
378    return timestampInSeconds * 1000;
379}
380
381static inline unsigned xKeyModifiers(const WebEvent& event)
382{
383    unsigned xModifiers = 0;
384    if (event.controlKey())
385        xModifiers |= ControlMask;
386    if (event.shiftKey())
387        xModifiers |= ShiftMask;
388    if (event.altKey())
389        xModifiers |= Mod1Mask;
390    if (event.metaKey())
391        xModifiers |= Mod4Mask;
392
393    return xModifiers;
394}
395
396template <typename XEventType, typename WebEventType>
397static inline void setCommonMouseEventFields(XEventType& xEvent, const WebEventType& webEvent, const WebCore::IntPoint& pluginLocation)
398{
399    xEvent.root = rootWindowID();
400    xEvent.subwindow = 0;
401    xEvent.time = xTimeStamp(webEvent.timestamp());
402    xEvent.x = webEvent.position().x() - pluginLocation.x();
403    xEvent.y = webEvent.position().y() - pluginLocation.y();
404    xEvent.x_root = webEvent.globalPosition().x();
405    xEvent.y_root = webEvent.globalPosition().y();
406    xEvent.state = xKeyModifiers(webEvent);
407    xEvent.same_screen = true;
408}
409
410static inline void setXMotionEventFields(XEvent& xEvent, const WebMouseEvent& webEvent, const WebCore::IntPoint& pluginLocation)
411{
412    XMotionEvent& xMotion = xEvent.xmotion;
413    setCommonMouseEventFields(xMotion, webEvent, pluginLocation);
414    xMotion.type = MotionNotify;
415}
416
417static inline void setXButtonEventFields(XEvent& xEvent, const WebMouseEvent& webEvent, const WebCore::IntPoint& pluginLocation)
418{
419    XButtonEvent& xButton = xEvent.xbutton;
420    setCommonMouseEventFields(xButton, webEvent, pluginLocation);
421
422    xButton.type = (webEvent.type() == WebEvent::MouseDown) ? ButtonPress : ButtonRelease;
423    switch (webEvent.button()) {
424    case WebMouseEvent::LeftButton:
425        xButton.button = Button1;
426        break;
427    case WebMouseEvent::MiddleButton:
428        xButton.button = Button2;
429        break;
430    case WebMouseEvent::RightButton:
431        xButton.button = Button3;
432        break;
433    default:
434        ASSERT_NOT_REACHED();
435        break;
436    }
437}
438
439static inline void setXButtonEventFieldsByWebWheelEvent(XEvent& xEvent, const WebWheelEvent& webEvent, const WebCore::IntPoint& pluginLocation)
440{
441    XButtonEvent& xButton = xEvent.xbutton;
442    setCommonMouseEventFields(xButton, webEvent, pluginLocation);
443
444    xButton.type = ButtonPress;
445    FloatSize ticks = webEvent.wheelTicks();
446    if (ticks.height()) {
447        if (ticks.height() > 0)
448            xButton.button = 4; // up
449        else
450            xButton.button = 5; // down
451    } else {
452        if (ticks.width() > 0)
453            xButton.button = 6; // left
454        else
455            xButton.button = 7; // right
456    }
457}
458
459static inline void setXCrossingEventFields(XEvent& xEvent, const WebMouseEvent& webEvent, const WebCore::IntPoint& pluginLocation, int type)
460{
461    XCrossingEvent& xCrossing = xEvent.xcrossing;
462    setCommonMouseEventFields(xCrossing, webEvent, pluginLocation);
463
464    xCrossing.type = type;
465    xCrossing.mode = NotifyNormal;
466    xCrossing.detail = NotifyDetailNone;
467    xCrossing.focus = false;
468}
469
470bool NetscapePlugin::platformHandleMouseEvent(const WebMouseEvent& event)
471{
472    if (m_isWindowed)
473        return false;
474
475    if ((event.type() == WebEvent::MouseDown || event.type() == WebEvent::MouseUp)
476         && event.button() == WebMouseEvent::RightButton
477         && quirks().contains(PluginQuirks::IgnoreRightClickInWindowlessMode))
478        return false;
479
480    XEvent xEvent;
481    initializeXEvent(xEvent);
482
483    switch (event.type()) {
484    case WebEvent::MouseDown:
485    case WebEvent::MouseUp:
486        setXButtonEventFields(xEvent, event, convertToRootView(IntPoint()));
487        break;
488    case WebEvent::MouseMove:
489        setXMotionEventFields(xEvent, event, convertToRootView(IntPoint()));
490        break;
491    case WebEvent::NoType:
492    case WebEvent::Wheel:
493    case WebEvent::KeyDown:
494    case WebEvent::KeyUp:
495    case WebEvent::RawKeyDown:
496    case WebEvent::Char:
497#if ENABLE(TOUCH_EVENTS)
498    case WebEvent::TouchStart:
499    case WebEvent::TouchMove:
500    case WebEvent::TouchEnd:
501    case WebEvent::TouchCancel:
502#endif
503        return false;
504    }
505
506    return !NPP_HandleEvent(&xEvent);
507}
508
509// We undefine these constants in npruntime_internal.h to avoid collision
510// with WebKit and platform headers. Values are defined in X.h.
511const int kKeyPressType = 2;
512const int kKeyReleaseType = 3;
513const int kFocusInType = 9;
514const int kFocusOutType = 10;
515
516bool NetscapePlugin::platformHandleWheelEvent(const WebWheelEvent& event)
517{
518    if (m_isWindowed)
519        return false;
520
521    XEvent xEvent;
522    initializeXEvent(xEvent);
523    setXButtonEventFieldsByWebWheelEvent(xEvent, event, convertToRootView(IntPoint()));
524
525    return !NPP_HandleEvent(&xEvent);
526}
527
528void NetscapePlugin::platformSetFocus(bool focusIn)
529{
530    if (m_isWindowed)
531        return;
532
533    XEvent xEvent;
534    initializeXEvent(xEvent);
535    XFocusChangeEvent& focusEvent = xEvent.xfocus;
536    focusEvent.type = focusIn ? kFocusInType : kFocusOutType;
537    focusEvent.mode = NotifyNormal;
538    focusEvent.detail = NotifyDetailNone;
539
540    NPP_HandleEvent(&xEvent);
541}
542
543bool NetscapePlugin::wantsPluginRelativeNPWindowCoordinates()
544{
545    return true;
546}
547
548bool NetscapePlugin::platformHandleMouseEnterEvent(const WebMouseEvent& event)
549{
550    if (m_isWindowed)
551        return false;
552
553    XEvent xEvent;
554    initializeXEvent(xEvent);
555    setXCrossingEventFields(xEvent, event, convertToRootView(IntPoint()), EnterNotify);
556
557    return !NPP_HandleEvent(&xEvent);
558}
559
560bool NetscapePlugin::platformHandleMouseLeaveEvent(const WebMouseEvent& event)
561{
562    if (m_isWindowed)
563        return false;
564
565    XEvent xEvent;
566    initializeXEvent(xEvent);
567    setXCrossingEventFields(xEvent, event, convertToRootView(IntPoint()), LeaveNotify);
568
569    return !NPP_HandleEvent(&xEvent);
570}
571
572static inline void setXKeyEventFields(XEvent& xEvent, const WebKeyboardEvent& webEvent)
573{
574    xEvent.xany.type = (webEvent.type() == WebEvent::KeyDown) ? kKeyPressType : kKeyReleaseType;
575    XKeyEvent& xKey = xEvent.xkey;
576    xKey.root = rootWindowID();
577    xKey.subwindow = 0;
578    xKey.time = xTimeStamp(webEvent.timestamp());
579    xKey.state = xKeyModifiers(webEvent);
580    xKey.keycode = webEvent.nativeVirtualKeyCode();
581
582    xKey.same_screen = true;
583
584    // Key events propagated to the plugin does not need to have position.
585    // source: https://developer.mozilla.org/en/NPEvent
586    xKey.x = 0;
587    xKey.y = 0;
588    xKey.x_root = 0;
589    xKey.y_root = 0;
590}
591
592bool NetscapePlugin::platformHandleKeyboardEvent(const WebKeyboardEvent& event)
593{
594    // We don't generate other types of keyboard events via WebEventFactory.
595    ASSERT(event.type() == WebEvent::KeyDown || event.type() == WebEvent::KeyUp);
596
597    XEvent xEvent;
598    initializeXEvent(xEvent);
599    setXKeyEventFields(xEvent, event);
600
601    return !NPP_HandleEvent(&xEvent);
602}
603
604} // namespace WebKit
605
606#endif // PLUGIN_ARCHITECTURE(X11) && ENABLE(NETSCAPE_PLUGIN_API)
607