Test for bug 598862. r=karlt a=blocking
authorChris Jones <jones.chris.g@gmail.com>
Mon, 08 Nov 2010 20:48:59 -0600
changeset 57139 1c7a0925aa46c5272296983ba54c2f0b418c06e6
parent 57138 f45d69fd9aaa5da1b6c6084c6909d80e1e26fa5e
child 57140 f915a22def59a09afd87941311fdbd69c27de8ff
push idunknown
push userunknown
push dateunknown
reviewerskarlt, blocking
bugs598862
milestone2.0b8pre
Test for bug 598862. r=karlt a=blocking
modules/plugin/test/crashtests/598862.html
modules/plugin/test/crashtests/crashtests.list
modules/plugin/test/testplugin/nptest.cpp
modules/plugin/test/testplugin/nptest_gtk2.cpp
modules/plugin/test/testplugin/nptest_platform.h
new file mode 100644
--- /dev/null
+++ b/modules/plugin/test/crashtests/598862.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+  <head>
+    <script>
+var unusedScreenX;
+function XSync() {
+  unusedScreenX = window.screenX;
+}
+
+function finish() {
+  document.documentElement.removeAttribute("class");
+}
+
+function cleanup() {
+  try {
+    document.getElementById("plugin").crash();
+  } catch (dontcare) {
+  }
+  window.setTimeout(finish, 100);
+}
+
+function scrollOfDeath() {
+  // Add a listener for the MozAfterPaint after the scrollTo below.
+  // If we don't crash during the scroll, we'll get the
+  // MozAfterPaint.
+  window.addEventListener("MozAfterPaint", cleanup, false);
+  window.scrollTo(0, 50);
+}
+
+//
+// The sequence of events we expect is
+//
+//   load (including initial paints of plugin that are cached)
+//   destroy X resources
+//   [X server has time to observe resource destruction]
+//   scrollTo
+//   [repaint using cached plugin surface: BOOM if buggy]
+//   MozAfterPaint
+//   cleanup
+//
+// However, this test is fundamentally nondeterministic.  There are
+// two main "failure" modes
+//   (1) X server doesn't have time to observer resource destruction
+//       before paint-after-scroll
+//   (2) plugin's crash notification arrives before
+//       paint-after-scroll
+// Both result in spurious passes.
+//
+// This test is anal about cleanup because it must be pretty sure that
+// the plugin subprocess is gone before starting the next test.  We
+// double-check that the plugin is gone by the time we're done by 
+// trying to crash it again, after we expect it to have crashed already.
+//
+function runTest() {
+  // Have the plugin throw away its X resources, one of which is
+  // probably a drawable to which we hold a reference
+  document.getElementById("plugin").destroySharedGfxStuff();
+
+  // Do something that's (hopefully) equivalent to an XSync() to allow
+  // the resource destruction to propagate to the server
+  XSync();
+
+  // Set up a scroll to happen soon
+  window.setTimeout(scrollOfDeath, 100);
+}
+
+window.addEventListener("MozReftestInvalidate", runTest, false);
+    </script>
+  </head>
+
+  <body style="width: 400px; height: 10000px;">
+    <embed id="plugin" type="application/x-test"
+           style="position:absolute;
+                  top:100px; left:50px; width:200px; height:200px;">
+    </embed>
+  </body>
+</html>
--- a/modules/plugin/test/crashtests/crashtests.list
+++ b/modules/plugin/test/crashtests/crashtests.list
@@ -1,7 +1,8 @@
 load 41276-1.html
 load 48856-1.html
 load 110650-1.html
 skip-if(!haveTestPlugin) skip-if(winWidget) asserts(0-4) load 522512-1.html
 skip-if(!haveTestPlugin) skip-if(cocoaWidget) script 539897-1.html
 skip-if(!haveTestPlugin) script 540114-1.html
 load 570884.html
+skip-if(!haveTestPlugin||http.platform!="X11"||!testPluginIsOOP) load 598862.html
--- a/modules/plugin/test/testplugin/nptest.cpp
+++ b/modules/plugin/test/testplugin/nptest.cpp
@@ -164,16 +164,17 @@ static bool getCookie(NPObject* npobj, c
 static bool getAuthInfo(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool asyncCallbackTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool checkGCRace(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool hangPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool callOnDestroy(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool reinitWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
+static bool destroySharedGfxStuff(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool propertyAndMethod(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getTopLevelWindowActivationState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getTopLevelWindowActivationEventCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getFocusState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getFocusEventCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getEventModel(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getReflector(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 
@@ -215,16 +216,17 @@ static const NPUTF8* sPluginMethodIdenti
   "getAuthInfo",
   "asyncCallbackTest",
   "checkGCRace",
   "hang",
   "getClipboardText",
   "callOnDestroy",
   "reinitWidget",
   "crashInNestedLoop",
+  "destroySharedGfxStuff",
   "propertyAndMethod",
   "getTopLevelWindowActivationState",
   "getTopLevelWindowActivationEventCount",
   "getFocusState",
   "getFocusEventCount",
   "getEventModel",
   "getReflector"
 };
@@ -267,16 +269,17 @@ static const ScriptableFunction sPluginM
   getAuthInfo,
   asyncCallbackTest,
   checkGCRace,
   hangPlugin,
   getClipboardText,
   callOnDestroy,
   reinitWidget,
   crashPluginInNestedLoop,
+  destroySharedGfxStuff,
   propertyAndMethod,
   getTopLevelWindowActivationState,
   getTopLevelWindowActivationEventCount,
   getFocusState,
   getFocusEventCount,
   getEventModel,
   getReflector
 };
@@ -2865,32 +2868,49 @@ bool
 crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args,
                         uint32_t argCount, NPVariant* result)
 {
   NPP npp = static_cast<TestNPObject*>(npobj)->npp;
   InstanceData* id = static_cast<InstanceData*>(npp->pdata);
   return pluginCrashInNestedLoop(id);
 }
 
+bool
+destroySharedGfxStuff(NPObject* npobj, const NPVariant* args,
+                        uint32_t argCount, NPVariant* result)
+{
+  NPP npp = static_cast<TestNPObject*>(npobj)->npp;
+  InstanceData* id = static_cast<InstanceData*>(npp->pdata);
+  return pluginDestroySharedGfxStuff(id);
+}
+
 #else
 bool
 getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount,
                  NPVariant* result)
 {
   // XXX Not implemented!
   return false;
 }
 
 bool
 crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args,
                         uint32_t argCount, NPVariant* result)
 {
   // XXX Not implemented!
   return false;
 }
+
+bool
+destroySharedGfxStuff(NPObject* npobj, const NPVariant* args,
+                        uint32_t argCount, NPVariant* result)
+{
+  // XXX Not implemented!
+  return false;
+}
 #endif
 
 bool
 callOnDestroy(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result)
 {
   NPP npp = static_cast<TestNPObject*>(npobj)->npp;
   InstanceData* id = static_cast<InstanceData*>(npp->pdata);
 
--- a/modules/plugin/test/testplugin/nptest_gtk2.cpp
+++ b/modules/plugin/test/testplugin/nptest_gtk2.cpp
@@ -719,8 +719,31 @@ pluginCrashInNestedLoop(InstanceData* in
     g_warning("Should have crashed in ProcessBrowserEvents");
   } else {
     g_warning("ProcessBrowserEvents did not fire");
   }
 
   // if we get here without crashing, then we'll trigger a test failure
   return true;
 }
+
+static int
+SleepThenDie(Display* display)
+{
+  NoteIntentionalCrash();
+  fprintf(stderr, "[testplugin:%d] SleepThenDie: sleeping\n", getpid());
+  sleep(1);
+
+  fprintf(stderr, "[testplugin:%d] SleepThenDie: dying\n", getpid());
+  _exit(1);
+}
+
+bool
+pluginDestroySharedGfxStuff(InstanceData* instanceData)
+{
+  // Closing the X socket results in the gdk error handler being
+  // invoked, which exit()s us.  We want to give the parent process a
+  // little while to do whatever it wanted to do, so steal the IO
+  // handler from gdk and set up our own that delays seppuku.
+  XSetIOErrorHandler(SleepThenDie);
+  close(ConnectionNumber(GDK_DISPLAY()));
+  return true;
+}
--- a/modules/plugin/test/testplugin/nptest_platform.h
+++ b/modules/plugin/test/testplugin/nptest_platform.h
@@ -129,9 +129,20 @@ std::string pluginGetClipboardText(Insta
 /**
  * Crash while in a nested event loop.  The goal is to catch the
  * browser processing the XPCOM event generated from the plugin's
  * crash while other plugin code is still on the stack. 
  * See https://bugzilla.mozilla.org/show_bug.cgi?id=550026.
  */
 bool pluginCrashInNestedLoop(InstanceData* instanceData);
 
+/**
+ * Destroy gfx things that might be shared with the parent process
+ * when we're run out-of-process.  It's not expected that this
+ * function will be called when the test plugin is loaded in-process,
+ * and bad things will happen if it is called.
+ *
+ * This call leaves the plugin subprocess in an undefined state.  It
+ * must not be used after this call or weird things will happen.
+ */
+bool pluginDestroySharedGfxStuff(InstanceData* instanceData);
+
 #endif // nptest_platform_h_