Bug 506304. Support synthesized mouse events being sent to windowless plugins, support reporting mouse event coordinates from the test plugin, and add tests for mouse events. r=josh
authorRobert O'Callahan <robert@ocallahan.org>
Wed, 05 Aug 2009 13:36:37 +1200
changeset 31130 22588ab14ac69be73ff8ebda2099f96adcfa647c
parent 31129 14a9da98c6452dc53aa4ddf3fc8130afb253d0f1
child 31131 acba6709fc4a4063ccf9b6e5f8f57f0b557b17b9
push id8388
push userrocallahan@mozilla.com
push dateWed, 05 Aug 2009 02:43:46 +0000
treeherdermozilla-central@73e08f744e9a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjosh
bugs506304
milestone1.9.2a1pre
Bug 506304. Support synthesized mouse events being sent to windowless plugins, support reporting mouse event coordinates from the test plugin, and add tests for mouse events. r=josh
layout/generic/nsObjectFrame.cpp
layout/generic/test/Makefile.in
layout/generic/test/test_plugin_mouse_coords.html
modules/plugin/test/testplugin/README
modules/plugin/test/testplugin/nptest.cpp
modules/plugin/test/testplugin/nptest.h
modules/plugin/test/testplugin/nptest_gtk2.cpp
modules/plugin/test/testplugin/nptest_macosx.mm
modules/plugin/test/testplugin/nptest_windows.cpp
--- a/layout/generic/nsObjectFrame.cpp
+++ b/layout/generic/nsObjectFrame.cpp
@@ -3324,20 +3324,24 @@ nsresult nsPluginInstanceOwner::EnsureCa
   return NS_OK;
 }
 
 
 // Here's where we forward events to plugins.
 
 #ifdef XP_MACOSX
 
-static void InitializeEventRecord(EventRecord* event)
+static void InitializeEventRecord(EventRecord* event, Point* aMousePosition)
 {
   memset(event, 0, sizeof(EventRecord));
-  ::GetGlobalMouse(&event->where);
+  if (aMousePosition) {
+    event->where = *aMousePosition;
+  } else {
+    ::GetGlobalMouse(&event->where);
+  }
   event->when = ::TickCount();
   event->modifiers = ::GetCurrentEventKeyModifiers();
 }
 
 NPDrawingModel nsPluginInstanceOwner::GetDrawingModel()
 {
 #ifndef NP_NO_QUICKDRAW
   NPDrawingModel drawingModel = NPDrawingModelQuickDraw;
@@ -3440,17 +3444,17 @@ nsresult nsPluginInstanceOwner::ScrollPo
 {
 #ifdef XP_MACOSX
   CancelTimer();
 
   if (mInstance) {
     nsCOMPtr<nsIPluginWidget> pluginWidget = do_QueryInterface(mWidget);
     if (pluginWidget && NS_SUCCEEDED(pluginWidget->StartDrawPlugin())) {
       EventRecord scrollEvent;
-      InitializeEventRecord(&scrollEvent);
+      InitializeEventRecord(&scrollEvent, nsnull);
       scrollEvent.what = NPEventType_ScrollingBeginsEvent;
 
       WindowRef window = FixUpPluginWindow(ePluginPaintDisable);
       if (window) {
         nsPluginEvent pluginEvent = { &scrollEvent, nsPluginPlatformWindowRef(window) };
         PRBool eventHandled = PR_FALSE;
         mInstance->HandleEvent(&pluginEvent, &eventHandled);
       }
@@ -3463,17 +3467,17 @@ nsresult nsPluginInstanceOwner::ScrollPo
 
 nsresult nsPluginInstanceOwner::ScrollPositionDidChange(nsIScrollableView* aScrollable, nscoord aX, nscoord aY)
 {
 #ifdef XP_MACOSX
   if (mInstance) {
     nsCOMPtr<nsIPluginWidget> pluginWidget = do_QueryInterface(mWidget);
     if (pluginWidget && NS_SUCCEEDED(pluginWidget->StartDrawPlugin())) {
       EventRecord scrollEvent;
-      InitializeEventRecord(&scrollEvent);
+      InitializeEventRecord(&scrollEvent, nsnull);
       scrollEvent.what = NPEventType_ScrollingEndsEvent;
 
       WindowRef window = FixUpPluginWindow(ePluginPaintEnable);
       if (window) {
         nsPluginEvent pluginEvent = { &scrollEvent, nsPluginPlatformWindowRef(window) };
         PRBool eventHandled = PR_FALSE;
         mInstance->HandleEvent(&pluginEvent, &eventHandled);
       }
@@ -4074,22 +4078,40 @@ nsEventStatus nsPluginInstanceOwner::Pro
 #ifdef XP_MACOSX
   // check for null mWidget
   if (mWidget) {
     nsCOMPtr<nsIPluginWidget> pluginWidget = do_QueryInterface(mWidget);
     if (pluginWidget && NS_SUCCEEDED(pluginWidget->StartDrawPlugin())) {
       EventRecord carbonEvent;
       void* event = anEvent.nativeMsg;
       if (!event || (static_cast<EventRecord*>(event)->what == nullEvent)) {
-        InitializeEventRecord(&carbonEvent);
-        if (anEvent.message == NS_FOCUS_CONTENT || anEvent.message == NS_BLUR_CONTENT) {
+        event = &carbonEvent;
+        nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(&anEvent, mOwner);
+        nsPresContext* presContext = mOwner->PresContext();
+        nsIntPoint ptPx(presContext->AppUnitsToDevPixels(pt.x),
+                        presContext->AppUnitsToDevPixels(pt.y));
+        Point carbonPt = { ptPx.y + mPluginWindow->y, ptPx.x + mPluginWindow->x };
+        InitializeEventRecord(&carbonEvent, &carbonPt);
+
+        switch (anEvent.message) {
+        case NS_FOCUS_CONTENT:
+        case NS_BLUR_CONTENT:
           carbonEvent.what = (anEvent.message == NS_FOCUS_CONTENT) ?
-                                NPEventType_GetFocusEvent : NPEventType_LoseFocusEvent;
+            NPEventType_GetFocusEvent : NPEventType_LoseFocusEvent;
+          break;
+        case NS_MOUSE_MOVE:
+          carbonEvent.what = osEvt;
+          break;
+        case NS_MOUSE_BUTTON_DOWN:
+          carbonEvent.what = mouseDown;
+          break;
+        case NS_MOUSE_BUTTON_UP:
+          carbonEvent.what = mouseUp;
+          break;
         }
-        event = &carbonEvent;
       }
 
       if (anEvent.message == NS_FOCUS_CONTENT) {
         // Work around an issue in the Flash plugin, which can cache a pointer
         // to a doomed TSM document (one that belongs to a NSTSMInputContext)
         // and try to activate it after it has been deleted. See bug 183313.
         ::DeactivateTSMDocument(::TSMGetActiveDocument());
       }
@@ -4115,18 +4137,58 @@ nsEventStatus nsPluginInstanceOwner::Pro
 
 #ifdef XP_WIN
   // this code supports windowless plugins
   nsPluginEvent * pPluginEvent = (nsPluginEvent *)anEvent.nativeMsg;
   // we can get synthetic events from the nsEventStateManager... these
   // have no nativeMsg
   nsPluginEvent pluginEvent;
   if (anEvent.eventStructType == NS_MOUSE_EVENT) {
-    // XXX we could synthesize Windows mouse events here for our
-    // synthetic mouse events (i.e. !pPluginEvent)
+    if (!pPluginEvent) {
+      // XXX Should extend this list to synthesize events for more event
+      // types
+      pluginEvent.event = 0;
+      const nsMouseEvent* mouseEvent = static_cast<const nsMouseEvent*>(&anEvent);
+      switch (anEvent.message) {
+      case NS_MOUSE_MOVE:
+        pluginEvent.event = WM_MOUSEMOVE;
+        break;
+      case NS_MOUSE_BUTTON_DOWN: {
+        static const int downMsgs[] =
+          { WM_LBUTTONDOWN, WM_MBUTTONDOWN, WM_RBUTTONDOWN };
+        pluginEvent.event = downMsgs[mouseEvent->button];
+        break;
+      }
+      case NS_MOUSE_BUTTON_UP: {
+        static const int upMsgs[] =
+          { WM_LBUTTONUP, WM_MBUTTONUP, WM_RBUTTONUP };
+        pluginEvent.event = upMsgs[mouseEvent->button];
+        break;
+      }
+      case NS_MOUSE_DOUBLECLICK: {
+        static const int dblClickMsgs[] =
+          { WM_LBUTTONDBLCLK, WM_MBUTTONDBLCLK, WM_RBUTTONDBLCLK };
+        pluginEvent.event = dblClickMsgs[mouseEvent->button];
+        break;
+      }
+      default:
+        break;
+      }
+      if (pluginEvent.event) {
+        pPluginEvent = &pluginEvent;
+        pluginEvent.wParam =
+          (::GetKeyState(VK_CONTROL) ? MK_CONTROL : 0) |
+          (::GetKeyState(VK_SHIFT) ? MK_SHIFT : 0) |
+          (::GetKeyState(VK_LBUTTON) ? MK_LBUTTON : 0) |
+          (::GetKeyState(VK_MBUTTON) ? MK_MBUTTON : 0) |
+          (::GetKeyState(VK_RBUTTON) ? MK_RBUTTON : 0) |
+          (::GetKeyState(VK_XBUTTON1) ? MK_XBUTTON1 : 0) |
+          (::GetKeyState(VK_XBUTTON2) ? MK_XBUTTON2 : 0);
+      }
+    }
     if (pPluginEvent) {
       // Make event coordinates relative to our enclosing widget,
       // not the widget they were received on.
       // See use of nsPluginEvent in widget/src/windows/nsWindow.cpp
       // for why this assert should be safe
       NS_ASSERTION(anEvent.message == NS_MOUSE_BUTTON_DOWN ||
                    anEvent.message == NS_MOUSE_BUTTON_UP ||
                    anEvent.message == NS_MOUSE_DOUBLECLICK ||
@@ -4464,17 +4526,17 @@ void nsPluginInstanceOwner::Paint()
   if (!mInstance || !mOwner)
     return;
  
   nsCOMPtr<nsIPluginWidget> pluginWidget = do_QueryInterface(mWidget);
   if (pluginWidget && NS_SUCCEEDED(pluginWidget->StartDrawPlugin())) {
     WindowRef window = FixUpPluginWindow(ePluginPaintEnable);
     if (window) {
       EventRecord updateEvent;
-      InitializeEventRecord(&updateEvent);
+      InitializeEventRecord(&updateEvent, nsnull);
       updateEvent.what = updateEvt;
       updateEvent.message = UInt32(window);
     
       nsPluginEvent pluginEvent = { &updateEvent, nsPluginPlatformWindowRef(window) };
       PRBool eventHandled = PR_FALSE;
       mInstance->HandleEvent(&pluginEvent, &eventHandled);
     }
     pluginWidget->EndDrawPlugin();
@@ -4786,17 +4848,17 @@ NS_IMETHODIMP nsPluginInstanceOwner::Not
   // reflect the current widget location. This makes sure that everything is updated
   // correctly in the event of scrolling in the window.
   if (mInstance) {
     nsCOMPtr<nsIPluginWidget> pluginWidget = do_QueryInterface(mWidget);
     if (pluginWidget && NS_SUCCEEDED(pluginWidget->StartDrawPlugin())) {
       WindowRef window = FixUpPluginWindow(ePluginPaintEnable);
       if (window) {
         EventRecord idleEvent;
-        InitializeEventRecord(&idleEvent);
+        InitializeEventRecord(&idleEvent, nsnull);
         idleEvent.what = nullEvent;
 
         // give a bogus 'where' field of our null event when hidden, so Flash
         // won't respond to mouse moves in other tabs, see bug 120875
         if (!mWidgetVisible)
           idleEvent.where.h = idleEvent.where.v = 20000;
 
         nsPluginEvent pluginEvent = { &idleEvent, nsPluginPlatformWindowRef(window) };
--- a/layout/generic/test/Makefile.in
+++ b/layout/generic/test/Makefile.in
@@ -86,16 +86,17 @@ include $(topsrcdir)/config/rules.mk
 		test_bug469613.xul \
 		test_bug470212.html \
 		test_movement_by_characters.html \
 		test_movement_by_words.html \
 		test_plugin_clipping.xhtml \
 		test_plugin_clipping2.xhtml \
 		test_plugin_clipping_transformed.xhtml \
 		test_plugin_clipping_table.xhtml \
+		test_plugin_mouse_coords.html \
 		test_plugin_position.xhtml \
 		test_selection_underline.html \
 		$(NULL)
 # disabled temporarily
 #		test_bug488417.html \
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/layout/generic/test/test_plugin_mouse_coords.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test delivering mouse events to plugins</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>    
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <style>
+  embed { width:200px; height:200px; display:block; }
+  iframe { border:none; }
+  </style>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: block">
+  <embed id="p1" type="application/x-test"
+         style="position:absolute; left:300px; top:10px;"></embed></div>
+  <iframe id="f1" style="position:absolute; left:0; top:250px;"
+          src="data:text/html,&lt;embed id='p2' type='application/x-test' style='position:absolute; left:10px; top:10px'&gt;"></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function doTest() {
+  var p1 = document.getElementById("p1");
+  synthesizeMouse(p1, 15, 18, { type:"mousedown" });
+  is(p1.getLastMouseX(), 15, "p1 mouse down X");
+  is(p1.getLastMouseY(), 18, "p1 mouse down Y");
+  synthesizeMouse(p1, 15, 38, { type:"mousemove" });
+  is(p1.getLastMouseX(), 15, "p1 mouse move X");
+  is(p1.getLastMouseY(), 38, "p1 mouse move Y");
+  synthesizeMouse(p1, 15, 28, { type:"mouseup" });
+  is(p1.getLastMouseX(), 15, "p1 mouse up X");
+  is(p1.getLastMouseY(), 28, "p1 mouse up Y");
+
+  var f1 = document.getElementById("f1");
+  var p2 = f1.contentDocument.getElementById("p2");
+  synthesizeMouse(p2, 15, 18, { type:"mousedown" }, f1.contentWindow);
+  is(p2.getLastMouseX(), 15, "p2 mouse down X");
+  is(p2.getLastMouseY(), 18, "p2 mouse down Y");
+  synthesizeMouse(p2, 15, 38, { type:"mousemove" }, f1.contentWindow);
+  is(p2.getLastMouseX(), 15, "p2 mouse move X");
+  is(p2.getLastMouseY(), 38, "p2 mouse move Y");
+  synthesizeMouse(p2, 15, 28, { type:"mouseup" }, f1.contentWindow);
+  is(p2.getLastMouseX(), 15, "p2 mouse up X");
+  is(p2.getLastMouseY(), 28, "p2 mouse up Y");
+
+  SimpleTest.finish();
+}
+// Need to run 'doTest' after painting is unsuppressed, or we'll set clip
+// regions to empty.
+addLoadEvent(function() { setTimeout(doTest, 0); } );
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
--- a/modules/plugin/test/testplugin/README
+++ b/modules/plugin/test/testplugin/README
@@ -97,16 +97,30 @@ rectangle only. So if you request wmode=
 
 * getClipRegionRectEdge(i, edge)
 Returns the integer screen pixel coordinate of an edge of a rectangle from the
 plugin's clip region. If i is less than zero or greater than or equal to
 getClipRegionRectCount(), this will throw an error. The coordinates are
 the same as for getEdge. See getClipRegionRectCount() above for
 notes on platform plugin limitations.
 
+== Mouse events ==
+
+The test plugin supports the following scriptable methods:
+
+* getLastMouseX()
+Returns the X coordinate of the last mouse event (move, button up, or
+button down), relative to the left edge of the plugin, or -1 if no mouse
+event has been received.
+
+* getLastMouseX()
+Returns the Y coordinate of the last mouse event (move, button up, or
+button down), relative to the top edge of the plugin, or -1 if no mouse
+event has been received.
+
 == Instance lifecycle ==
 
 The test plugin supports the following scriptable methods:
 
 * startWatchingInstanceCount()
 Marks all currently running instances as "ignored". Throws an exception if
 there is already a watch (startWatchingInstanceCount has already been
 called on some instance without a corresponding stopWatchingInstanceCount).
--- a/modules/plugin/test/testplugin/nptest.cpp
+++ b/modules/plugin/test/testplugin/nptest.cpp
@@ -68,47 +68,53 @@ static bool lastReportedPrivateModeState
 static bool hasWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getEdge(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getClipRegionRectCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getClipRegionRectEdge(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool startWatchingInstanceCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getInstanceCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool stopWatchingInstanceCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool unscheduleAllTimers(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
+static bool getLastMouseX(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
+static bool getLastMouseY(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 
 static const NPUTF8* sPluginMethodIdentifierNames[] = {
   "setUndefinedValueTest",
   "identifierToStringTest",
   "timerTest",
   "queryPrivateModeState",
   "lastReportedPrivateModeState",
   "hasWidget",
   "getEdge",
   "getClipRegionRectCount",
   "getClipRegionRectEdge",
   "startWatchingInstanceCount",
   "getInstanceCount",
   "stopWatchingInstanceCount",
   "unscheduleAllTimers",
+  "getLastMouseX",
+  "getLastMouseY",
 };
 static NPIdentifier sPluginMethodIdentifiers[ARRAY_LENGTH(sPluginMethodIdentifierNames)];
 static const ScriptableFunction sPluginMethodFunctions[ARRAY_LENGTH(sPluginMethodIdentifierNames)] = {
   setUndefinedValueTest,
   identifierToStringTest,
   timerTest,
   queryPrivateModeState,
   lastReportedPrivateModeState,
   hasWidget,
   getEdge,
   getClipRegionRectCount,
   getClipRegionRectEdge,
   startWatchingInstanceCount,
   getInstanceCount,
   stopWatchingInstanceCount,
   unscheduleAllTimers,
+  getLastMouseX,
+  getLastMouseY,
 };
 
 static bool sIdentifiersInitialized = false;
 
 /**
  * Incremented for every startWatchingInstanceCount.
  */
 static int32_t sCurrentInstanceCountWatchGeneration = 0;
@@ -335,16 +341,17 @@ NPP_New(NPMIMEType pluginType, NPP insta
   }
 
   if (scriptableObject->drawMode == DM_SOLID_COLOR &&
       (scriptableObject->drawColor & 0xFF000000) != 0xFF000000) {
     NPN_SetValue(instance, NPPVpluginTransparentBool, (void*)true);
   }
 
   instanceData->lastReportedPrivateModeState = false;
+  instanceData->lastMouseX = instanceData->lastMouseY = -1;
 
   // do platform-specific initialization
   NPError err = pluginInstanceInit(instanceData);
   if (err != NPERR_NO_ERROR) {
     NPN_ReleaseObject(scriptableObject);
     free(instanceData);
     return err;
   }
@@ -871,8 +878,32 @@ unscheduleAllTimers(NPObject* npobj, con
 
   NPN_UnscheduleTimer(npp, id->timerID1);
   id->timerID1 = 0;
   NPN_UnscheduleTimer(npp, id->timerID2);
   id->timerID2 = 0;
 
   return true;
 }
+
+static bool
+getLastMouseX(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result)
+{
+  if (argCount != 0)
+    return false;
+
+  NPP npp = static_cast<TestNPObject*>(npobj)->npp;
+  InstanceData* id = static_cast<InstanceData*>(npp->pdata);
+  INT32_TO_NPVARIANT(id->lastMouseX, *result);
+  return true;
+}
+
+static bool
+getLastMouseY(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result)
+{
+  if (argCount != 0)
+    return false;
+
+  NPP npp = static_cast<TestNPObject*>(npobj)->npp;
+  InstanceData* id = static_cast<InstanceData*>(npp->pdata);
+  INT32_TO_NPVARIANT(id->lastMouseY, *result);
+  return true;
+}
--- a/modules/plugin/test/testplugin/nptest.h
+++ b/modules/plugin/test/testplugin/nptest.h
@@ -59,11 +59,13 @@ typedef struct InstanceData {
   NPWindow window;
   TestNPObject* scriptableObject;
   PlatformData* platformData;
   uint32_t instanceCountWatchGeneration;
   bool lastReportedPrivateModeState;
   bool hasWidget;
   uint32_t timerID1;
   uint32_t timerID2;
+  int32_t lastMouseX;
+  int32_t lastMouseY;
 } InstanceData;
 
 #endif // nptest_h_
--- a/modules/plugin/test/testplugin/nptest_gtk2.cpp
+++ b/modules/plugin/test/testplugin/nptest_gtk2.cpp
@@ -210,16 +210,36 @@ ExposeWidget(GtkWidget* widget, GdkEvent
              gpointer user_data)
 {
   InstanceData* instanceData = static_cast<InstanceData*>(user_data);
   pluginDrawWindow(instanceData, event->window);
   return TRUE;
 }
 
 static gboolean
+MotionEvent(GtkWidget* widget, GdkEventMotion* event,
+            gpointer user_data)
+{
+  InstanceData* instanceData = static_cast<InstanceData*>(user_data);
+  instanceData->lastMouseX = event->x;
+  instanceData->lastMouseY = event->y;
+  return TRUE;
+}
+
+static gboolean
+ButtonEvent(GtkWidget* widget, GdkEventButton* event,
+            gpointer user_data)
+{
+  InstanceData* instanceData = static_cast<InstanceData*>(user_data);
+  instanceData->lastMouseX = event->x;
+  instanceData->lastMouseY = event->y;
+  return TRUE;
+}
+
+static gboolean
 DeleteWidget(GtkWidget* widget, GdkEvent* event, gpointer user_data)
 {
   InstanceData* instanceData = static_cast<InstanceData*>(user_data);
   // Some plugins do not expect the plug to be removed from the socket before
   // the plugin instance is destroyed.  e.g. bug 485125
   if (instanceData->platformData->plug)
     g_error("plug removed"); // this aborts
 
@@ -251,45 +271,70 @@ pluginWidgetInit(InstanceData* instanceD
 
   /* create a GtkPlug container */
   GtkWidget* plug = gtk_plug_new(nativeWinId);
 
   /* make sure the widget is capable of receiving focus */
   GTK_WIDGET_SET_FLAGS (GTK_WIDGET(plug), GTK_CAN_FOCUS);
 
   /* all the events that our widget wants to receive */
-  gtk_widget_add_events(plug, GDK_EXPOSURE_MASK);
+  gtk_widget_add_events(plug, GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK |
+                              GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
   g_signal_connect(G_OBJECT(plug), "expose-event", G_CALLBACK(ExposeWidget),
                    instanceData);
+  g_signal_connect(G_OBJECT(plug), "motion_notify_event", G_CALLBACK(MotionEvent),
+                   instanceData);
+  g_signal_connect(G_OBJECT(plug), "button_press_event", G_CALLBACK(ButtonEvent),
+                   instanceData);
+  g_signal_connect(G_OBJECT(plug), "button_release_event", G_CALLBACK(ButtonEvent),
+                   instanceData);
   g_signal_connect(G_OBJECT(plug), "delete-event", G_CALLBACK(DeleteWidget),
                    instanceData);
   gtk_widget_show(plug);
 
   instanceData->platformData->plug = plug;
 #endif
 }
 
 int16_t
 pluginHandleEvent(InstanceData* instanceData, void* event)
 {
 #ifdef MOZ_X11
-  XEvent *nsEvent = (XEvent *)event;
+  XEvent* nsEvent = (XEvent*)event;
 
-  if (nsEvent->type != GraphicsExpose)
-    return 0;
+  switch (nsEvent->type) {
+  case GraphicsExpose: {
+    XGraphicsExposeEvent* expose = &nsEvent->xgraphicsexpose;
+    instanceData->window.window = (void*)(expose->drawable);
 
-  XGraphicsExposeEvent *expose = &nsEvent->xgraphicsexpose;
-  instanceData->window.window = (void*)(expose->drawable);
+    GdkNativeWindow nativeWinId =
+      reinterpret_cast<XID>(instanceData->window.window);
+    GdkDrawable* gdkWindow = GDK_DRAWABLE(gdk_window_foreign_new(nativeWinId));  
+    pluginDrawWindow(instanceData, gdkWindow);
+    g_object_unref(gdkWindow);
+    break;
+  }
+  case MotionNotify: {
+    XMotionEvent* motion = &nsEvent->xmotion;
+    instanceData->lastMouseX = motion->x;
+    instanceData->lastMouseY = motion->y;
+    break;
+  }
+  case ButtonPress:
+  case ButtonRelease: {
+    XButtonEvent* button = &nsEvent->xbutton;
+    instanceData->lastMouseX = button->x;
+    instanceData->lastMouseY = button->y;
+    break;
+  }
+  default:
+    break;
+  }
+#endif
 
-  GdkNativeWindow nativeWinId =
-    reinterpret_cast<XID>(instanceData->window.window);
-  GdkDrawable* gdkWindow = GDK_DRAWABLE(gdk_window_foreign_new(nativeWinId));  
-  pluginDrawWindow(instanceData, gdkWindow);
-  g_object_unref(gdkWindow);
-#endif
   return 0;
 }
 
 int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge)
 {
   if (!instanceData->hasWidget)
     return NPTEST_INT32_ERROR;
   GtkWidget* plug = instanceData->platformData->plug;
--- a/modules/plugin/test/testplugin/nptest_macosx.mm
+++ b/modules/plugin/test/testplugin/nptest_macosx.mm
@@ -232,21 +232,33 @@ pluginDraw(InstanceData* instanceData)
   }
   }
 }
 
 int16_t
 pluginHandleEvent(InstanceData* instanceData, void* event)
 {
   EventRecord* carbonEvent = (EventRecord*)event;
-  if (carbonEvent && (carbonEvent->what == updateEvt)) {
+  if (!carbonEvent)
+    return 0;
+
+  NPWindow* w = &instanceData->window;
+  switch (carbonEvent->what) {
+  case updateEvt:
     pluginDraw(instanceData);
     return 1;
+  case mouseDown:
+  case mouseUp:
+  case osEvt:
+    instanceData->lastMouseX = carbonEvent->where.h - w->x;
+    instanceData->lastMouseY = carbonEvent->where.v - w->y;
+    return 1;
+  default:
+    return 0;
   }
-  return 0;
 }
 
 int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge)
 {
   NPWindow* w = &instanceData->window;
   switch (edge) {
   case EDGE_LEFT:
     return w->x;
--- a/modules/plugin/test/testplugin/nptest_windows.cpp
+++ b/modules/plugin/test/testplugin/nptest_windows.cpp
@@ -30,16 +30,17 @@
  *   Josh Aas <josh@mozilla.com>
  *   Jim Mathies <jmathies@mozilla.com>
  * 
  * ***** END LICENSE BLOCK ***** */
 
 #include "nptest_platform.h"
 
 #include <windows.h>
+#include <windowsx.h>
 
 #pragma comment(lib, "msimg32.lib")
 
 void SetSubclass(HWND hWnd, InstanceData* instanceData);
 void ClearSubclass(HWND hWnd);
 LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
 
 bool
@@ -362,49 +363,70 @@ pluginGetClipRegionRectEdge(InstanceData
     return addOffset(rect.bottom, pluginGetEdge(instanceData, EDGE_TOP));
   }
 
   return NPTEST_INT32_ERROR;
 }
 
 /* windowless plugin events */
 
+static bool
+handleEventInternal(InstanceData* instanceData, NPEvent* pe)
+{
+  switch ((UINT)pe->event) {
+    case WM_PAINT:
+      pluginDraw(instanceData);
+      return true;
+
+    case WM_MOUSEMOVE:
+    case WM_LBUTTONDOWN:
+    case WM_LBUTTONUP:
+    case WM_MBUTTONDOWN:
+    case WM_MBUTTONUP:
+    case WM_RBUTTONDOWN:
+    case WM_RBUTTONUP: {
+      int x = instanceData->hasWidget ? 0 : instanceData->window.x;
+      int y = instanceData->hasWidget ? 0 : instanceData->window.y;
+      instanceData->lastMouseX = GET_X_LPARAM(pe->lParam) - x;
+      instanceData->lastMouseY = GET_Y_LPARAM(pe->lParam) - y;
+      return true;
+    }
+
+    default:
+      return false;
+  }
+}
+
 int16_t
 pluginHandleEvent(InstanceData* instanceData, void* event)
 {
-  NPEvent * pe = (NPEvent*) event;
+  NPEvent* pe = (NPEvent*)event;
 
   if (pe == NULL || instanceData == NULL ||
       instanceData->window.type != NPWindowTypeDrawable)
     return 0;   
 
-  switch((UINT)pe->event) {
-    case WM_PAINT:
-      pluginDraw(instanceData);   
-      return 1;
-  }
-  
-  return 0;
+  return handleEventInternal(instanceData, pe);
 }
 
 /* windowed plugin events */
 
 LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
 {
 	WNDPROC wndProc = (WNDPROC)GetProp(hWnd, "MozillaWndProc");
   if (!wndProc)
     return 0;
   InstanceData* pInstance = (InstanceData*)GetProp(hWnd, "InstanceData");
   if (!pInstance)
     return 0;
 
-  if (uMsg == WM_PAINT) {
-    pluginDraw(pInstance);
+  NPEvent event = { uMsg, wParam, lParam };
+
+  if (handleEventInternal(pInstance, &event))
     return 0;
-  }
 
   if (uMsg == WM_CLOSE) {
     ClearSubclass((HWND)pInstance->window.window);
   }
 
   return CallWindowProc(wndProc, hWnd, uMsg, wParam, lParam);
 }