Bug 484923. Test plugin should support 'window' mode on GTK2/X. r=josh,karl
authorRobert O'Callahan <robert@ocallahan.org>
Wed, 01 Apr 2009 14:07:19 +1300
changeset 26802 adbc4043e8bd3b7c994c4ca4ea8a20008a16bc8e
parent 26801 42183a8ce3eb82008255b96b02f93f6bae7f0e28
child 26803 7157cc2440bb26429a6218408873c823dd9c4dd0
push idunknown
push userunknown
push dateunknown
reviewersjosh, karl
bugs484923
milestone1.9.2a1pre
Bug 484923. Test plugin should support 'window' mode on GTK2/X. r=josh,karl
modules/plugin/test/mochitest/Makefile.in
modules/plugin/test/mochitest/test_wmode.xul
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_platform.h
modules/plugin/test/testplugin/nptest_qt.cpp
modules/plugin/test/testplugin/nptest_windows.cpp
--- a/modules/plugin/test/mochitest/Makefile.in
+++ b/modules/plugin/test/mochitest/Makefile.in
@@ -42,13 +42,14 @@ VPATH		= @srcdir@
 relativesrcdir  = modules/plugin/test
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES =	test_npruntime.xul
 _TEST_FILES = \
 		test_npruntime.xul   \
-    test_privatemode.xul \
+		test_privatemode.xul \
+		test_wmode.xul \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $^ $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/modules/plugin/test/mochitest/test_wmode.xul
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<window title="WMode Tests"
+  xmlns:html="http://www.w3.org/1999/xhtml"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <title>WMode Tests</title>
+  <script type="application/javascript" 
+   src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+<body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()">
+<embed id="plugin2" type="application/x-test" width="400" height="400" wmode="window"></embed>
+<embed id="plugin1" type="application/x-test" width="400" height="400"></embed>
+</body>
+<script class="testbody" type="application/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+function runTests() {
+  var p1 = document.getElementById("plugin1");
+  is(p1.hasWidget(), false, "Plugin should be windowless by default");
+  
+  var p2 = document.getElementById("plugin2");
+  if (navigator.platform.indexOf("Mac") >= 0) {
+    is(p2.hasWidget(), false, "Mac does not support windowed plugins");
+  } else if (navigator.platform.indexOf("Win") >= 0) {
+    is(p2.hasWidget(), false, "Windows does not support windowed plugins (yet)");
+  } else if (navigator.platform.indexOf("Linux") >= 0) {
+    is(p2.hasWidget(), true, "Linux supports windowed plugins");
+  }
+
+  SimpleTest.finish();
+}
+]]>
+</script>
+</window>
--- a/modules/plugin/test/testplugin/README
+++ b/modules/plugin/test/testplugin/README
@@ -31,8 +31,24 @@ then to a string, which is returned.
 
 The test plugin object supports the following scriptable methods:
 
 * queryPrivateModeState
 Returns the value of NPN_GetValue(NPNVprivateModeBool).
 
 * lastReportedPrivateModeState
 Returns the last value set by NPP_SetValue(NPNVprivateModeBool).
+
+== Windowed/windowless mode ==
+
+The test plugin is windowless by default.
+
+The test plugin supports the following parameter:
+
+* wmode="window"
+The plugin will be given a native widget on platforms where we support this
+(Windows and X).
+
+The test plugin object supports the following scriptable method:
+
+* hasWidget()
+Returns true if the plugin has an associated widget. This will return true if
+wmode="window" was specified and the platform supports windowed plugins.
--- a/modules/plugin/test/testplugin/nptest.cpp
+++ b/modules/plugin/test/testplugin/nptest.cpp
@@ -53,23 +53,39 @@ static NPClass sNPClass;
 
 //
 // identifiers
 //
 
 #define IDENTIFIER_TO_STRING_TEST_METHOD        0
 #define QUERY_PRIVATE_MODE_STATE_METHOD         1
 #define LAST_REPORTED_PRIVATE_MODE_STATE_METHOD 2
-#define NUM_METHOD_IDENTIFIERS                  3
+#define HAS_WIDGET_METHOD                       3
+#define NUM_METHOD_IDENTIFIERS                  4
+
+typedef bool (* ScriptableFunction)
+  (NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
+
+static bool identifierToStringTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
+static bool queryPrivateModeState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
+static bool lastReportedPrivateModeState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
+static bool hasWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 
 static NPIdentifier sPluginMethodIdentifiers[NUM_METHOD_IDENTIFIERS];
-static const NPUTF8 *sPluginMethodIdentifierNames[NUM_METHOD_IDENTIFIERS] = {
+static const NPUTF8* sPluginMethodIdentifierNames[NUM_METHOD_IDENTIFIERS] = {
   "identifierToStringTest",
   "queryPrivateModeState",
   "lastReportedPrivateModeState",
+  "hasWidget",
+};
+static const ScriptableFunction sPluginMethodFunctions[NUM_METHOD_IDENTIFIERS] = {
+  identifierToStringTest,
+  queryPrivateModeState,
+  lastReportedPrivateModeState,
+  hasWidget,
 };
 
 static bool sIdentifiersInitialized = false;
 
 static void initializeIdentifiers()
 {
   if (!sIdentifiersInitialized) {
     NPN_GetStringIdentifiers(sPluginMethodIdentifierNames, NUM_METHOD_IDENTIFIERS, sPluginMethodIdentifiers);
@@ -82,20 +98,16 @@ static void clearIdentifiers()
   memset(sPluginMethodIdentifierNames, 0, NUM_METHOD_IDENTIFIERS * sizeof(NPIdentifier));
   sIdentifiersInitialized = false;
 }
 
 //
 // function signatures
 //
 
-bool identifierToStringTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
-bool queryPrivateModeState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
-bool lastReportedPrivateModeState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
-
 NPObject* scriptableAllocate(NPP npp, NPClass* aClass);
 void scriptableDeallocate(NPObject* npobj);
 void scriptableInvalidate(NPObject* npobj);
 bool scriptableHasMethod(NPObject* npobj, NPIdentifier name);
 bool scriptableInvoke(NPObject* npobj, NPIdentifier name, const NPVariant* args, uint32_t argCount, NPVariant* result);
 bool scriptableInvokeDefault(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 bool scriptableHasProperty(NPObject* npobj, NPIdentifier name);
 bool scriptableGetProperty(NPObject* npobj, NPIdentifier name, NPVariant* result);
@@ -217,73 +229,111 @@ NPError OSCALL NP_Shutdown()
   clearIdentifiers();
 
   return NPERR_NO_ERROR;
 }
 
 NPError
 NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* saved)
 {
-  NPN_SetValue(instance, NPPVpluginWindowBool, NULL);
+  // Make sure we can render this plugin
+  NPBool browserSupportsWindowless = false;
+  NPN_GetValue(instance, NPNVSupportsWindowless, &browserSupportsWindowless);
+  if (!browserSupportsWindowless && !pluginSupportsWindowMode()) {
+    printf("Windowless mode not supported by the browser, windowed mode not supported by the plugin!\n");
+    return NPERR_GENERIC_ERROR;
+  }
 
   // set up our our instance data
   InstanceData* instanceData = (InstanceData*)malloc(sizeof(InstanceData));
   if (!instanceData)
     return NPERR_OUT_OF_MEMORY_ERROR;
   memset(instanceData, 0, sizeof(InstanceData));
   instanceData->npp = instance;
   instance->pdata = instanceData;
 
   TestNPObject* scriptableObject = (TestNPObject*)NPN_CreateObject(instance, &sNPClass);
   if (!scriptableObject) {
     printf("NPN_CreateObject failed to create an object, can't create a plugin instance\n");
+    free(instanceData);
     return NPERR_GENERIC_ERROR;
   }
   NPN_RetainObject(scriptableObject);
   scriptableObject->npp = instance;
+  scriptableObject->drawMode = DM_DEFAULT;
+  scriptableObject->drawColor = 0;
   instanceData->scriptableObject = scriptableObject;
 
-  scriptableObject->drawMode = DM_DEFAULT;
-  scriptableObject->drawColor = 0;
-
+  bool requestWindow = false;
   // handle extra params
   for (int i = 0; i < argc; i++) {
     if (strcmp(argn[i], "drawmode") == 0) {
       if (strcmp(argv[i], "solid") == 0)
         scriptableObject->drawMode = DM_SOLID_COLOR;    
     }
     else if (strcmp(argn[i], "color") == 0) {
       scriptableObject->drawColor = parseHexColor(argv[i]);
     }
+    else if (strcmp(argn[i], "wmode") == 0) {
+      if (strcmp(argv[i], "window") == 0) {
+        requestWindow = true;
+      }
+    }
+  }
+
+  if (!browserSupportsWindowless || !pluginSupportsWindowlessMode()) {
+    requestWindow = true;
+  } else if (!pluginSupportsWindowMode()) {
+    requestWindow = false;
+  }
+  if (requestWindow) {
+    instanceData->hasWidget = true;
+  } else {
+    // NPPVpluginWindowBool should default to true, so we may as well
+    // test that by not setting it in the window case
+    NPN_SetValue(instance, NPPVpluginWindowBool, (void*)false);
+  }
+
+  if (scriptableObject->drawMode == DM_SOLID_COLOR &&
+      (scriptableObject->drawColor & 0xFF000000) != 0xFF000000) {
+    NPN_SetValue(instance, NPPVpluginTransparentBool, (void*)true);
   }
 
   instanceData->lastReportedPrivateModeState = false;
 
   // do platform-specific initialization
   NPError err = pluginInstanceInit(instanceData);
-  if (err != NPERR_NO_ERROR)
+  if (err != NPERR_NO_ERROR) {
+    NPN_ReleaseObject(scriptableObject);
+    free(instanceData);
     return err;
+  }
 
   return NPERR_NO_ERROR;
 }
 
 NPError
 NPP_Destroy(NPP instance, NPSavedData** save)
 {
   InstanceData* instanceData = (InstanceData*)(instance->pdata);
+  pluginInstanceShutdown(instanceData);
   NPN_ReleaseObject(instanceData->scriptableObject);
   free(instanceData);
   return NPERR_NO_ERROR;
 }
 
 NPError
 NPP_SetWindow(NPP instance, NPWindow* window)
 {
   InstanceData* instanceData = (InstanceData*)(instance->pdata);
+  void* oldWindow = instanceData->window.window;
   instanceData->window = *window;
+  if (instanceData->hasWidget && oldWindow != instanceData->window.window) {
+    pluginWidgetInit(instanceData, oldWindow);
+  }
   return NPERR_NO_ERROR;
 }
 
 NPError
 NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16_t* stype)
 {
   *stype = NP_ASFILEONLY;
   return NPERR_NO_ERROR;
@@ -327,22 +377,28 @@ NPP_HandleEvent(NPP instance, void* even
 void
 NPP_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData)
 {
 }
 
 NPError
 NPP_GetValue(NPP instance, NPPVariable variable, void* value)
 {
+  InstanceData* instanceData = (InstanceData*)instance->pdata;
   if (variable == NPPVpluginScriptableNPObject) {
-    NPObject* object = ((InstanceData*)instance->pdata)->scriptableObject;
+    NPObject* object = instanceData->scriptableObject;
     NPN_RetainObject(object);
     *((NPObject**)value) = object;
     return NPERR_NO_ERROR;
   }
+  if (variable == NPPVpluginNeedsXEmbed) {
+    // Only relevant for X plugins
+    *(NPBool*)value = instanceData->hasWidget;
+    return NPERR_NO_ERROR;
+  }
 
   return NPERR_GENERIC_ERROR;
 }
 
 NPError
 NPP_SetValue(NPP instance, NPNVariable variable, void* value)
 {
   if (variable == NPNVprivateModeBool) {
@@ -480,22 +536,20 @@ scriptableHasMethod(NPObject* npobj, NPI
       return true;
   }
   return false;
 }
 
 bool
 scriptableInvoke(NPObject* npobj, NPIdentifier name, const NPVariant* args, uint32_t argCount, NPVariant* result)
 {
-  if (name == sPluginMethodIdentifiers[IDENTIFIER_TO_STRING_TEST_METHOD])
-    return identifierToStringTest(npobj, args, argCount, result);
-  else if (name == sPluginMethodIdentifiers[QUERY_PRIVATE_MODE_STATE_METHOD])
-    return queryPrivateModeState(npobj, args, argCount, result);
-  else if (name == sPluginMethodIdentifiers[LAST_REPORTED_PRIVATE_MODE_STATE_METHOD])
-    return lastReportedPrivateModeState(npobj, args, argCount, result);
+  for (int i = 0; i < NUM_METHOD_IDENTIFIERS; i++) {
+    if (name == sPluginMethodIdentifiers[i])
+      return sPluginMethodFunctions[i](npobj, args, argCount, result);
+  }
   return false;
 }
 
 bool
 scriptableInvokeDefault(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result)
 {
   return false;
 }
@@ -535,40 +589,47 @@ scriptableConstruct(NPObject* npobj, con
 {
   return false;
 }
 
 //
 // test functions
 //
 
-bool
+static bool
 identifierToStringTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result)
 {
   if (argCount != 1)
     return false;
   NPIdentifier identifier = variantToIdentifier(args[0]);
   if (!identifier)
     return false;
   NPUTF8* utf8String = NPN_UTF8FromIdentifier(identifier);
   if (!utf8String)
     return false;
   STRINGZ_TO_NPVARIANT(utf8String, *result);
   return true;
 }
 
-bool
+static bool
 queryPrivateModeState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result)
 {
   NPBool pms = false;
   NPN_GetValue(static_cast<TestNPObject*>(npobj)->npp, NPNVprivateModeBool, &pms);
   BOOLEAN_TO_NPVARIANT(pms, *result);
   return true;
 }
 
-bool
+static bool
 lastReportedPrivateModeState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result)
 {
   InstanceData* id = static_cast<InstanceData*>(static_cast<TestNPObject*>(npobj)->npp->pdata);
-  NPBool pms = id->lastReportedPrivateModeState;
-  BOOLEAN_TO_NPVARIANT(pms, *result);
+  BOOLEAN_TO_NPVARIANT(id->lastReportedPrivateModeState, *result);
   return true;
 }
+
+static bool
+hasWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result)
+{
+  InstanceData* id = static_cast<InstanceData*>(static_cast<TestNPObject*>(npobj)->npp->pdata);
+  BOOLEAN_TO_NPVARIANT(id->hasWidget, *result);
+  return true;
+}
--- a/modules/plugin/test/testplugin/nptest.h
+++ b/modules/plugin/test/testplugin/nptest.h
@@ -51,12 +51,14 @@ typedef struct TestNPObject : NPObject {
   DrawMode drawMode;
   PRUint32 drawColor; // 0xAARRGGBB
 } TestNPObject;
 
 typedef struct InstanceData {
   NPP npp;
   NPWindow window;
   TestNPObject* scriptableObject;
-  NPBool lastReportedPrivateModeState;
+  void* platformData;
+  bool lastReportedPrivateModeState;
+  bool hasWidget;
 } InstanceData;
 
 #endif // nptest_h_
--- a/modules/plugin/test/testplugin/nptest_gtk2.cpp
+++ b/modules/plugin/test/testplugin/nptest_gtk2.cpp
@@ -33,127 +33,178 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "nptest_platform.h"
 #include "npapi.h"
 #include <gdk/gdk.h>
 #ifdef MOZ_X11
 #include <gdk/gdkx.h>
 #endif
+#include <gtk/gtk.h>
 
-void pluginDrawSolid(InstanceData* instanceData, GdkDrawable* gdkWindow);
+bool
+pluginSupportsWindowMode()
+{
+  return true;
+}
+
+bool
+pluginSupportsWindowlessMode()
+{
+  return true;
+}
 
 NPError
 pluginInstanceInit(InstanceData* instanceData)
 {
 #ifdef MOZ_X11
   return NPERR_NO_ERROR;
 #else
   // we only support X11 here, since thats what the plugin system uses
   return NPERR_INCOMPATIBLE_VERSION_ERROR;
 #endif
 }
 
-int16_t
-pluginHandleEvent(InstanceData* instanceData, void* event)
+void
+pluginInstanceShutdown(InstanceData* instanceData)
 {
-#ifdef MOZ_X11
-  XEvent *nsEvent = (XEvent *)event;
-  gboolean handled = 0;
-
-  if (nsEvent->type != GraphicsExpose)
-    return 0;
-
-  XGraphicsExposeEvent *expose = &nsEvent->xgraphicsexpose;
-  instanceData->window.window = (void*)(expose->drawable);
-
-  pluginDraw(instanceData);
-#endif
-  return 0;
+  GtkWidget* plug = static_cast<GtkWidget*>(instanceData->platformData);
+  if (plug) {
+    gtk_widget_destroy(plug);
+    instanceData->platformData = 0;
+  }
 }
 
 static void 
 SetCairoRGBA(cairo_t* cairoWindow, PRUint32 rgba)
 {
   float b = (rgba & 0xFF) / 255.0;
   float g = ((rgba & 0xFF00) >> 8) / 255.0;
   float r = ((rgba & 0xFF0000) >> 16) / 255.0;
   float a = ((rgba & 0xFF000000) >> 24) / 255.0;
 
   cairo_set_source_rgba(cairoWindow, r, g, b, a);
 }
 
-void
-pluginDrawSolid(InstanceData* instanceData, GdkDrawable* gdkWindow)
+static void
+pluginDrawSolid(InstanceData* instanceData, GdkDrawable* gdkWindow,
+                int x, int y, int width, int height)
 {
-#ifdef MOZ_X11
   cairo_t* cairoWindow = gdk_cairo_create(gdkWindow);
 
-  NPWindow window = instanceData->window;
-  GdkRectangle windowRect;
-  windowRect.x = window.x;
-  windowRect.y = window.y;
-  windowRect.width = window.width;
-  windowRect.height = window.height;
-
+  GdkRectangle windowRect = { x, y, width, height };
   gdk_cairo_rectangle(cairoWindow, &windowRect);
   SetCairoRGBA(cairoWindow, instanceData->scriptableObject->drawColor);
 
   cairo_fill(cairoWindow);
   cairo_destroy(cairoWindow);
-  g_object_unref(gdkWindow);
-#endif
 }
 
-void
-pluginDraw(InstanceData* instanceData)
+static void
+pluginDrawWindow(InstanceData* instanceData, GdkDrawable* gdkWindow)
 {
-#ifdef MOZ_X11
-  if (!instanceData)
+  NPWindow& window = instanceData->window;
+  // When we have a widget, window.x/y are meaningless since our
+  // widget is always positioned correctly and we just draw into it at 0,0
+  int x = instanceData->hasWidget ? 0 : window.x;
+  int y = instanceData->hasWidget ? 0 : window.y;
+  int width = window.width;
+  int height = window.height;
+
+  if (instanceData->scriptableObject->drawMode == DM_SOLID_COLOR) {
+    // drawing a solid color for reftests
+    pluginDrawSolid(instanceData, gdkWindow, x, y, width, height);
     return;
+  }
 
   NPP npp = instanceData->npp;
   if (!npp)
     return;
 
   const char* uaString = NPN_UserAgent(npp);
   if (!uaString)
     return;
 
-  NPWindow window = instanceData->window;
-  GdkNativeWindow nativeWinId = reinterpret_cast<XID>(window.window);
-  GdkDrawable* gdkWindow = GDK_DRAWABLE(gdk_window_foreign_new(nativeWinId));
-
-  if (instanceData->scriptableObject->drawMode == DM_SOLID_COLOR) {
-    // drawing a solid color for reftests
-    pluginDrawSolid(instanceData, gdkWindow);
-    return;
-  }
-
   GdkGC* gdkContext = gdk_gc_new(gdkWindow);
 
   // draw a grey background for the plugin frame
   GdkColor grey;
   grey.red = grey.blue = grey.green = 32767;
   gdk_gc_set_rgb_fg_color(gdkContext, &grey);
-  gdk_draw_rectangle(gdkWindow, gdkContext, TRUE, window.x, window.y,
-                     window.width, window.height);
+  gdk_draw_rectangle(gdkWindow, gdkContext, TRUE, x, y, width, height);
 
   // draw a 3-pixel-thick black frame around the plugin
   GdkColor black;
   black.red = black.green = black.blue = 0;
   gdk_gc_set_rgb_fg_color(gdkContext, &black);
   gdk_gc_set_line_attributes(gdkContext, 3, GDK_LINE_SOLID, GDK_CAP_NOT_LAST, GDK_JOIN_MITER);
-  gdk_draw_rectangle(gdkWindow, gdkContext, FALSE, window.x + 1, window.y + 1,
-                     window.width - 3, window.height - 3);
+  gdk_draw_rectangle(gdkWindow, gdkContext, FALSE, x + 1, y + 1,
+                     width - 3, height - 3);
 
   // paint the UA string
   PangoContext* pangoContext = gdk_pango_context_get();
   PangoLayout* pangoTextLayout = pango_layout_new(pangoContext);
-  pango_layout_set_width(pangoTextLayout, (window.width - 10) * PANGO_SCALE);
+  pango_layout_set_width(pangoTextLayout, (width - 10) * PANGO_SCALE);
   pango_layout_set_text(pangoTextLayout, uaString, -1);
-  gdk_draw_layout(gdkWindow, gdkContext, window.x + 5, window.y + 5, pangoTextLayout);
+  gdk_draw_layout(gdkWindow, gdkContext, x + 5, y + 5, pangoTextLayout);
   g_object_unref(pangoTextLayout);
 
   g_object_unref(gdkContext);
+}
+
+static gboolean
+ExposeWidget(GtkWidget* widget, GdkEventExpose* event,
+             gpointer user_data)
+{
+  InstanceData* instanceData = static_cast<InstanceData*>(user_data);
+  pluginDrawWindow(instanceData, event->window);
+  return TRUE;
+}
+
+void
+pluginWidgetInit(InstanceData* instanceData, void* oldWindow)
+{
+#ifdef MOZ_X11
+  GtkWidget* oldPlug = static_cast<GtkWidget*>(instanceData->platformData);
+  if (oldPlug) {
+    gtk_widget_destroy(oldPlug);
+    instanceData->platformData = 0;
+  }
+
+  GdkNativeWindow nativeWinId =
+    reinterpret_cast<XID>(instanceData->window.window);
+
+  /* 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);
+  g_signal_connect(G_OBJECT(plug), "event", G_CALLBACK(ExposeWidget),
+                   instanceData);
+  gtk_widget_show(plug);
+
+  instanceData->platformData = plug;
+#endif
+}
+
+int16_t
+pluginHandleEvent(InstanceData* instanceData, void* event)
+{
+#ifdef MOZ_X11
+  XEvent *nsEvent = (XEvent *)event;
+
+  if (nsEvent->type != GraphicsExpose)
+    return 0;
+
+  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);
 #endif
+  return 0;
 }
--- a/modules/plugin/test/testplugin/nptest_macosx.mm
+++ b/modules/plugin/test/testplugin/nptest_macosx.mm
@@ -29,52 +29,64 @@
  * Contributor(s):
  *   Josh Aas <josh@mozilla.com>
  * 
  * ***** END LICENSE BLOCK ***** */
 
 #include "nptest_platform.h"
 #include <CoreServices/CoreServices.h>
 
+bool
+pluginSupportsWindowMode()
+{
+  return false;
+}
+
+bool
+pluginSupportsWindowlessMode()
+{
+  return true;
+}
+
 NPError
 pluginInstanceInit(InstanceData* instanceData)
 {
   NPP npp = instanceData->npp;
   // select the right drawing model if necessary
   NPBool supportsCoreGraphics = false;
   if (NPN_GetValue(npp, NPNVsupportsCoreGraphicsBool, &supportsCoreGraphics) == NPERR_NO_ERROR && supportsCoreGraphics) {
     NPN_SetValue(npp, NPPVpluginDrawingModel, (void*)NPDrawingModelCoreGraphics);
   } else {
     printf("CoreGraphics drawing model not supported, can't create a plugin instance.\n");
     return NPERR_INCOMPATIBLE_VERSION_ERROR;
   }
   return NPERR_NO_ERROR;
 }
 
-int16_t
-pluginHandleEvent(InstanceData* instanceData, void* event)
+void
+pluginInstanceShutdown(InstanceData* instanceData)
 {
-  EventRecord* carbonEvent = (EventRecord*)event;
-  if (carbonEvent && (carbonEvent->what == updateEvt)) {
-    pluginDraw(instanceData);
-    return 1;
-  }
-  return 0;
+}
+
+void
+pluginWidgetInit(InstanceData* instanceData, void* oldWindow)
+{
+  // Should never be called since we don't support window mode
 }
 
 static void 
 GetColorsFromRGBA(PRUint32 rgba, float* r, float* g, float* b, float* a)
 {
   *b = (rgba & 0xFF) / 255.0;
   *g = ((rgba & 0xFF00) >> 8) / 255.0;
   *r = ((rgba & 0xFF0000) >> 16) / 255.0;
   *a = ((rgba & 0xFF000000) >> 24) / 255.0;
 }
 
-void
+static void
 pluginDraw(InstanceData* instanceData)
 {
   if (!instanceData)
     return;
 
   NPP npp = instanceData->npp;
   if (!npp)
     return;
@@ -191,8 +203,19 @@ pluginDraw(InstanceData* instanceData)
     CGContextDrawPath(cgContext, kCGPathFill);
 
     // restore the cgcontext gstate
     CGContextRestoreGState(cgContext);
     break;
   }
   }
 }
+
+int16_t
+pluginHandleEvent(InstanceData* instanceData, void* event)
+{
+  EventRecord* carbonEvent = (EventRecord*)event;
+  if (carbonEvent && (carbonEvent->what == updateEvt)) {
+    pluginDraw(instanceData);
+    return 1;
+  }
+  return 0;
+}
--- a/modules/plugin/test/testplugin/nptest_platform.h
+++ b/modules/plugin/test/testplugin/nptest_platform.h
@@ -31,13 +31,44 @@
  * 
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nptest_platform_h_
 #define nptest_platform_h_
 
 #include "nptest.h"
 
+/**
+ * Returns true if the plugin supports windowed mode
+ */
+bool    pluginSupportsWindowMode();
+
+/**
+ * Returns true if the plugin supports windowless mode. At least one of
+ * "pluginSupportsWindowMode" and "pluginSupportsWindowlessMode" must
+ * return true.
+ */
+bool    pluginSupportsWindowlessMode();
+
+/**
+ * Initialize the plugin instance. Returning an error here will cause the
+ * plugin instantiation to fail.
+ */
 NPError pluginInstanceInit(InstanceData* instanceData);
+
+/**
+ * Shutdown the plugin instance.
+ */
+void    pluginInstanceShutdown(InstanceData* instanceData);
+
+/**
+ * Initialize the window for a windowed plugin. oldWindow is the old
+ * native window value. This will never be called for windowless plugins.
+ */
+void    pluginWidgetInit(InstanceData* instanceData, void* oldWindow);
+
+/**
+ * Handle an event for a windowless plugin. (Windowed plugins are
+ * responsible for listening for their own events.)
+ */
 int16_t pluginHandleEvent(InstanceData* instanceData, void* event);
-void    pluginDraw(InstanceData* instanceData);
 
 #endif // nptest_platform_h_
--- a/modules/plugin/test/testplugin/nptest_qt.cpp
+++ b/modules/plugin/test/testplugin/nptest_qt.cpp
@@ -28,24 +28,41 @@
  * 
  * Contributor(s):
  *   Josh Aas <josh@mozilla.com>
  * 
  * ***** END LICENSE BLOCK ***** */
 
 #include "nptest_platform.h"
 
+bool
+pluginSupportsWindowMode()
+{
+  return false;
+}
+
+bool
+pluginSupportsWindowlessMode()
+{
+  return true;
+}
+
 NPError
 pluginInstanceInit(InstanceData* instanceData)
 {
   return NPERR_NO_ERROR;
 }
 
+void
+pluginInstanceShutdown(InstanceData* instanceData)
+{
+}
+
+void
+pluginWidgetInit(InstanceData* instanceData, void* oldWindow)
+{
+}
+
 int16_t
 pluginHandleEvent(InstanceData* instanceData, void* event)
 {
   return 0;
 }
-
-void
-pluginDraw(InstanceData* instanceData)
-{
-}
--- a/modules/plugin/test/testplugin/nptest_windows.cpp
+++ b/modules/plugin/test/testplugin/nptest_windows.cpp
@@ -36,38 +36,43 @@
 
 #include <windows.h>
 #include <unknwn.h>
 #include <gdiplus.h>
 using namespace Gdiplus;
 
 #pragma comment(lib, "gdiplus.lib")
 
+bool
+pluginSupportsWindowMode()
+{
+  return false;
+}
+
+bool
+pluginSupportsWindowlessMode()
+{
+  return true;
+}
+
 NPError
 pluginInstanceInit(InstanceData* instanceData)
 {
   return NPERR_NO_ERROR;
 }
 
-int16_t
-pluginHandleEvent(InstanceData* instanceData, void* event)
+void
+pluginInstanceShutdown(InstanceData* instanceData)
 {
-  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;
+void
+pluginWidgetInit(InstanceData* instanceData, void* oldWindow)
+{
+  // Should never be called since we don't support window mode (yet)
 }
 
 static Color
 GetColorsFromRGBA(PRUint32 rgba)
 {
   BYTE r, g, b, a;
   b = (rgba & 0xFF);
   g = ((rgba & 0xFF00) >> 8);
@@ -158,8 +163,26 @@ pluginDraw(InstanceData* instanceData)
   }
 
   // Shutdown GDI+
   GdiplusShutdown(gdiplusToken);
 
   // Pop our hdc changes off the resource stack
   RestoreDC(hdc, savedDCID);
 }
+
+int16_t
+pluginHandleEvent(InstanceData* instanceData, void* 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;
+}