Bug 391728: No placeholder for disabled plugins. r=jst, r=josh, sr=bz
authorDave Townsend <dtownsend@oxymoronical.com>
Tue, 09 Sep 2008 16:43:21 +0100
changeset 19014 4b9f9c1f7e1deb1d2c5b566c538dfe200dbdded6
parent 19013 250798f9981924698912b4ff52174b081a197db6
child 19015 4a1ad24a36bcdd67c3b9498e70c0ee6a8c3f9f44
push idunknown
push userunknown
push dateunknown
reviewersjst, josh, bz
bugs391728
milestone1.9.1b1pre
Bug 391728: No placeholder for disabled plugins. r=jst, r=josh, sr=bz
content/base/src/nsObjectLoadingContent.cpp
content/base/src/nsObjectLoadingContent.h
content/base/test/Makefile.in
content/base/test/file_bug391728.html
content/base/test/file_bug391728_2.html
content/base/test/test_bug391728.html
content/base/test/test_bug425013.html
content/events/public/nsIEventStateManager.h
layout/style/nsCSSPseudoClassList.h
layout/style/nsCSSRuleProcessor.cpp
modules/plugin/Makefile.in
modules/plugin/base/src/nsPluginsDirDarwin.cpp
modules/plugin/test/Makefile.in
modules/plugin/test/testplugin/Info.plist
modules/plugin/test/testplugin/Makefile.in
modules/plugin/test/testplugin/nptest.cpp
modules/plugin/test/testplugin/nptest.rc
--- a/content/base/src/nsObjectLoadingContent.cpp
+++ b/content/base/src/nsObjectLoadingContent.cpp
@@ -158,41 +158,49 @@ nsAsyncInstantiateEvent::Run()
 }
 
 /**
  * A task for firing PluginNotFound and PluginBlocklisted DOM Events.
  */
 class nsPluginErrorEvent : public nsRunnable {
 public:
   nsCOMPtr<nsIContent> mContent;
-  PRBool mBlocklisted;
+  PluginSupportState mState;
 
-  nsPluginErrorEvent(nsIContent* aContent, PRBool aBlocklisted)
+  nsPluginErrorEvent(nsIContent* aContent, PluginSupportState aState)
     : mContent(aContent),
-      mBlocklisted(aBlocklisted)
+      mState(aState)
   {}
 
   ~nsPluginErrorEvent() {}
 
   NS_IMETHOD Run();
 };
 
 NS_IMETHODIMP
 nsPluginErrorEvent::Run()
 {
   LOG(("OBJLC []: Firing plugin not found event for content %p\n",
        mContent.get()));
-  if (mBlocklisted)
-    nsContentUtils::DispatchTrustedEvent(mContent->GetDocument(), mContent,
-                                         NS_LITERAL_STRING("PluginBlocklisted"),
-                                         PR_TRUE, PR_TRUE);
-  else
-    nsContentUtils::DispatchTrustedEvent(mContent->GetDocument(), mContent,
-                                         NS_LITERAL_STRING("PluginNotFound"),
-                                         PR_TRUE, PR_TRUE);
+  nsString type;
+  switch (mState) {
+    case ePluginUnsupported:
+      type = NS_LITERAL_STRING("PluginNotFound");
+      break;
+    case ePluginDisabled:
+      type = NS_LITERAL_STRING("PluginDisabled");
+      break;
+    case ePluginBlocklisted:
+      type = NS_LITERAL_STRING("PluginBlocklisted");
+      break;
+    default:
+      return NS_OK;
+  }
+  nsContentUtils::DispatchTrustedEvent(mContent->GetDocument(), mContent,
+                                       type, PR_TRUE, PR_TRUE);
 
   return NS_OK;
 }
 
 class AutoNotifier {
   public:
     AutoNotifier(nsObjectLoadingContent* aContent, PRBool aNotify) :
       mContent(aContent), mNotify(aNotify) {
@@ -227,38 +235,39 @@ class AutoNotifier {
 
 /**
  * A class that will automatically fall back if a |rv| variable has a failure
  * code when this class is destroyed. It does not notify.
  */
 class AutoFallback {
   public:
     AutoFallback(nsObjectLoadingContent* aContent, const nsresult* rv)
-      : mContent(aContent), mResult(rv), mTypeUnsupported(PR_FALSE) {}
+      : mContent(aContent), mResult(rv), mPluginState(ePluginOtherState) {}
     ~AutoFallback() {
       if (NS_FAILED(*mResult)) {
         LOG(("OBJLC [%p]: rv=%08x, falling back\n", mContent, *mResult));
         mContent->Fallback(PR_FALSE);
-        if (mTypeUnsupported) {
-          mContent->mTypeUnsupported = PR_TRUE;
+        if (mPluginState != ePluginOtherState) {
+          mContent->mPluginState = mPluginState;
         }
       }
     }
 
     /**
-     * This function can be called to indicate that, after falling back,
-     * mTypeUnsupported should be set to true.
+     * This should be set to something other than ePluginOtherState to indicate
+     * a specific failure that should be passed on.
      */
-    void TypeUnsupported() {
-      mTypeUnsupported = PR_TRUE;
-    }
+     void SetPluginState(PluginSupportState aState) {
+       NS_ASSERTION(aState != ePluginOtherState, "Should not be setting ePluginOtherState");
+       mPluginState = aState;
+     }
   private:
     nsObjectLoadingContent* mContent;
     const nsresult* mResult;
-    PRBool mTypeUnsupported;
+    PluginSupportState mPluginState;
 };
 
 /**
  * A class that automatically sets mInstantiating to false when it goes
  * out of scope.
  */
 class AutoSetInstantiatingToFalse {
   public:
@@ -335,17 +344,17 @@ IsPluginEnabledByExtension(nsIURI* uri, 
 
 nsObjectLoadingContent::nsObjectLoadingContent()
   : mPendingInstantiateEvent(nsnull)
   , mChannel(nsnull)
   , mType(eType_Loading)
   , mInstantiating(PR_FALSE)
   , mUserDisabled(PR_FALSE)
   , mSuppressed(PR_FALSE)
-  , mTypeUnsupported(PR_FALSE)
+  , mPluginState(ePluginOtherState)
 {
 }
 
 nsObjectLoadingContent::~nsObjectLoadingContent()
 {
   DestroyImageLoadingContent();
   if (mFrameLoader) {
     mFrameLoader->Destroy();
@@ -568,30 +577,26 @@ nsObjectLoadingContent::OnStartRequest(n
       }
 
       break;
     case eType_Loading:
       NS_NOTREACHED("Should not have a loading type here!");
     case eType_Null:
       LOG(("OBJLC [%p]: Unsupported type, falling back\n", this));
       // Need to fallback here (instead of using the case below), so that we can
-      // set mTypeUnsupported without it being overwritten. This is also why we
+      // set mPluginState without it being overwritten. This is also why we
       // return early.
       Fallback(PR_FALSE);
 
       PluginSupportState pluginState = GetPluginSupportState(thisContent,
                                                              mContentType);
       // Do nothing, but fire the plugin not found event if needed
-      if (pluginState == ePluginUnsupported ||
-          pluginState == ePluginBlocklisted) {
-        FirePluginError(thisContent, pluginState == ePluginBlocklisted);
-      }
-      if (pluginState != ePluginDisabled &&
-          pluginState != ePluginBlocklisted) {
-        mTypeUnsupported = PR_TRUE;
+      if (pluginState != ePluginOtherState) {
+        FirePluginError(thisContent, pluginState);
+        mPluginState = pluginState;
       }
       return NS_BINDING_ABORTED;
   }
 
   if (mFinalListener) {
     mType = newType;
     rv = mFinalListener->OnStartRequest(aRequest, aContext);
     if (NS_FAILED(rv)) {
@@ -887,18 +892,26 @@ nsObjectLoadingContent::ObjectState() co
     case eType_Null:
       if (mSuppressed)
         return NS_EVENT_STATE_SUPPRESSED;
       if (mUserDisabled)
         return NS_EVENT_STATE_USERDISABLED;
 
       // Otherwise, broken
       PRInt32 state = NS_EVENT_STATE_BROKEN;
-      if (mTypeUnsupported) {
-        state |= NS_EVENT_STATE_TYPE_UNSUPPORTED;
+      switch (mPluginState) {
+        case ePluginDisabled:
+          state |= NS_EVENT_STATE_HANDLER_DISABLED;
+          break;
+        case ePluginBlocklisted:
+          state |= NS_EVENT_STATE_HANDLER_BLOCKED;
+          break;
+        case ePluginUnsupported:
+          state |= NS_EVENT_STATE_TYPE_UNSUPPORTED;
+          break;
       }
       return state;
   };
   NS_NOTREACHED("unknown type?");
   // this return statement only exists to avoid a compile warning
   return 0;
 }
 
@@ -956,26 +969,21 @@ IsAboutBlank(nsIURI* aURI)
   return str.EqualsLiteral("about:blank");  
 }
 
 void
 nsObjectLoadingContent::UpdateFallbackState(nsIContent* aContent,
                                             AutoFallback& fallback,
                                             const nsCString& aTypeHint)
 {
-  PluginSupportState pluginState = GetPluginDisabledState(aTypeHint);
-  if (pluginState == ePluginUnsupported) {
-    // For unknown plugins notify the UI and allow the unknown plugin binding
-    // to attach.
-    FirePluginError(aContent, PR_FALSE);
-    fallback.TypeUnsupported();
-  }
-  else if (pluginState == ePluginBlocklisted) {
-    // For blocklisted plugins just send a notification to the UI.
-    FirePluginError(aContent, PR_TRUE);
+  // Notify the UI and update the fallback state
+  PluginSupportState state = GetPluginSupportState(aContent, aTypeHint);
+  if (state != ePluginOtherState) {
+    fallback.SetPluginState(state);
+    FirePluginError(aContent, state);
   }
 }
 
 nsresult
 nsObjectLoadingContent::LoadObject(nsIURI* aURI,
                                    PRBool aNotify,
                                    const nsCString& aTypeHint,
                                    PRBool aForceLoad)
@@ -1443,17 +1451,18 @@ nsObjectLoadingContent::UnloadContent()
 {
   // Don't notify in CancelImageRequests. We do it ourselves.
   CancelImageRequests(PR_FALSE);
   if (mFrameLoader) {
     mFrameLoader->Destroy();
     mFrameLoader = nsnull;
   }
   mType = eType_Null;
-  mUserDisabled = mSuppressed = mTypeUnsupported = PR_FALSE;
+  mUserDisabled = mSuppressed = PR_FALSE;
+  mPluginState = ePluginOtherState;
 }
 
 void
 nsObjectLoadingContent::NotifyStateChanged(ObjectType aOldType,
                                           PRInt32 aOldState,
                                           PRBool aSync)
 {
   LOG(("OBJLC [%p]: Notifying about state change: (%u, %x) -> (%u, %x) (sync=%i)\n",
@@ -1493,22 +1502,22 @@ nsObjectLoadingContent::NotifyStateChang
     while ((shell = iter.GetNextShell())) {
       shell->RecreateFramesFor(thisContent);
     }
   }
 }
 
 /* static */ void
 nsObjectLoadingContent::FirePluginError(nsIContent* thisContent,
-                                        PRBool blocklisted)
+                                        PluginSupportState state)
 {
   LOG(("OBJLC []: Dispatching nsPluginErrorEvent for content %p\n",
        thisContent));
 
-  nsCOMPtr<nsIRunnable> ev = new nsPluginErrorEvent(thisContent, blocklisted);
+  nsCOMPtr<nsIRunnable> ev = new nsPluginErrorEvent(thisContent, state);
   nsresult rv = NS_DispatchToCurrentThread(ev);
   if (NS_FAILED(rv)) {
     NS_WARNING("failed to dispatch nsPluginErrorEvent");
   }
 }
 
 nsObjectLoadingContent::ObjectType
 nsObjectLoadingContent::GetTypeOfContent(const nsCString& aMIMEType)
@@ -1761,17 +1770,17 @@ nsObjectLoadingContent::ShouldShowDefaul
 {
   if (nsContentUtils::GetBoolPref("plugin.default_plugin_disabled", PR_FALSE)) {
     return PR_FALSE;
   }
 
   return GetPluginSupportState(aContent, aContentType) == ePluginUnsupported;
 }
 
-/* static */ nsObjectLoadingContent::PluginSupportState
+/* static */ PluginSupportState
 nsObjectLoadingContent::GetPluginSupportState(nsIContent* aContent,
                                               const nsCString& aContentType)
 {
   if (!aContent->IsNodeOfType(nsINode::eHTML)) {
     return ePluginOtherState;
   }
 
   if (aContent->Tag() == nsGkAtoms::embed ||
@@ -1798,17 +1807,17 @@ nsObjectLoadingContent::GetPluginSupport
         nsStyleUtil::IsSignificantChild(child, PR_TRUE, PR_FALSE);
     }
   }
 
   return hasAlternateContent ? ePluginOtherState :
     GetPluginDisabledState(aContentType);
 }
 
-/* static */ nsObjectLoadingContent::PluginSupportState
+/* static */ PluginSupportState
 nsObjectLoadingContent::GetPluginDisabledState(const nsCString& aContentType)
 {
   nsCOMPtr<nsIPluginHost> host(do_GetService("@mozilla.org/plugin/host;1"));
   if (!host) {
     return ePluginUnsupported;
   }
   nsresult rv = host->IsPluginEnabledForType(aContentType.get());
   if (rv == NS_ERROR_PLUGIN_DISABLED)
--- a/content/base/src/nsObjectLoadingContent.h
+++ b/content/base/src/nsObjectLoadingContent.h
@@ -54,16 +54,25 @@
 #include "nsIRunnable.h"
 #include "nsIChannelClassifier.h"
 
 class nsAsyncInstantiateEvent;
 class AutoNotifier;
 class AutoFallback;
 class AutoSetInstantiatingToFalse;
 
+enum PluginSupportState {
+  ePluginUnsupported,  // The plugin is not supported (not installed, say)
+  ePluginDisabled,     // The plugin has been explicitly disabled by the
+                       // user.
+  ePluginBlocklisted,  // The plugin is blocklisted and disabled
+  ePluginOtherState    // Something else (e.g. not a plugin at all as far
+                       // as we can tell).
+};
+
 /**
  * INVARIANTS OF THIS CLASS
  * - mChannel is non-null between asyncOpen and onStopRequest (NOTE: Only needs
  *   to be valid until onStopRequest is called on mFinalListener, not
  *   necessarily until the channel calls onStopRequest on us)
  * - mChannel corresponds to the channel that gets passed to the
  *   nsIRequestObserver/nsIStreamListener methods
  * - mChannel can be cancelled and ODA calls will stop
@@ -247,17 +256,17 @@ class nsObjectLoadingContent : public ns
      */
     void NotifyStateChanged(ObjectType aOldType, PRInt32 aOldState,
                             PRBool aSync);
 
     /**
      * Fires the "Plugin not found" event. This function doesn't do any checks
      * whether it should be fired, the caller should do that.
      */
-    static void FirePluginError(nsIContent* thisContent, PRBool blocklisted);
+    static void FirePluginError(nsIContent* thisContent, PluginSupportState state);
 
     ObjectType GetTypeOfContent(const nsCString& aMIMEType);
 
     /**
      * For a classid, returns the MIME type that can be used to instantiate
      * a plugin for this ID.
      *
      * @return NS_ERROR_NOT_AVAILABLE Unsupported class ID.
@@ -327,25 +336,16 @@ class nsObjectLoadingContent : public ns
     /**
      * Whether to treat this content as a plugin, even though we can't handle
      * the type. This function impl should match the checks in the plugin host.
      * aContentType is the MIME type we ended up with.
      */
     static PRBool ShouldShowDefaultPlugin(nsIContent* aContent,
                                           const nsCString& aContentType);
 
-    enum PluginSupportState {
-      ePluginUnsupported,  // The plugin is not supported (not installed, say)
-      ePluginDisabled,     // The plugin has been explicitly disabled by the
-                           // user.
-      ePluginBlocklisted,  // The plugin is blocklisted and disabled
-      ePluginOtherState    // Something else (e.g. not a plugin at all as far
-                           // as we can tell).
-    };
-
     /**
      * Get the plugin support state for the given content node and MIME type.
      * This is used for purposes of determining whether to fire PluginNotFound
      * events etc.  aContentType is the MIME type we ended up with.
      *
      * This should only be called if the type of this content is eType_Null.
      */
     static PluginSupportState
@@ -413,16 +413,16 @@ class nsObjectLoadingContent : public ns
     /**
      * Whether we are about to call instantiate on our frame. If we aren't,
      * SetFrame needs to asynchronously call Instantiate.
      */
     PRBool                      mInstantiating : 1;
     // Blocking status from content policy
     PRBool                      mUserDisabled  : 1;
     PRBool                      mSuppressed    : 1;
-    // Whether we fell back because of an unsupported type
-    PRBool                      mTypeUnsupported:1;
+    // A specific state that caused us to fallback
+    PluginSupportState          mPluginState;
 
     friend class nsAsyncInstantiateEvent;
 };
 
 
 #endif
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -198,16 +198,19 @@ include $(topsrcdir)/config/rules.mk
 		test_bug444030.xhtml \
 		test_NodeIterator_basics_filters.xhtml \
 		test_NodeIterator_mutations_1.xhtml \
 		test_NodeIterator_mutations_2.html \
 		test_bug28293.html \
 		file_bug28293.sjs \
 		test_title.html \
 		test_bug453521.html \
+		test_bug391728.html \
+		file_bug391728.html \
+		file_bug391728_2.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
 
 check::
 	@$(EXIT_ON_ERROR) \
 	for f in $(subst .cpp,,$(CPP_UNIT_TESTS)); do \
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_bug391728.html
@@ -0,0 +1,100 @@
+<html>
+<head>
+<style type="text/css">
+embed,object {
+  border: 1px solid black;
+}
+
+embed:-moz-handler-disabled,
+object:-moz-handler-disabled {
+  border-style: dotted !important;
+}
+
+embed:-moz-handler-blocked,
+object:-moz-handler-blocked {
+  border-style: dashed !important;
+}
+
+embed:-moz-type-unsupported,
+object:-moz-type-unsupported {
+  border-style: none !important;
+}
+</style>
+<script type="text/javascript">
+function unknown_plugin_detected(event) {
+  window.parent.unknown_plugin_detected(event);
+}
+
+function blocked_plugin_detected(event) {
+  window.parent.blocked_plugin_detected(event);
+}
+
+function disabled_plugin_detected(event) {
+  window.parent.disabled_plugin_detected(event);
+}
+
+document.addEventListener("PluginNotFound", unknown_plugin_detected, true);
+document.addEventListener("PluginDisabled", disabled_plugin_detected, true);
+document.addEventListener("PluginBlocklisted", blocked_plugin_detected, true);
+</script>
+</head>
+<body>
+<!-- Embeds always fire events and have the pseudo class attached -->
+<div><embed id="plugin1" style="width: 100px; height: 100px" type="application/x-test"></div>
+<div><embed id="plugin2" style="width: 100px; height: 100px" src="data:application/x-test,test"></div>
+
+<!-- So do objects with no content and no pluginurl -->
+<div><object id="plugin3" style="width: 100px; height: 100px" type="application/x-test"></object></div>
+<div><object id="plugin4" style="width: 100px; height: 100px" data="data:application/x-test,test"></object></div>
+
+<!-- The mimetype of the actual data is supposed to be used in preference -->
+<div><embed id="plugin5" style="width: 100px; height: 100px" type="application/x-unknown" src="data:application/x-test,test"></div>
+<div><object id="plugin6" style="width: 100px; height: 100px" type="application/x-unknown" data="data:application/x-test,test"></object></div>
+
+<!-- Params are not considered content -->
+<div><object id="plugin7" style="width: 100px; height: 100px" type="application/x-test">
+  <param name="foo" value="bar">
+</object></div>
+<div><object id="plugin8" style="width: 100px; height: 100px" data="data:application/x-test,test">
+  <param name="foo" value="bar">
+</object></div>
+
+<!-- Nor is whitespace -->
+<div><object id="plugin9" style="width: 100px; height: 100px" type="application/x-test">
+
+  
+</object></div>
+<div><object id="plugin10" style="width: 100px; height: 100px" data="data:application/x-test,test">
+
+  
+</object></div>
+
+<!-- Pluginurl forces the psuedo class and error event regardless of content -->
+<div><object id="plugin11" style="width: 100px; height: 100px" type="application/x-test">
+  <param name="pluginurl" value="http://foo">
+  <p>Fallback content</p>
+</object></div>
+<div><object id="plugin12" style="width: 100px; height: 100px" data="data:application/x-test,test">
+  <param name="pluginurl" value="http://foo">
+  <p>Fallback content</p>
+</object></div>
+
+<!-- No errors or psuedo classes for objects with fallback content -->
+<div><object id="fallback1" style="width: 100px; height: 100px" type="application/x-test">
+  <p>Fallback content</p>
+</object></div>
+<div><object id="fallback2" style="width: 100px; height: 100px" data="data:application/x-test,test">
+  <p>Fallback content</p>
+</object></div>
+
+<!-- Even other plugins are considered content so no errors dispatched from these
+     objects, but the inner embeds do get processed -->
+<div><object id="fallback3" style="width: 100px; height: 100px" type="application/x-test">
+  <embed id="plugin13" style="width: 100px; height: 100px" type="application/x-test">
+</object></div>
+<div><object id="fallback4" style="width: 100px; height: 100px" data="data:application/x-test,test">
+  <embed id="plugin14" style="width: 100px; height: 100px" type="application/x-test">
+</object></div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_bug391728_2.html
@@ -0,0 +1,99 @@
+<html>
+<head>
+<style type="text/css">
+embed,object {
+  border: 1px solid black;
+}
+
+embed:-moz-handler-disabled,
+object:-moz-handler-disabled {
+  border-style: dotted !important;
+}
+
+embed:-moz-handler-blocked,
+object:-moz-handler-blocked {
+  border-style: dashed !important;
+}
+
+embed:-moz-type-unsupported,
+object:-moz-type-unsupported {
+  border-style: none !important;
+}
+</style>
+<script type="text/javascript">
+function unknown_plugin_detected(event) {
+  window.parent.unknown_plugin_detected(event);
+}
+
+function blocked_plugin_detected(event) {
+  window.parent.blocked_plugin_detected(event);
+}
+
+function disabled_plugin_detected(event) {
+  window.parent.disabled_plugin_detected(event);
+}
+
+document.addEventListener("PluginNotFound", unknown_plugin_detected, true);
+document.addEventListener("PluginDisabled", disabled_plugin_detected, true);
+document.addEventListener("PluginBlocklisted", blocked_plugin_detected, true);
+</script>
+</head>
+<body>
+<!-- Embeds always fire events and have the pseudo class attached -->
+<div><embed id="plugin1" style="width: 100px; height: 100px" type="application/x-unknown"></div>
+<div><embed id="plugin2" style="width: 100px; height: 100px" src="data:application/x-unknown,test"></div>
+
+<!-- So do objects with no content and no pluginurl -->
+<div><object id="plugin3" style="width: 100px; height: 100px" type="application/x-unknown"></object></div>
+<div><object id="plugin4" style="width: 100px; height: 100px" data="data:application/x-unknown,test"></object></div>
+
+<!-- The mimetype of the actual data is supposed to be used in preference -->
+<div><embed id="plugin5" style="width: 100px; height: 100px" type="application/x-test" src="data:application/x-unknown,test"></div>
+<div><object id="plugin6" style="width: 100px; height: 100px" type="application/x-test" data="data:application/x-unknown,test"></object></div>
+
+<!-- Params are not considered content -->
+<div><object id="plugin7" style="width: 100px; height: 100px" type="application/x-unknown">
+  <param name="foo" value="bar">
+</object></div>
+<div><object id="plugin8" style="width: 100px; height: 100px" data="data:application/x-unknown,test">
+  <param name="foo" value="bar">
+</object></div>
+
+<!-- Nor is whitespace -->
+<div><object id="plugin9" style="width: 100px; height: 100px" type="application/x-unknown">
+
+  
+</object></div>
+<div><object id="plugin10" style="width: 100px; height: 100px" data="data:application/x-unknown,test">
+
+  
+</object></div>
+
+<!-- Pluginurl forces the psuedo class and error event regardless of content -->
+<div><object id="plugin11" style="width: 100px; height: 100px" type="application/x-unknown">
+  <param name="pluginurl" value="http://foo">
+  <p>Fallback content</p>
+</object></div>
+<div><object id="plugin12" style="width: 100px; height: 100px" data="data:application/x-unknown,test">
+  <param name="pluginurl" value="http://foo">
+  <p>Fallback content</p>
+</object></div>
+
+<!-- No errors or psuedo classes for objects with fallback content -->
+<div><object id="fallback1" style="width: 100px; height: 100px" type="application/x-unknown">
+  <p>Fallback content</p>
+</object></div>
+<div><object id="fallback2" style="width: 100px; height: 100px" data="data:application/x-unknown,test">
+  <p>Fallback content</p>
+</object></div>
+
+<!-- Even other plugins are considered content so no errors dispatched from these
+     objects, but the inner embeds do get processed -->
+<div><object id="fallback3" style="width: 100px; height: 100px" type="application/x-unknown">
+  <embed id="plugin13" style="width: 100px; height: 100px" type="application/x-unknown">
+</object></div>
+<div><object id="fallback4" style="width: 100px; height: 100px" data="data:application/x-unknown,test">
+  <embed id="plugin14" style="width: 100px; height: 100px" type="application/x-unknown">
+</object></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_bug391728.html
@@ -0,0 +1,176 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=391728
+-->
+<head>
+  <title>Test for Bug 391728</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=391728">Mozilla Bug 391728</a>
+<p id="display"></p>
+<div id="content">
+  <iframe id="testframe" width="150" height="250" src="about:blank"></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 391728 **/
+// Plugins that should dispatch error events and have the pseudo classes set
+const PLUGIN_COUNT = 14;
+// Plugins that should neither dispatch error events or have the pseudo classes set
+const FALLBACK_COUNT = 4;
+
+var gNextTest = null;
+var gUnknown = [];
+var gBlocked = [];
+var gDisabled = [];
+
+function get_test_plugin() {
+  var ph = Components.classes["@mozilla.org/plugin/host;1"]
+                     .getService(Components.interfaces.nsIPluginHost);
+  var tags = ph.getPluginTags({});
+  
+  // Find the test plugin
+  for (var i = 0; i < tags.length; i++) {
+    if (tags[i].name == "Test Plug-in")
+      return tags[i];
+  }
+}
+
+function disabled_plugin_detected(event) {
+  gDisabled.push(event.target.id);
+}
+
+function blocked_plugin_detected(event) {
+  gBlocked.push(event.target.id);
+}
+
+function unknown_plugin_detected(event) {
+  gUnknown.push(event.target.id);
+}
+
+function init_test() {
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  // Make sure the blocklist is off for the duration of this test
+  var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                        .getService(Components.interfaces.nsIPrefBranch);
+  prefs.setBoolPref("extensions.blocklist.enabled", false);
+
+  var plugin = get_test_plugin();
+  ok(plugin, "Test plugin was not found");
+  
+  is(plugin.description, "Plug-in for testing purposes.", "Test plugin had an incorrect description");
+  is(plugin.version, "1.0.0.0", "Test plugin had an incorrect version");
+  ok(!plugin.disabled, "Test plugin should not be disabled");
+  ok(!plugin.blocklisted, "Test plugin should not be blocklisted");
+  
+  var frame = document.getElementById("testframe");
+  frame.addEventListener("load", frame_loaded, true);
+  load_frame(test_normal, "file_bug391728");
+}
+
+function finish_test() {
+  var plugin = get_test_plugin();
+  plugin.disabled = false;
+  plugin.blocklisted = false;
+  var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                        .getService(Components.interfaces.nsIPrefBranch);
+  prefs.clearUserPref("extensions.blocklist.enabled");
+  SimpleTest.finish();
+}
+
+function load_frame(nextTest, file) {
+  gNextTest = nextTest;
+  gDisabled = [];
+  gUnknown = [];
+  gBlocked = [];
+  var frame = document.getElementById("testframe");
+  frame.src = file + ".html?" + Math.random();
+}
+
+function frame_loaded() {
+  // We must delay to wait for the plugin sources to be loaded :(
+  setTimeout(gNextTest, 500);
+}
+
+function test_style(expected) {
+  var frame = document.getElementById("testframe");
+  for (var i = 1; i <= PLUGIN_COUNT; i++) {
+    var tag = frame.contentDocument.getElementById("plugin" + i);
+    ok(tag, "Plugin " + i + " did not exist");
+    var style = frame.contentWindow.getComputedStyle(tag, null);
+    is(style.borderTopStyle, expected, "Plugin " + i + " had an incorrect border style");
+  }
+  for (i = 1; i <= FALLBACK_COUNT; i++) {
+    var tag = frame.contentDocument.getElementById("fallback" + i);
+    ok(tag, "Fallback plugin " + i + " did not exist");
+    var style = frame.contentWindow.getComputedStyle(tag, null);
+    is(style.borderTopStyle, "solid", "Fallback plugin " + i + " had an incorrect border style");
+  }
+}
+
+function test_list(list) {
+  for (var i = 1; i <= PLUGIN_COUNT; i++) {
+    ok(list.indexOf("plugin" + i) >= 0, "Plugin " + i + " did not send the event");
+  }
+  for (i = 1; i <= FALLBACK_COUNT; i++) {
+    ok(list.indexOf("fallback" + i) < 0, "Fallback plugin " + i + " should not have sent the event");
+  }
+}
+
+function test_normal() {
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  is(gUnknown.length, 0, "Should not have been any unknown plugins");
+  is(gDisabled.length, 0, "Should not have been any disabled plugins");
+  is(gBlocked.length, 0, "Should not have been any blocked plugins");
+  test_style("solid");
+  var plugin = get_test_plugin();
+  plugin.disabled = true;
+  load_frame(test_disabled, "file_bug391728");
+}
+
+function test_disabled() {
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  is(gUnknown.length, 0, "Should not have been any unknown plugins");
+  is(gDisabled.length, PLUGIN_COUNT, "Should have been disabled plugins");
+  test_list(gDisabled);
+  is(gBlocked.length, 0, "Should not have been any blocked plugins");
+  test_style("dotted");
+  var plugin = get_test_plugin();
+  ok(plugin.disabled, "Plugin lost its disabled status");
+  plugin.disabled = false;
+  plugin.blocklisted = true;
+  load_frame(test_blocked, "file_bug391728");
+}
+
+function test_blocked() {
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  is(gUnknown.length, 0, "Should not have been any unknown plugins");
+  is(gDisabled.length, 0, "Should not have been any disabled plugins");
+  is(gBlocked.length, PLUGIN_COUNT, "Should have been blocked plugins");
+  test_list(gBlocked);
+  test_style("dashed");
+  var plugin = get_test_plugin();
+  ok(plugin.blocklisted, "Plugin lost its blocklist status");
+  load_frame(test_unknown, "file_bug391728_2");
+}
+
+function test_unknown() {
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  is(gUnknown.length, PLUGIN_COUNT, "Should have been unknown plugins");
+  test_list(gUnknown);
+  is(gDisabled.length, 0, "Should not have been any disabled plugins");
+  is(gBlocked.length, 0, "Should not have been any blocked plugins");
+  test_style("none");
+  finish_test();
+}
+
+SimpleTest.waitForExplicitFinish();
+window.addEventListener("load", init_test, false);
+</script>
+</pre>
+</body>
+</html>
--- a/content/base/test/test_bug425013.html
+++ b/content/base/test/test_bug425013.html
@@ -51,28 +51,22 @@ document.addEventListener("PluginNotFoun
 <p>Alternate content</p>
 </object>
 
 <embed type="a/b" id="embed2"></embed>
 
 <script class="testbody" type="text/javascript">
 function runtests()
 {
-  is(missingPlugins[0], document.getElementById("obj1"),
-     "Wrong missing plugin element 1");
-  is(missingPlugins[1], document.getElementById("embed1"),
-     "Wrong missing plugin element 2");
-  is(missingPlugins[2], document.getElementById("obj2"),
-     "Wrong missing plugin element 3");
-  is(missingPlugins[3], document.getElementById("embed2"),
-     "Wrong missing plugin element 4");
-  is(missingPlugins[4], document.getElementById("obj3"),
-     "Wrong missing plugin element 5");
+  ok(missingPlugins.indexOf(document.getElementById("obj1")) >= 0, "Missing plugin element obj1");
+  ok(missingPlugins.indexOf(document.getElementById("embed1")) >= 0, "Missing plugin element embed1");
+  ok(missingPlugins.indexOf(document.getElementById("embed2")) >= 0, "Missing plugin element embed2");
+  ok(missingPlugins.indexOf(document.getElementById("obj3")) >= 0, "Missing plugin element obj3");
 
-  is(missingPlugins.length, 5, "Wrong number of missing plugins");
+  is(missingPlugins.length, 4, "Wrong number of missing plugins");
 
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
--- a/content/events/public/nsIEventStateManager.h
+++ b/content/events/public/nsIEventStateManager.h
@@ -213,10 +213,16 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsIEventSt
 // user (eg an image which hasn't started coming in yet)
 #define NS_EVENT_STATE_LOADING       0x00200000
 // Content is of a type that gecko can't handle
 #define NS_EVENT_STATE_TYPE_UNSUPPORTED \
                                      0x00400000
 #ifdef MOZ_MATHML
 #define NS_EVENT_STATE_INCREMENT_SCRIPT_LEVEL 0x00800000
 #endif
+// Handler for the content has been blocked
+#define NS_EVENT_STATE_HANDLER_BLOCKED \
+                                     0x01000000
+// Handler for the content has been disabled
+#define NS_EVENT_STATE_HANDLER_DISABLED \
+                                     0x02000000
 
 #endif // nsIEventStateManager_h__
--- a/layout/style/nsCSSPseudoClassList.h
+++ b/layout/style/nsCSSPseudoClassList.h
@@ -87,16 +87,18 @@ CSS_PSEUDO_CLASS(nthOfType, ":nth-of-typ
 CSS_PSEUDO_CLASS(nthLastOfType, ":nth-last-of-type")
 
 // Image, object, etc state pseudo-classes
 CSS_PSEUDO_CLASS(mozBroken, ":-moz-broken")
 CSS_PSEUDO_CLASS(mozUserDisabled, ":-moz-user-disabled")
 CSS_PSEUDO_CLASS(mozSuppressed, ":-moz-suppressed")
 CSS_PSEUDO_CLASS(mozLoading, ":-moz-loading")
 CSS_PSEUDO_CLASS(mozTypeUnsupported, ":-moz-type-unsupported")
+CSS_PSEUDO_CLASS(mozHandlerDisabled, ":-moz-handler-disabled")
+CSS_PSEUDO_CLASS(mozHandlerBlocked, ":-moz-handler-blocked")
 
 CSS_PSEUDO_CLASS(mozHasHandlerRef, ":-moz-has-handlerref")
 
 // Match nodes that are HTML but not XHTML
 CSS_PSEUDO_CLASS(mozIsHTML, ":-moz-is-html")
 
 // Matches anything when the specified look-and-feel metric is set
 CSS_PSEUDO_CLASS(mozSystemMetric, ":-moz-system-metric")
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -1507,16 +1507,22 @@ static PRBool SelectorMatches(RuleProces
       stateToCheck = NS_EVENT_STATE_SUPPRESSED;
     }
     else if (nsCSSPseudoClasses::mozLoading == pseudoClass->mAtom) {
       stateToCheck = NS_EVENT_STATE_LOADING;
     }
     else if (nsCSSPseudoClasses::mozTypeUnsupported == pseudoClass->mAtom) {
       stateToCheck = NS_EVENT_STATE_TYPE_UNSUPPORTED;
     }
+    else if (nsCSSPseudoClasses::mozHandlerDisabled == pseudoClass->mAtom) {
+      stateToCheck = NS_EVENT_STATE_HANDLER_DISABLED;
+    }
+    else if (nsCSSPseudoClasses::mozHandlerBlocked == pseudoClass->mAtom) {
+      stateToCheck = NS_EVENT_STATE_HANDLER_BLOCKED;
+    }
     else if (nsCSSPseudoClasses::defaultPseudo == pseudoClass->mAtom) {
       stateToCheck = NS_EVENT_STATE_DEFAULT;
     }
     else if (nsCSSPseudoClasses::required == pseudoClass->mAtom) {
       stateToCheck = NS_EVENT_STATE_REQUIRED;
     }
     else if (nsCSSPseudoClasses::optional == pseudoClass->mAtom) {
       stateToCheck = NS_EVENT_STATE_OPTIONAL;
--- a/modules/plugin/Makefile.in
+++ b/modules/plugin/Makefile.in
@@ -77,16 +77,17 @@ endif
 ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
 TOOL_DIRS += default/mac
 endif
 
 ifdef ENABLE_TESTS
 ifneq (,$(filter WINNT Darwin Linux,$(OS_ARCH)))
 TOOL_DIRS += sdk
 endif
+DIRS += test
 endif
 
 endif # MOZ_PLUGINS
 
 include $(topsrcdir)/config/rules.mk
 
 ifdef MOZ_PLUGINS
 $(DIST)/bin/plugins:
--- a/modules/plugin/base/src/nsPluginsDirDarwin.cpp
+++ b/modules/plugin/base/src/nsPluginsDirDarwin.cpp
@@ -148,16 +148,27 @@ static PRBool IsLoadablePlugin(CFURLRef 
       close(f);
     }
   }
   return isLoadable;
 }
 
 PRBool nsPluginsDir::IsPluginFile(nsIFile* file)
 {
+  nsCString temp;
+  file->GetNativeLeafName(temp);
+  /*
+   * Don't load the VDP fake plugin, to avoid tripping a bad bug in OS X
+   * 10.5.3 (see bug 436575).
+   */
+  if (!strcmp(temp.get(), "VerifiedDownloadPlugin.plugin")) {
+    NS_WARNING("Preventing load of VerifiedDownloadPlugin.plugin (see bug 436575)");
+    return PR_FALSE;
+  }
+    
   CFURLRef pluginURL = NULL;
   if (NS_FAILED(toCFURLRef(file, pluginURL)))
     return PR_FALSE;
   
   PRBool isPluginFile = PR_FALSE;
   
   CFBundleRef pluginBundle = CFBundleCreate(kCFAllocatorDefault, pluginURL);
   if (pluginBundle) {
@@ -281,26 +292,16 @@ nsPluginFile::~nsPluginFile() {}
 nsresult nsPluginFile::LoadPlugin(PRLibrary* &outLibrary)
 {
     const char* path;
 
     if (!mPlugin)
         return NS_ERROR_NULL_POINTER;
 
     nsCAutoString temp;
-    mPlugin->GetNativeLeafName(temp);
-    /*
-     * Don't load the VDP fake plugin, to avoid tripping a bad bug in OS X
-     * 10.5.3 (see bug 436575).
-     */
-    if (!strcmp(temp.get(), "VerifiedDownloadPlugin.plugin")) {
-        NS_WARNING("Preventing load of VerifiedDownloadPlugin.plugin (see bug 436575)");
-        return NS_ERROR_FAILURE;
-    }
-
     mPlugin->GetNativePath(temp);
     path = temp.get();
 
     outLibrary = PR_LoadLibrary(path);
     pLibrary = outLibrary;
     if (!outLibrary) {
         return NS_ERROR_FAILURE;
     }
new file mode 100644
--- /dev/null
+++ b/modules/plugin/test/Makefile.in
@@ -0,0 +1,49 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is mozilla.org
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Dave Townsend <dtownsend@oxymoronical.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE = plugin
+
+DIRS = testplugin
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/modules/plugin/test/testplugin/Info.plist
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>English</string>
+	<key>CFBundleExecutable</key>
+	<string>libnptest.dylib</string>
+	<key>CFBundleIdentifier</key>
+	<string>com.mozilla.TestPlugin</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundlePackageType</key>
+	<string>BRPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0.0.0</string>
+	<key>CFBundleSignature</key>
+	<string>TEST</string>
+	<key>CFBundleVersion</key>
+	<string>1.0.0.0</string>
+	<key>WebPluginName</key>
+	<string>Test Plug-in</string>
+	<key>WebPluginDescription</key>
+	<string>Plug-in for testing purposes.</string>
+	<key>WebPluginMIMETypes</key>
+	<dict>
+		<key>application/x-test</key>
+		<dict>
+			<key>WebPluginExtensions</key>
+			<array>
+				<string>tst</string>
+			</array>
+			<key>WebPluginTypeDescription</key>
+			<string>Test mimetype</string>
+		</dict>
+	</dict>
+</dict>
+</plist>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/modules/plugin/test/testplugin/Makefile.in
@@ -0,0 +1,76 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is mozilla.org
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Dave Townsend <dtownsend@oxymoronical.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH     = ../../../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE       = nptest
+LIBRARY_NAME = nptest
+MODULE_NAME  = TestPlugin
+
+REQUIRES = \
+  plugin \
+  $(NULL)
+
+# Need to custom install plugins
+NO_DIST_INSTALL	= 1
+NO_INSTALL = 1
+
+ifeq ($(OS_ARCH),WINNT)
+RCFILE    = nptest.rc
+RESFILE   = nptest.res
+endif
+
+CPPSRCS   = nptest.cpp
+
+include $(topsrcdir)/config/rules.mk
+
+install-plugin: $(SHARED_LIBRARY)
+ifdef SHARED_LIBRARY
+ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
+	$(INSTALL) $(srcdir)/Info.plist $(DIST)/bin/plugins/Test.plugin/Contents
+	$(INSTALL) $(SHARED_LIBRARY) $(DIST)/bin/plugins/Test.plugin/Contents/MacOS
+else
+	$(INSTALL) $(SHARED_LIBRARY) $(DIST)/bin/plugins
+endif
+endif
+
+libs:: install-plugin
new file mode 100644
--- /dev/null
+++ b/modules/plugin/test/testplugin/nptest.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is mozilla.org
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Dave Townsend <dtownsend@oxymoronical.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "npapi.h"
+#include "npupp.h"
+
+#if defined(XP_UNIX)
+
+#define PLUGIN_NAME         "Test Plug-in"
+#define PLUGIN_DESCRIPTION  "Plug-in for testing purposes."
+#define PLUGIN_VERSION      "1.0.0.0"
+
+NP_EXPORT(char*)
+NP_GetPluginVersion(void) {
+    return PLUGIN_VERSION;
+}
+
+NP_EXPORT(char*)
+NP_GetMIMEDescription(void) {
+    return "application/x-test:tst:Test mimetype";
+}
+
+NP_EXPORT(NPError)
+NP_Initialize(NPNetscapeFuncs*, NPPluginFuncs*) {
+    return NPERR_NO_ERROR;
+}
+
+NP_EXPORT(NPError)
+NP_Shutdown(void) {
+    return NPERR_NO_ERROR;
+}
+
+NP_EXPORT(NPError) 
+NP_GetValue(void *future, NPPVariable aVariable, void *aValue) {
+   switch (aVariable) {
+     case NPPVpluginNameString:
+       *((char **)aValue) = PLUGIN_NAME;
+       break;
+     case NPPVpluginDescriptionString:
+       *((char **)aValue) = PLUGIN_DESCRIPTION;
+       break;
+     default:
+       return NPERR_INVALID_PARAM;
+       break;
+   }
+   return NPERR_NO_ERROR;
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/modules/plugin/test/testplugin/nptest.rc
@@ -0,0 +1,42 @@
+#include<winver.h>
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION    1,0,0,0
+ PRODUCTVERSION 1,0,0,0
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904e4"
+        BEGIN
+            VALUE "CompanyName", "mozilla.org"
+            VALUE "FileDescription", "Plug-in for testing purposes."
+            VALUE "FileExtents", "tst"
+            VALUE "FileOpenName", "Test mimetype"
+            VALUE "FileVersion", "1.0"
+            VALUE "InternalName", "nptest"
+            VALUE "MIMEType", "application/x-test"
+            VALUE "OriginalFilename", "nptest.dll"
+            VALUE "ProductName", "Test Plug-in"
+            VALUE "ProductVersion", "1.0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1252
+    END
+END