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