Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 *
4 * Copyright (c) 2008, Mozilla Corporation
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * * Redistributions of source code must retain the above copyright notice, this
11 * list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
15 * * Neither the name of the Mozilla Corporation nor the names of its
16 * contributors may be used to endorse or promote products derived from this
17 * software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 *
30 * Contributor(s):
31 * Josh Aas <josh@mozilla.com>
32 * Michael Ventnor <mventnor@mozilla.com>
33 *
34 * ***** END LICENSE BLOCK ***** */
36 #include "nptest_platform.h"
37 #include "npapi.h"
38 #include <pthread.h>
39 #include <gdk/gdk.h>
40 #ifdef MOZ_X11
41 #include <gdk/gdkx.h>
42 #include <X11/extensions/shape.h>
43 #endif
44 #include <glib.h>
45 #include <gtk/gtk.h>
46 #include <unistd.h>
48 #include "mozilla/NullPtr.h"
49 #include "mozilla/IntentionalCrash.h"
51 using namespace std;
53 struct _PlatformData {
54 #ifdef MOZ_X11
55 Display* display;
56 Visual* visual;
57 Colormap colormap;
58 #endif
59 GtkWidget* plug;
60 };
62 bool
63 pluginSupportsWindowMode()
64 {
65 return true;
66 }
68 bool
69 pluginSupportsWindowlessMode()
70 {
71 return true;
72 }
74 bool
75 pluginSupportsAsyncBitmapDrawing()
76 {
77 return false;
78 }
80 NPError
81 pluginInstanceInit(InstanceData* instanceData)
82 {
83 #ifdef MOZ_X11
84 instanceData->platformData = static_cast<PlatformData*>
85 (NPN_MemAlloc(sizeof(PlatformData)));
86 if (!instanceData->platformData)
87 return NPERR_OUT_OF_MEMORY_ERROR;
89 instanceData->platformData->display = nullptr;
90 instanceData->platformData->visual = nullptr;
91 instanceData->platformData->colormap = None;
92 instanceData->platformData->plug = nullptr;
94 return NPERR_NO_ERROR;
95 #else
96 // we only support X11 here, since thats what the plugin system uses
97 return NPERR_INCOMPATIBLE_VERSION_ERROR;
98 #endif
99 }
101 void
102 pluginInstanceShutdown(InstanceData* instanceData)
103 {
104 if (instanceData->hasWidget) {
105 Window window = reinterpret_cast<XID>(instanceData->window.window);
107 if (window != None) {
108 // This window XID should still be valid.
109 // See bug 429604 and bug 454756.
110 XWindowAttributes attributes;
111 if (!XGetWindowAttributes(instanceData->platformData->display, window,
112 &attributes))
113 g_error("XGetWindowAttributes failed at plugin instance shutdown");
114 }
115 }
117 GtkWidget* plug = instanceData->platformData->plug;
118 if (plug) {
119 instanceData->platformData->plug = 0;
120 if (instanceData->cleanupWidget) {
121 // Default/tidy behavior
122 gtk_widget_destroy(plug);
123 } else {
124 // Flash Player style: let the GtkPlug destroy itself on disconnect.
125 g_signal_handlers_disconnect_matched(plug, G_SIGNAL_MATCH_DATA, 0, 0,
126 nullptr, nullptr, instanceData);
127 }
128 }
130 NPN_MemFree(instanceData->platformData);
131 instanceData->platformData = 0;
132 }
134 static void
135 SetCairoRGBA(cairo_t* cairoWindow, uint32_t rgba)
136 {
137 float b = (rgba & 0xFF) / 255.0;
138 float g = ((rgba & 0xFF00) >> 8) / 255.0;
139 float r = ((rgba & 0xFF0000) >> 16) / 255.0;
140 float a = ((rgba & 0xFF000000) >> 24) / 255.0;
142 cairo_set_source_rgba(cairoWindow, r, g, b, a);
143 }
145 static void
146 pluginDrawSolid(InstanceData* instanceData, GdkDrawable* gdkWindow,
147 int x, int y, int width, int height)
148 {
149 cairo_t* cairoWindow = gdk_cairo_create(gdkWindow);
151 if (!instanceData->hasWidget) {
152 NPRect* clip = &instanceData->window.clipRect;
153 cairo_rectangle(cairoWindow, clip->left, clip->top,
154 clip->right - clip->left, clip->bottom - clip->top);
155 cairo_clip(cairoWindow);
156 }
158 GdkRectangle windowRect = { x, y, width, height };
159 gdk_cairo_rectangle(cairoWindow, &windowRect);
160 SetCairoRGBA(cairoWindow, instanceData->scriptableObject->drawColor);
162 cairo_fill(cairoWindow);
163 cairo_destroy(cairoWindow);
164 }
166 static void
167 pluginDrawWindow(InstanceData* instanceData, GdkDrawable* gdkWindow,
168 const GdkRectangle& invalidRect)
169 {
170 NPWindow& window = instanceData->window;
171 // When we have a widget, window.x/y are meaningless since our
172 // widget is always positioned correctly and we just draw into it at 0,0
173 int x = instanceData->hasWidget ? 0 : window.x;
174 int y = instanceData->hasWidget ? 0 : window.y;
175 int width = window.width;
176 int height = window.height;
178 notifyDidPaint(instanceData);
180 if (instanceData->scriptableObject->drawMode == DM_SOLID_COLOR) {
181 // drawing a solid color for reftests
182 pluginDrawSolid(instanceData, gdkWindow,
183 invalidRect.x, invalidRect.y,
184 invalidRect.width, invalidRect.height);
185 return;
186 }
188 NPP npp = instanceData->npp;
189 if (!npp)
190 return;
192 const char* uaString = NPN_UserAgent(npp);
193 if (!uaString)
194 return;
196 GdkGC* gdkContext = gdk_gc_new(gdkWindow);
197 if (!gdkContext)
198 return;
200 if (!instanceData->hasWidget) {
201 NPRect* clip = &window.clipRect;
202 GdkRectangle gdkClip = { clip->left, clip->top, clip->right - clip->left,
203 clip->bottom - clip->top };
204 gdk_gc_set_clip_rectangle(gdkContext, &gdkClip);
205 }
207 // draw a grey background for the plugin frame
208 GdkColor grey;
209 grey.red = grey.blue = grey.green = 32767;
210 gdk_gc_set_rgb_fg_color(gdkContext, &grey);
211 gdk_draw_rectangle(gdkWindow, gdkContext, TRUE, x, y, width, height);
213 // draw a 3-pixel-thick black frame around the plugin
214 GdkColor black;
215 black.red = black.green = black.blue = 0;
216 gdk_gc_set_rgb_fg_color(gdkContext, &black);
217 gdk_gc_set_line_attributes(gdkContext, 3, GDK_LINE_SOLID, GDK_CAP_NOT_LAST, GDK_JOIN_MITER);
218 gdk_draw_rectangle(gdkWindow, gdkContext, FALSE, x + 1, y + 1,
219 width - 3, height - 3);
221 // paint the UA string
222 PangoContext* pangoContext = gdk_pango_context_get();
223 PangoLayout* pangoTextLayout = pango_layout_new(pangoContext);
224 pango_layout_set_width(pangoTextLayout, (width - 10) * PANGO_SCALE);
225 pango_layout_set_text(pangoTextLayout, uaString, -1);
226 gdk_draw_layout(gdkWindow, gdkContext, x + 5, y + 5, pangoTextLayout);
227 g_object_unref(pangoTextLayout);
229 g_object_unref(gdkContext);
230 }
232 static gboolean
233 ExposeWidget(GtkWidget* widget, GdkEventExpose* event,
234 gpointer user_data)
235 {
236 InstanceData* instanceData = static_cast<InstanceData*>(user_data);
237 pluginDrawWindow(instanceData, event->window, event->area);
238 return TRUE;
239 }
241 static gboolean
242 MotionEvent(GtkWidget* widget, GdkEventMotion* event,
243 gpointer user_data)
244 {
245 InstanceData* instanceData = static_cast<InstanceData*>(user_data);
246 instanceData->lastMouseX = event->x;
247 instanceData->lastMouseY = event->y;
248 return TRUE;
249 }
251 static gboolean
252 ButtonEvent(GtkWidget* widget, GdkEventButton* event,
253 gpointer user_data)
254 {
255 InstanceData* instanceData = static_cast<InstanceData*>(user_data);
256 instanceData->lastMouseX = event->x;
257 instanceData->lastMouseY = event->y;
258 if (event->type == GDK_BUTTON_RELEASE) {
259 instanceData->mouseUpEventCount++;
260 }
261 return TRUE;
262 }
264 static gboolean
265 DeleteWidget(GtkWidget* widget, GdkEvent* event, gpointer user_data)
266 {
267 InstanceData* instanceData = static_cast<InstanceData*>(user_data);
268 // Some plugins do not expect the plug to be removed from the socket before
269 // the plugin instance is destroyed. e.g. bug 485125
270 if (instanceData->platformData->plug)
271 g_error("plug removed"); // this aborts
273 return FALSE;
274 }
276 void
277 pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow)
278 {
279 instanceData->window = *newWindow;
281 #ifdef MOZ_X11
282 NPSetWindowCallbackStruct *ws_info =
283 static_cast<NPSetWindowCallbackStruct*>(newWindow->ws_info);
284 instanceData->platformData->display = ws_info->display;
285 instanceData->platformData->visual = ws_info->visual;
286 instanceData->platformData->colormap = ws_info->colormap;
287 #endif
288 }
290 void
291 pluginWidgetInit(InstanceData* instanceData, void* oldWindow)
292 {
293 #ifdef MOZ_X11
294 GtkWidget* oldPlug = instanceData->platformData->plug;
295 if (oldPlug) {
296 instanceData->platformData->plug = 0;
297 gtk_widget_destroy(oldPlug);
298 }
300 GdkNativeWindow nativeWinId =
301 reinterpret_cast<XID>(instanceData->window.window);
303 /* create a GtkPlug container */
304 GtkWidget* plug = gtk_plug_new(nativeWinId);
306 // Test for bugs 539138 and 561308
307 if (!plug->window)
308 g_error("Plug has no window"); // aborts
310 /* make sure the widget is capable of receiving focus */
311 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(plug), GTK_CAN_FOCUS);
313 /* all the events that our widget wants to receive */
314 gtk_widget_add_events(plug, GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK |
315 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
316 g_signal_connect(plug, "expose-event", G_CALLBACK(ExposeWidget),
317 instanceData);
318 g_signal_connect(plug, "motion_notify_event", G_CALLBACK(MotionEvent),
319 instanceData);
320 g_signal_connect(plug, "button_press_event", G_CALLBACK(ButtonEvent),
321 instanceData);
322 g_signal_connect(plug, "button_release_event", G_CALLBACK(ButtonEvent),
323 instanceData);
324 g_signal_connect(plug, "delete-event", G_CALLBACK(DeleteWidget),
325 instanceData);
326 gtk_widget_show(plug);
328 instanceData->platformData->plug = plug;
329 #endif
330 }
332 int16_t
333 pluginHandleEvent(InstanceData* instanceData, void* event)
334 {
335 #ifdef MOZ_X11
336 XEvent* nsEvent = (XEvent*)event;
338 switch (nsEvent->type) {
339 case GraphicsExpose: {
340 const XGraphicsExposeEvent& expose = nsEvent->xgraphicsexpose;
341 NPWindow& window = instanceData->window;
342 window.window = (void*)(expose.drawable);
344 GdkNativeWindow nativeWinId = reinterpret_cast<XID>(window.window);
346 GdkDisplay* gdkDisplay = gdk_x11_lookup_xdisplay(expose.display);
347 if (!gdkDisplay) {
348 g_warning("Display not opened by GDK");
349 return 0;
350 }
351 // gdk_pixmap_foreign_new() doesn't check whether a GdkPixmap already
352 // exists, so check first.
353 // https://bugzilla.gnome.org/show_bug.cgi?id=590690
354 GdkPixmap* gdkDrawable =
355 GDK_DRAWABLE(gdk_pixmap_lookup_for_display(gdkDisplay, nativeWinId));
356 // If there is no existing GdkPixmap or it doesn't have a colormap then
357 // create our own.
358 if (gdkDrawable) {
359 GdkColormap* gdkColormap = gdk_drawable_get_colormap(gdkDrawable);
360 if (!gdkColormap) {
361 g_warning("No GdkColormap on GdkPixmap");
362 return 0;
363 }
364 if (gdk_x11_colormap_get_xcolormap(gdkColormap)
365 != instanceData->platformData->colormap) {
366 g_warning("wrong Colormap");
367 return 0;
368 }
369 if (gdk_x11_visual_get_xvisual(gdk_colormap_get_visual(gdkColormap))
370 != instanceData->platformData->visual) {
371 g_warning("wrong Visual");
372 return 0;
373 }
374 g_object_ref(gdkDrawable);
375 } else {
376 gdkDrawable =
377 GDK_DRAWABLE(gdk_pixmap_foreign_new_for_display(gdkDisplay,
378 nativeWinId));
379 VisualID visualID = instanceData->platformData->visual->visualid;
380 GdkVisual* gdkVisual =
381 gdk_x11_screen_lookup_visual(gdk_drawable_get_screen(gdkDrawable),
382 visualID);
383 GdkColormap* gdkColormap =
384 gdk_x11_colormap_foreign_new(gdkVisual,
385 instanceData->platformData->colormap);
386 gdk_drawable_set_colormap(gdkDrawable, gdkColormap);
387 g_object_unref(gdkColormap);
388 }
390 const NPRect& clip = window.clipRect;
391 if (expose.x < clip.left || expose.y < clip.top ||
392 expose.x + expose.width > clip.right ||
393 expose.y + expose.height > clip.bottom) {
394 g_warning("expose rectangle (x=%d,y=%d,w=%d,h=%d) not in clip rectangle (l=%d,t=%d,r=%d,b=%d)",
395 expose.x, expose.y, expose.width, expose.height,
396 clip.left, clip.top, clip.right, clip.bottom);
397 return 0;
398 }
399 if (expose.x < window.x || expose.y < window.y ||
400 expose.x + expose.width > window.x + int32_t(window.width) ||
401 expose.y + expose.height > window.y + int32_t(window.height)) {
402 g_warning("expose rectangle (x=%d,y=%d,w=%d,h=%d) not in plugin rectangle (x=%d,y=%d,w=%d,h=%d)",
403 expose.x, expose.y, expose.width, expose.height,
404 window.x, window.y, window.width, window.height);
405 return 0;
406 }
408 GdkRectangle invalidRect =
409 { expose.x, expose.y, expose.width, expose.height };
410 pluginDrawWindow(instanceData, gdkDrawable, invalidRect);
411 g_object_unref(gdkDrawable);
412 break;
413 }
414 case MotionNotify: {
415 XMotionEvent* motion = &nsEvent->xmotion;
416 instanceData->lastMouseX = motion->x;
417 instanceData->lastMouseY = motion->y;
418 break;
419 }
420 case ButtonPress:
421 case ButtonRelease: {
422 XButtonEvent* button = &nsEvent->xbutton;
423 instanceData->lastMouseX = button->x;
424 instanceData->lastMouseY = button->y;
425 if (nsEvent->type == ButtonRelease) {
426 instanceData->mouseUpEventCount++;
427 }
428 break;
429 }
430 default:
431 break;
432 }
433 #endif
435 return 0;
436 }
438 int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge)
439 {
440 if (!instanceData->hasWidget)
441 return NPTEST_INT32_ERROR;
442 GtkWidget* plug = instanceData->platformData->plug;
443 if (!plug)
444 return NPTEST_INT32_ERROR;
445 GdkWindow* plugWnd = plug->window;
446 if (!plugWnd)
447 return NPTEST_INT32_ERROR;
449 GdkWindow* toplevelGdk = 0;
450 #ifdef MOZ_X11
451 Window toplevel = 0;
452 NPN_GetValue(instanceData->npp, NPNVnetscapeWindow, &toplevel);
453 if (!toplevel)
454 return NPTEST_INT32_ERROR;
455 toplevelGdk = gdk_window_foreign_new(toplevel);
456 #endif
457 if (!toplevelGdk)
458 return NPTEST_INT32_ERROR;
460 GdkRectangle toplevelFrameExtents;
461 gdk_window_get_frame_extents(toplevelGdk, &toplevelFrameExtents);
462 g_object_unref(toplevelGdk);
464 gint pluginWidth, pluginHeight;
465 gdk_drawable_get_size(GDK_DRAWABLE(plugWnd), &pluginWidth, &pluginHeight);
466 gint pluginOriginX, pluginOriginY;
467 gdk_window_get_origin(plugWnd, &pluginOriginX, &pluginOriginY);
468 gint pluginX = pluginOriginX - toplevelFrameExtents.x;
469 gint pluginY = pluginOriginY - toplevelFrameExtents.y;
471 switch (edge) {
472 case EDGE_LEFT:
473 return pluginX;
474 case EDGE_TOP:
475 return pluginY;
476 case EDGE_RIGHT:
477 return pluginX + pluginWidth;
478 case EDGE_BOTTOM:
479 return pluginY + pluginHeight;
480 }
482 return NPTEST_INT32_ERROR;
483 }
485 #ifdef MOZ_X11
486 static void intersectWithShapeRects(Display* display, Window window,
487 int kind, GdkRegion* region)
488 {
489 int count = -1, order;
490 XRectangle* shapeRects =
491 XShapeGetRectangles(display, window, kind, &count, &order);
492 // The documentation says that shapeRects will be nullptr when the
493 // extension is not supported. Unfortunately XShapeGetRectangles
494 // also returns nullptr when the region is empty, so we can't treat
495 // nullptr as failure. I hope this way is OK.
496 if (count < 0)
497 return;
499 GdkRegion* shapeRegion = gdk_region_new();
500 if (!shapeRegion) {
501 XFree(shapeRects);
502 return;
503 }
505 for (int i = 0; i < count; ++i) {
506 XRectangle* r = &shapeRects[i];
507 GdkRectangle rect = { r->x, r->y, r->width, r->height };
508 gdk_region_union_with_rect(shapeRegion, &rect);
509 }
510 XFree(shapeRects);
512 gdk_region_intersect(region, shapeRegion);
513 gdk_region_destroy(shapeRegion);
514 }
515 #endif
517 static GdkRegion* computeClipRegion(InstanceData* instanceData)
518 {
519 if (!instanceData->hasWidget)
520 return 0;
522 GtkWidget* plug = instanceData->platformData->plug;
523 if (!plug)
524 return 0;
525 GdkWindow* plugWnd = plug->window;
526 if (!plugWnd)
527 return 0;
529 gint plugWidth, plugHeight;
530 gdk_drawable_get_size(GDK_DRAWABLE(plugWnd), &plugWidth, &plugHeight);
531 GdkRectangle pluginRect = { 0, 0, plugWidth, plugHeight };
532 GdkRegion* region = gdk_region_rectangle(&pluginRect);
533 if (!region)
534 return 0;
536 int pluginX = 0, pluginY = 0;
538 #ifdef MOZ_X11
539 Display* display = GDK_WINDOW_XDISPLAY(plugWnd);
540 Window window = GDK_WINDOW_XWINDOW(plugWnd);
542 Window toplevel = 0;
543 NPN_GetValue(instanceData->npp, NPNVnetscapeWindow, &toplevel);
544 if (!toplevel)
545 return 0;
547 for (;;) {
548 Window root;
549 int x, y;
550 unsigned int width, height, border_width, depth;
551 if (!XGetGeometry(display, window, &root, &x, &y, &width, &height,
552 &border_width, &depth)) {
553 gdk_region_destroy(region);
554 return 0;
555 }
557 GdkRectangle windowRect = { 0, 0, static_cast<gint>(width),
558 static_cast<gint>(height) };
559 GdkRegion* windowRgn = gdk_region_rectangle(&windowRect);
560 if (!windowRgn) {
561 gdk_region_destroy(region);
562 return 0;
563 }
564 intersectWithShapeRects(display, window, ShapeBounding, windowRgn);
565 intersectWithShapeRects(display, window, ShapeClip, windowRgn);
566 gdk_region_offset(windowRgn, -pluginX, -pluginY);
567 gdk_region_intersect(region, windowRgn);
568 gdk_region_destroy(windowRgn);
570 // Stop now if we've reached the toplevel. Stopping here means
571 // clipping performed by the toplevel window is taken into account.
572 if (window == toplevel)
573 break;
575 Window parent;
576 Window* children;
577 unsigned int nchildren;
578 if (!XQueryTree(display, window, &root, &parent, &children, &nchildren)) {
579 gdk_region_destroy(region);
580 return 0;
581 }
582 XFree(children);
584 pluginX += x;
585 pluginY += y;
587 window = parent;
588 }
589 #endif
590 // pluginX and pluginY are now relative to the toplevel. Make them
591 // relative to the window frame top-left.
592 GdkWindow* toplevelGdk = gdk_window_foreign_new(window);
593 if (!toplevelGdk)
594 return 0;
595 GdkRectangle toplevelFrameExtents;
596 gdk_window_get_frame_extents(toplevelGdk, &toplevelFrameExtents);
597 gint toplevelOriginX, toplevelOriginY;
598 gdk_window_get_origin(toplevelGdk, &toplevelOriginX, &toplevelOriginY);
599 g_object_unref(toplevelGdk);
601 pluginX += toplevelOriginX - toplevelFrameExtents.x;
602 pluginY += toplevelOriginY - toplevelFrameExtents.y;
604 gdk_region_offset(region, pluginX, pluginY);
605 return region;
606 }
608 int32_t pluginGetClipRegionRectCount(InstanceData* instanceData)
609 {
610 GdkRegion* region = computeClipRegion(instanceData);
611 if (!region)
612 return NPTEST_INT32_ERROR;
614 GdkRectangle* rects;
615 gint nrects;
616 gdk_region_get_rectangles(region, &rects, &nrects);
617 gdk_region_destroy(region);
618 g_free(rects);
619 return nrects;
620 }
622 int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData,
623 int32_t rectIndex, RectEdge edge)
624 {
625 GdkRegion* region = computeClipRegion(instanceData);
626 if (!region)
627 return NPTEST_INT32_ERROR;
629 GdkRectangle* rects;
630 gint nrects;
631 gdk_region_get_rectangles(region, &rects, &nrects);
632 gdk_region_destroy(region);
633 if (rectIndex >= nrects) {
634 g_free(rects);
635 return NPTEST_INT32_ERROR;
636 }
638 GdkRectangle rect = rects[rectIndex];
639 g_free(rects);
641 switch (edge) {
642 case EDGE_LEFT:
643 return rect.x;
644 case EDGE_TOP:
645 return rect.y;
646 case EDGE_RIGHT:
647 return rect.x + rect.width;
648 case EDGE_BOTTOM:
649 return rect.y + rect.height;
650 }
651 return NPTEST_INT32_ERROR;
652 }
654 void pluginDoInternalConsistencyCheck(InstanceData* instanceData, string& error)
655 {
656 }
658 string
659 pluginGetClipboardText(InstanceData* instanceData)
660 {
661 GtkClipboard* cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
662 // XXX this is a BAD WAY to interact with GtkClipboard. We use this
663 // deprecated interface only to test nested event loop handling.
664 gchar* text = gtk_clipboard_wait_for_text(cb);
665 string retText = text ? text : "";
667 g_free(text);
669 return retText;
670 }
672 //-----------------------------------------------------------------------------
673 // NB: this test is quite gross in that it's not only
674 // nondeterministic, but dependent on the guts of the nested glib
675 // event loop handling code in PluginModule. We first sleep long
676 // enough to make sure that the "detection timer" will be pending when
677 // we enter the nested glib loop, then similarly for the "process browser
678 // events" timer. Then we "schedule" the crasher thread to run at about the
679 // same time we expect that the PluginModule "process browser events" task
680 // will run. If all goes well, the plugin process will crash and generate the
681 // XPCOM "plugin crashed" task, and the browser will run that task while still
682 // in the "process some events" loop.
684 static void*
685 CrasherThread(void* data)
686 {
687 // Give the parent thread a chance to send the message.
688 usleep(200);
690 // Exit (without running atexit hooks) rather than crashing with a signal
691 // so as to make timing more reliable. The process terminates immediately
692 // rather than waiting for a thread in the parent process to attach and
693 // generate a minidump.
694 _exit(1);
696 // not reached
697 return(nullptr);
698 }
700 bool
701 pluginCrashInNestedLoop(InstanceData* instanceData)
702 {
703 // wait at least long enough for nested loop detector task to be pending ...
704 sleep(1);
706 // Run the nested loop detector by processing all events that are waiting.
707 bool found_event = false;
708 while (g_main_context_iteration(nullptr, FALSE)) {
709 found_event = true;
710 }
711 if (!found_event) {
712 g_warning("DetectNestedEventLoop did not fire");
713 return true; // trigger a test failure
714 }
716 // wait at least long enough for the "process browser events" task to be
717 // pending ...
718 sleep(1);
720 // we'll be crashing soon, note that fact now to avoid messing with
721 // timing too much
722 mozilla::NoteIntentionalCrash("plugin");
724 // schedule the crasher thread ...
725 pthread_t crasherThread;
726 if (0 != pthread_create(&crasherThread, nullptr, CrasherThread, nullptr)) {
727 g_warning("Failed to create thread");
728 return true; // trigger a test failure
729 }
731 // .. and hope it crashes at about the same time as the "process browser
732 // events" task (that should run in this loop) is being processed in the
733 // parent.
734 found_event = false;
735 while (g_main_context_iteration(nullptr, FALSE)) {
736 found_event = true;
737 }
738 if (found_event) {
739 g_warning("Should have crashed in ProcessBrowserEvents");
740 } else {
741 g_warning("ProcessBrowserEvents did not fire");
742 }
744 // if we get here without crashing, then we'll trigger a test failure
745 return true;
746 }
748 static int
749 SleepThenDie(Display* display)
750 {
751 mozilla::NoteIntentionalCrash("plugin");
752 fprintf(stderr, "[testplugin:%d] SleepThenDie: sleeping\n", getpid());
753 sleep(1);
755 fprintf(stderr, "[testplugin:%d] SleepThenDie: dying\n", getpid());
756 _exit(1);
757 }
759 bool
760 pluginDestroySharedGfxStuff(InstanceData* instanceData)
761 {
762 // Closing the X socket results in the gdk error handler being
763 // invoked, which exit()s us. We want to give the parent process a
764 // little while to do whatever it wanted to do, so steal the IO
765 // handler from gdk and set up our own that delays seppuku.
766 XSetIOErrorHandler(SleepThenDie);
767 close(ConnectionNumber(GDK_DISPLAY()));
768 return true;
769 }