diff --git a/modules/plugin/test/mochitest/Makefile.in b/modules/plugin/test/mochitest/Makefile.in
index 1690595465e..12a6c5ea1db 100644
--- a/modules/plugin/test/mochitest/Makefile.in
+++ b/modules/plugin/test/mochitest/Makefile.in
@@ -117,6 +117,7 @@ endif
ifeq (gtk2,$(MOZ_WIDGET_TOOLKIT))
_MOCHITEST_FILES += \
test_copyText.html \
+ test_crash_nested_loop.html \
$(NULL)
endif
diff --git a/modules/plugin/test/mochitest/test_crash_nested_loop.html b/modules/plugin/test/mochitest/test_crash_nested_loop.html
new file mode 100644
index 00000000000..bad44294d33
--- /dev/null
+++ b/modules/plugin/test/mochitest/test_crash_nested_loop.html
@@ -0,0 +1,40 @@
+
+ Plugin crashing in nested loop
+
+
+
+
+
+
+
diff --git a/modules/plugin/test/testplugin/nptest.cpp b/modules/plugin/test/testplugin/nptest.cpp
index 5e996ebf68a..a23cb1f28c2 100644
--- a/modules/plugin/test/testplugin/nptest.cpp
+++ b/modules/plugin/test/testplugin/nptest.cpp
@@ -60,6 +60,8 @@
#define PLUGIN_VERSION "1.0.0.0"
#define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0]))
+#define STATIC_ASSERT(condition) \
+ extern void np_static_assert(int arg[(condition) ? 1 : -1])
//
// Intentional crash
@@ -67,7 +69,7 @@
int gCrashCount = 0;
-static void
+void
NoteIntentionalCrash()
{
char* bloatLog = getenv("XPCOM_MEM_BLOAT_LOG");
@@ -157,6 +159,7 @@ static bool hangPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount
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 propertyAndMethod(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
static const NPUTF8* sPluginMethodIdentifierNames[] = {
@@ -200,10 +203,11 @@ static const NPUTF8* sPluginMethodIdentifierNames[] = {
"getClipboardText",
"callOnDestroy",
"reinitWidget",
+ "crashInNestedLoop",
"propertyAndMethod"
};
static NPIdentifier sPluginMethodIdentifiers[ARRAY_LENGTH(sPluginMethodIdentifierNames)];
-static const ScriptableFunction sPluginMethodFunctions[ARRAY_LENGTH(sPluginMethodIdentifierNames)] = {
+static const ScriptableFunction sPluginMethodFunctions[] = {
npnEvaluateTest,
npnInvokeTest,
npnInvokeDefaultTest,
@@ -244,8 +248,13 @@ static const ScriptableFunction sPluginMethodFunctions[ARRAY_LENGTH(sPluginMetho
getClipboardText,
callOnDestroy,
reinitWidget,
+ crashPluginInNestedLoop,
propertyAndMethod
};
+
+STATIC_ASSERT(ARRAY_LENGTH(sPluginMethodIdentifierNames) ==
+ ARRAY_LENGTH(sPluginMethodFunctions));
+
static const NPUTF8* sPluginPropertyIdentifierNames[] = {
"propertyAndMethod"
};
@@ -2711,12 +2720,29 @@ getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount,
return true;
}
+bool
+crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args,
+ uint32_t argCount, NPVariant* result)
+{
+ NPP npp = static_cast(npobj)->npp;
+ InstanceData* id = static_cast(npp->pdata);
+ return pluginCrashInNestedLoop(id);
+}
+
#else
bool
getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount,
NPVariant* result)
{
- /// XXX Not implemented!
+ // XXX Not implemented!
+ return false;
+}
+
+bool
+crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args,
+ uint32_t argCount, NPVariant* result)
+{
+ // XXX Not implemented!
return false;
}
#endif
diff --git a/modules/plugin/test/testplugin/nptest.h b/modules/plugin/test/testplugin/nptest.h
index 327efcea8f7..779d4f9b795 100644
--- a/modules/plugin/test/testplugin/nptest.h
+++ b/modules/plugin/test/testplugin/nptest.h
@@ -127,4 +127,6 @@ typedef struct InstanceData {
void notifyDidPaint(InstanceData* instanceData);
+void NoteIntentionalCrash();
+
#endif // nptest_h_
diff --git a/modules/plugin/test/testplugin/nptest_gtk2.cpp b/modules/plugin/test/testplugin/nptest_gtk2.cpp
index ef5a9d073a5..b395ed389c4 100644
--- a/modules/plugin/test/testplugin/nptest_gtk2.cpp
+++ b/modules/plugin/test/testplugin/nptest_gtk2.cpp
@@ -35,12 +35,15 @@
#include "nptest_platform.h"
#include "npapi.h"
+#include
#include
#ifdef MOZ_X11
#include
#include
#endif
+#include
#include
+#include
using namespace std;
@@ -635,3 +638,79 @@ pluginGetClipboardText(InstanceData* instanceData)
return retText;
}
+
+//-----------------------------------------------------------------------------
+// NB: this test is quite gross in that it's not only
+// nondeterministic, but dependent on the guts of the nested glib
+// event loop handling code in PluginModule. We first sleep long
+// enough to make sure that the "detection timer" will be pending when
+// we enter the nested glib loop, then similarly for the "process browser
+// events" timer. Then we "schedule" the crasher thread to run at about the
+// same time we expect that the PluginModule "process browser events" task
+// will run. If all goes well, the plugin process will crash and generate the
+// XPCOM "plugin crashed" task, and the browser will run that task while still
+// in the "process some events" loop.
+
+static void*
+CrasherThread(void* data)
+{
+ // Give the parent thread a chance to send the message.
+ usleep(200);
+
+ // Exit (without running atexit hooks) rather than crashing with a signal
+ // so as to make timing more reliable. The process terminates immediately
+ // rather than waiting for a thread in the parent process to attach and
+ // generate a minidump.
+ _exit(1);
+
+ // not reached
+ return(NULL);
+}
+
+bool
+pluginCrashInNestedLoop(InstanceData* instanceData)
+{
+ // wait at least long enough for nested loop detector task to be pending ...
+ sleep(1);
+
+ // Run the nested loop detector by processing all events that are waiting.
+ bool found_event = false;
+ while (g_main_context_iteration(NULL, FALSE)) {
+ found_event = true;
+ }
+ if (!found_event) {
+ g_warning("DetectNestedEventLoop did not fire");
+ return true; // trigger a test failure
+ }
+
+ // wait at least long enough for the "process browser events" task to be
+ // pending ...
+ sleep(1);
+
+ // we'll be crashing soon, note that fact now to avoid messing with
+ // timing too much
+ NoteIntentionalCrash();
+
+ // schedule the crasher thread ...
+ pthread_t crasherThread;
+ if (0 != pthread_create(&crasherThread, NULL, CrasherThread, NULL)) {
+ g_warning("Failed to create thread");
+ return true; // trigger a test failure
+ }
+
+ // .. and hope it crashes at about the same time as the "process browser
+ // events" task (that should run in this loop) is being processed in the
+ // parent.
+ found_event = false;
+ while (g_main_context_iteration(NULL, FALSE)) {
+ found_event = true;
+ }
+ if (found_event) {
+ 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;
+}
diff --git a/modules/plugin/test/testplugin/nptest_platform.h b/modules/plugin/test/testplugin/nptest_platform.h
index 2e85306ecba..08f028edad1 100644
--- a/modules/plugin/test/testplugin/nptest_platform.h
+++ b/modules/plugin/test/testplugin/nptest_platform.h
@@ -126,4 +126,12 @@ void pluginDoInternalConsistencyCheck(InstanceData* instanceData, std::string& e
*/
std::string pluginGetClipboardText(InstanceData* instanceData);
+/**
+ * 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);
+
#endif // nptest_platform_h_