|
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 ***** */ |
|
35 |
|
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> |
|
47 |
|
48 #include "mozilla/NullPtr.h" |
|
49 #include "mozilla/IntentionalCrash.h" |
|
50 |
|
51 using namespace std; |
|
52 |
|
53 struct _PlatformData { |
|
54 #ifdef MOZ_X11 |
|
55 Display* display; |
|
56 Visual* visual; |
|
57 Colormap colormap; |
|
58 #endif |
|
59 GtkWidget* plug; |
|
60 }; |
|
61 |
|
62 bool |
|
63 pluginSupportsWindowMode() |
|
64 { |
|
65 return true; |
|
66 } |
|
67 |
|
68 bool |
|
69 pluginSupportsWindowlessMode() |
|
70 { |
|
71 return true; |
|
72 } |
|
73 |
|
74 bool |
|
75 pluginSupportsAsyncBitmapDrawing() |
|
76 { |
|
77 return false; |
|
78 } |
|
79 |
|
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; |
|
88 |
|
89 instanceData->platformData->display = nullptr; |
|
90 instanceData->platformData->visual = nullptr; |
|
91 instanceData->platformData->colormap = None; |
|
92 instanceData->platformData->plug = nullptr; |
|
93 |
|
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 } |
|
100 |
|
101 void |
|
102 pluginInstanceShutdown(InstanceData* instanceData) |
|
103 { |
|
104 if (instanceData->hasWidget) { |
|
105 Window window = reinterpret_cast<XID>(instanceData->window.window); |
|
106 |
|
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 } |
|
116 |
|
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 } |
|
129 |
|
130 NPN_MemFree(instanceData->platformData); |
|
131 instanceData->platformData = 0; |
|
132 } |
|
133 |
|
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; |
|
141 |
|
142 cairo_set_source_rgba(cairoWindow, r, g, b, a); |
|
143 } |
|
144 |
|
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); |
|
150 |
|
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 } |
|
157 |
|
158 GdkRectangle windowRect = { x, y, width, height }; |
|
159 gdk_cairo_rectangle(cairoWindow, &windowRect); |
|
160 SetCairoRGBA(cairoWindow, instanceData->scriptableObject->drawColor); |
|
161 |
|
162 cairo_fill(cairoWindow); |
|
163 cairo_destroy(cairoWindow); |
|
164 } |
|
165 |
|
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; |
|
177 |
|
178 notifyDidPaint(instanceData); |
|
179 |
|
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 } |
|
187 |
|
188 NPP npp = instanceData->npp; |
|
189 if (!npp) |
|
190 return; |
|
191 |
|
192 const char* uaString = NPN_UserAgent(npp); |
|
193 if (!uaString) |
|
194 return; |
|
195 |
|
196 GdkGC* gdkContext = gdk_gc_new(gdkWindow); |
|
197 if (!gdkContext) |
|
198 return; |
|
199 |
|
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 } |
|
206 |
|
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); |
|
212 |
|
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); |
|
220 |
|
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); |
|
228 |
|
229 g_object_unref(gdkContext); |
|
230 } |
|
231 |
|
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 } |
|
240 |
|
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 } |
|
250 |
|
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 } |
|
263 |
|
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 |
|
272 |
|
273 return FALSE; |
|
274 } |
|
275 |
|
276 void |
|
277 pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow) |
|
278 { |
|
279 instanceData->window = *newWindow; |
|
280 |
|
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 } |
|
289 |
|
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 } |
|
299 |
|
300 GdkNativeWindow nativeWinId = |
|
301 reinterpret_cast<XID>(instanceData->window.window); |
|
302 |
|
303 /* create a GtkPlug container */ |
|
304 GtkWidget* plug = gtk_plug_new(nativeWinId); |
|
305 |
|
306 // Test for bugs 539138 and 561308 |
|
307 if (!plug->window) |
|
308 g_error("Plug has no window"); // aborts |
|
309 |
|
310 /* make sure the widget is capable of receiving focus */ |
|
311 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(plug), GTK_CAN_FOCUS); |
|
312 |
|
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); |
|
327 |
|
328 instanceData->platformData->plug = plug; |
|
329 #endif |
|
330 } |
|
331 |
|
332 int16_t |
|
333 pluginHandleEvent(InstanceData* instanceData, void* event) |
|
334 { |
|
335 #ifdef MOZ_X11 |
|
336 XEvent* nsEvent = (XEvent*)event; |
|
337 |
|
338 switch (nsEvent->type) { |
|
339 case GraphicsExpose: { |
|
340 const XGraphicsExposeEvent& expose = nsEvent->xgraphicsexpose; |
|
341 NPWindow& window = instanceData->window; |
|
342 window.window = (void*)(expose.drawable); |
|
343 |
|
344 GdkNativeWindow nativeWinId = reinterpret_cast<XID>(window.window); |
|
345 |
|
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 } |
|
389 |
|
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 } |
|
407 |
|
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 |
|
434 |
|
435 return 0; |
|
436 } |
|
437 |
|
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; |
|
448 |
|
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; |
|
459 |
|
460 GdkRectangle toplevelFrameExtents; |
|
461 gdk_window_get_frame_extents(toplevelGdk, &toplevelFrameExtents); |
|
462 g_object_unref(toplevelGdk); |
|
463 |
|
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; |
|
470 |
|
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 } |
|
481 |
|
482 return NPTEST_INT32_ERROR; |
|
483 } |
|
484 |
|
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; |
|
498 |
|
499 GdkRegion* shapeRegion = gdk_region_new(); |
|
500 if (!shapeRegion) { |
|
501 XFree(shapeRects); |
|
502 return; |
|
503 } |
|
504 |
|
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); |
|
511 |
|
512 gdk_region_intersect(region, shapeRegion); |
|
513 gdk_region_destroy(shapeRegion); |
|
514 } |
|
515 #endif |
|
516 |
|
517 static GdkRegion* computeClipRegion(InstanceData* instanceData) |
|
518 { |
|
519 if (!instanceData->hasWidget) |
|
520 return 0; |
|
521 |
|
522 GtkWidget* plug = instanceData->platformData->plug; |
|
523 if (!plug) |
|
524 return 0; |
|
525 GdkWindow* plugWnd = plug->window; |
|
526 if (!plugWnd) |
|
527 return 0; |
|
528 |
|
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; |
|
535 |
|
536 int pluginX = 0, pluginY = 0; |
|
537 |
|
538 #ifdef MOZ_X11 |
|
539 Display* display = GDK_WINDOW_XDISPLAY(plugWnd); |
|
540 Window window = GDK_WINDOW_XWINDOW(plugWnd); |
|
541 |
|
542 Window toplevel = 0; |
|
543 NPN_GetValue(instanceData->npp, NPNVnetscapeWindow, &toplevel); |
|
544 if (!toplevel) |
|
545 return 0; |
|
546 |
|
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 } |
|
556 |
|
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); |
|
569 |
|
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; |
|
574 |
|
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); |
|
583 |
|
584 pluginX += x; |
|
585 pluginY += y; |
|
586 |
|
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); |
|
600 |
|
601 pluginX += toplevelOriginX - toplevelFrameExtents.x; |
|
602 pluginY += toplevelOriginY - toplevelFrameExtents.y; |
|
603 |
|
604 gdk_region_offset(region, pluginX, pluginY); |
|
605 return region; |
|
606 } |
|
607 |
|
608 int32_t pluginGetClipRegionRectCount(InstanceData* instanceData) |
|
609 { |
|
610 GdkRegion* region = computeClipRegion(instanceData); |
|
611 if (!region) |
|
612 return NPTEST_INT32_ERROR; |
|
613 |
|
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 } |
|
621 |
|
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; |
|
628 |
|
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 } |
|
637 |
|
638 GdkRectangle rect = rects[rectIndex]; |
|
639 g_free(rects); |
|
640 |
|
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 } |
|
653 |
|
654 void pluginDoInternalConsistencyCheck(InstanceData* instanceData, string& error) |
|
655 { |
|
656 } |
|
657 |
|
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 : ""; |
|
666 |
|
667 g_free(text); |
|
668 |
|
669 return retText; |
|
670 } |
|
671 |
|
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. |
|
683 |
|
684 static void* |
|
685 CrasherThread(void* data) |
|
686 { |
|
687 // Give the parent thread a chance to send the message. |
|
688 usleep(200); |
|
689 |
|
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); |
|
695 |
|
696 // not reached |
|
697 return(nullptr); |
|
698 } |
|
699 |
|
700 bool |
|
701 pluginCrashInNestedLoop(InstanceData* instanceData) |
|
702 { |
|
703 // wait at least long enough for nested loop detector task to be pending ... |
|
704 sleep(1); |
|
705 |
|
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 } |
|
715 |
|
716 // wait at least long enough for the "process browser events" task to be |
|
717 // pending ... |
|
718 sleep(1); |
|
719 |
|
720 // we'll be crashing soon, note that fact now to avoid messing with |
|
721 // timing too much |
|
722 mozilla::NoteIntentionalCrash("plugin"); |
|
723 |
|
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 } |
|
730 |
|
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 } |
|
743 |
|
744 // if we get here without crashing, then we'll trigger a test failure |
|
745 return true; |
|
746 } |
|
747 |
|
748 static int |
|
749 SleepThenDie(Display* display) |
|
750 { |
|
751 mozilla::NoteIntentionalCrash("plugin"); |
|
752 fprintf(stderr, "[testplugin:%d] SleepThenDie: sleeping\n", getpid()); |
|
753 sleep(1); |
|
754 |
|
755 fprintf(stderr, "[testplugin:%d] SleepThenDie: dying\n", getpid()); |
|
756 _exit(1); |
|
757 } |
|
758 |
|
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 } |