Bug 757726 - Part 6a: Add support for cloaking plugin names in navigator.plugins and navigator.mimeTypes enumeration. r=johns sr=bsmedberg
authorChris Peterson <cpeterson@mozilla.com>
Thu, 31 Oct 2013 22:19:09 -0700
changeset 157356 ae4cfe0a9bdbca44b5c741c04dd5f7314c8a09e8
parent 157355 0b1889f4bc33ff62a832ba94e9ab38af94ed2929
child 157357 49cab4f07faf0168cef26a8abe241e8be668e020
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersjohns, bsmedberg
bugs757726
milestone28.0a1
Bug 757726 - Part 6a: Add support for cloaking plugin names in navigator.plugins and navigator.mimeTypes enumeration. r=johns sr=bsmedberg
dom/base/nsMimeTypeArray.cpp
dom/base/nsMimeTypeArray.h
dom/base/nsPluginArray.cpp
dom/base/nsPluginArray.h
dom/plugins/test/mochitest/test_secondPlugin.html
dom/tests/mochitest/bugs/test_bug427744.html
layout/tools/reftest/reftest.js
modules/libpref/src/init/all.js
--- a/dom/base/nsMimeTypeArray.cpp
+++ b/dom/base/nsMimeTypeArray.cpp
@@ -20,23 +20,23 @@ using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsMimeTypeArray)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsMimeTypeArray)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsMimeTypeArray)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_2(nsMimeTypeArray,
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_3(nsMimeTypeArray,
                                         mWindow,
-                                        mMimeTypes)
+                                        mMimeTypes,
+                                        mHiddenMimeTypes)
 
 nsMimeTypeArray::nsMimeTypeArray(nsPIDOMWindow* aWindow)
-  : mWindow(aWindow),
-    mPluginMimeTypeCount(0)
+  : mWindow(aWindow)
 {
   SetIsDOMBinding();
 }
 
 nsMimeTypeArray::~nsMimeTypeArray()
 {
 }
 
@@ -45,17 +45,17 @@ nsMimeTypeArray::WrapObject(JSContext* a
 {
   return MimeTypeArrayBinding::Wrap(aCx, aScope, this);
 }
 
 void
 nsMimeTypeArray::Refresh()
 {
   mMimeTypes.Clear();
-  mPluginMimeTypeCount = 0;
+  mHiddenMimeTypes.Clear();
 }
 
 nsPIDOMWindow*
 nsMimeTypeArray::GetParentObject() const
 {
   MOZ_ASSERT(mWindow);
   return mWindow;
 }
@@ -76,40 +76,54 @@ nsMimeTypeArray::NamedItem(const nsAStri
 
 nsMimeType*
 nsMimeTypeArray::IndexedGetter(uint32_t aIndex, bool &aFound)
 {
   aFound = false;
 
   EnsurePluginMimeTypes();
 
-  MOZ_ASSERT(mMimeTypes.Length() >= mPluginMimeTypeCount);
-
-  if (aIndex >= mPluginMimeTypeCount) {
+  if (aIndex >= mMimeTypes.Length()) {
     return nullptr;
   }
 
   aFound = true;
 
   return mMimeTypes[aIndex];
 }
 
+static nsMimeType*
+FindMimeType(const nsTArray<nsRefPtr<nsMimeType> >& aMimeTypes,
+             const nsAString& aType)
+{
+  for (uint32_t i = 0; i < aMimeTypes.Length(); ++i) {
+    nsMimeType* mimeType = aMimeTypes[i];
+    if (aType.Equals(mimeType->Type())) {
+      return mimeType;
+    }
+  }
+
+  return nullptr;
+}
+
 nsMimeType*
 nsMimeTypeArray::NamedGetter(const nsAString& aName, bool &aFound)
 {
   aFound = false;
 
   EnsurePluginMimeTypes();
 
-  for (uint32_t i = 0; i < mMimeTypes.Length(); ++i) {
-    if (aName.Equals(mMimeTypes[i]->Type())) {
-      aFound = true;
+  nsMimeType* mimeType = FindMimeType(mMimeTypes, aName);
+  if (!mimeType) {
+    mimeType = FindMimeType(mHiddenMimeTypes, aName);
+  }
 
-      return mMimeTypes[i];
-    }
+  if (mimeType) {
+    aFound = true;
+    return mimeType;
   }
 
   // Now let's check with the MIME service.
   nsCOMPtr<nsIMIMEService> mimeSrv = do_GetService("@mozilla.org/mime;1");
   if (!mimeSrv) {
     return nullptr;
   }
 
@@ -143,46 +157,46 @@ nsMimeTypeArray::NamedGetter(const nsASt
         }
       }
     }
   }
 
   // If we got here, we support this type!  Say so.
   aFound = true;
 
+  // We don't want navigator.mimeTypes enumeration to expose MIME types with
+  // application handlers, so add them to the list of hidden MIME types.
   nsMimeType *mt = new nsMimeType(mWindow, aName);
-  mMimeTypes.AppendElement(mt);
+  mHiddenMimeTypes.AppendElement(mt);
 
   return mt;
 }
 
 uint32_t
 nsMimeTypeArray::Length()
 {
   EnsurePluginMimeTypes();
 
-  MOZ_ASSERT(mMimeTypes.Length() >= mPluginMimeTypeCount);
-
-  return mPluginMimeTypeCount;
+  return mMimeTypes.Length();
 }
 
 void
 nsMimeTypeArray::GetSupportedNames(nsTArray< nsString >& aRetval)
 {
   EnsurePluginMimeTypes();
 
   for (uint32_t i = 0; i < mMimeTypes.Length(); ++i) {
     aRetval.AppendElement(mMimeTypes[i]->Type());
   }
 }
 
 void
 nsMimeTypeArray::EnsurePluginMimeTypes()
 {
-  if (!mMimeTypes.IsEmpty() || !mWindow) {
+  if (!mMimeTypes.IsEmpty() || !mHiddenMimeTypes.IsEmpty() || !mWindow) {
     return;
   }
 
   nsCOMPtr<nsIDOMNavigator> navigator;
   mWindow->GetNavigator(getter_AddRefs(navigator));
 
   if (!navigator) {
     return;
@@ -190,19 +204,17 @@ nsMimeTypeArray::EnsurePluginMimeTypes()
 
   ErrorResult rv;
   nsPluginArray *pluginArray =
     static_cast<Navigator*>(navigator.get())->GetPlugins(rv);
   if (!pluginArray) {
     return;
   }
 
-  pluginArray->GetMimeTypes(mMimeTypes);
-
-  mPluginMimeTypeCount = mMimeTypes.Length();
+  pluginArray->GetMimeTypes(mMimeTypes, mHiddenMimeTypes);
 }
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsMimeType, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsMimeType, Release)
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_2(nsMimeType, mWindow, mPluginElement)
 
 nsMimeType::nsMimeType(nsPIDOMWindow* aWindow, nsPluginElement* aPluginElement,
--- a/dom/base/nsMimeTypeArray.h
+++ b/dom/base/nsMimeTypeArray.h
@@ -41,25 +41,26 @@ public:
   void GetSupportedNames(nsTArray< nsString >& retval);
 
 protected:
   void EnsurePluginMimeTypes();
   void Clear();
 
   nsCOMPtr<nsPIDOMWindow> mWindow;
 
-  // mMimeTypes contains all mime types handled by plugins followed by
-  // any other mime types that we handle internally and have been
-  // looked up before.
+  // mMimeTypes contains MIME types handled by non-hidden plugins, those
+  // popular plugins that must be exposed in navigator.plugins enumeration to
+  // avoid breaking web content. Likewise, mMimeTypes are exposed in
+  // navigator.mimeTypes enumeration.
   nsTArray<nsRefPtr<nsMimeType> > mMimeTypes;
 
-  // mPluginMimeTypeCount is the number of plugin mime types that we
-  // have in mMimeTypes. The plugin mime types are always at the
-  // beginning of the list.
-  uint32_t mPluginMimeTypeCount;
+  // mHiddenMimeTypes contains MIME types handled by plugins hidden from
+  // navigator.plugins enumeration or by an OS PreferredApplicationHandler.
+  // mHiddenMimeTypes are hidden from navigator.mimeTypes enumeration.
+  nsTArray<nsRefPtr<nsMimeType> > mHiddenMimeTypes;
 };
 
 class nsMimeType MOZ_FINAL : public nsWrapperCache
 {
 public:
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsMimeType)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(nsMimeType)
 
--- a/dom/base/nsPluginArray.cpp
+++ b/dom/base/nsPluginArray.cpp
@@ -1,17 +1,20 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsPluginArray.h"
 
+#include "mozilla/Preferences.h"
 #include "mozilla/dom/PluginArrayBinding.h"
 #include "mozilla/dom/PluginBinding.h"
+
+#include "nsCharSeparatedTokenizer.h"
 #include "nsMimeTypeArray.h"
 #include "Navigator.h"
 #include "nsIDocShell.h"
 #include "nsIWebNavigation.h"
 #include "nsPluginHost.h"
 #include "nsPluginTags.h"
 #include "nsIObserverService.h"
 #include "nsIWeakReference.h"
@@ -58,35 +61,46 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPlugin
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPluginArray)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPluginArray)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_2(nsPluginArray,
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_3(nsPluginArray,
                                         mWindow,
-                                        mPlugins)
+                                        mPlugins,
+                                        mHiddenPlugins)
+
+static void
+GetPluginMimeTypes(const nsTArray<nsRefPtr<nsPluginElement> >& aPlugins,
+                   nsTArray<nsRefPtr<nsMimeType> >& aMimeTypes)
+{
+  for (uint32_t i = 0; i < aPlugins.Length(); ++i) {
+    nsPluginElement *plugin = aPlugins[i];
+    aMimeTypes.AppendElements(plugin->MimeTypes());
+  }
+}
 
 void
-nsPluginArray::GetMimeTypes(nsTArray<nsRefPtr<nsMimeType> >& aMimeTypes)
+nsPluginArray::GetMimeTypes(nsTArray<nsRefPtr<nsMimeType> >& aMimeTypes,
+                            nsTArray<nsRefPtr<nsMimeType> >& aHiddenMimeTypes)
 {
   aMimeTypes.Clear();
+  aHiddenMimeTypes.Clear();
 
   if (!AllowPlugins()) {
     return;
   }
 
   EnsurePlugins();
 
-  for (uint32_t i = 0; i < mPlugins.Length(); ++i) {
-    nsPluginElement *plugin = mPlugins[i];
-    aMimeTypes.AppendElements(plugin->MimeTypes());
-  }
+  GetPluginMimeTypes(mPlugins, aMimeTypes);
+  GetPluginMimeTypes(mHiddenPlugins, aHiddenMimeTypes);
 }
 
 nsPluginElement*
 nsPluginArray::Item(uint32_t aIndex)
 {
   bool unused;
   return IndexedGetter(aIndex, unused);
 }
@@ -116,22 +130,24 @@ nsPluginArray::Refresh(bool aReloadDocum
 
     // Check if the number of plugins we know about are different from
     // the number of plugin tags the plugin host knows about. If the
     // lengths are different, we refresh. This is safe because we're
     // notified for every plugin enabling/disabling event that
     // happens, and therefore the lengths will be in sync only when
     // the both arrays contain the same plugin tags (though as
     // different types).
-    if (newPluginTags.Length() == mPlugins.Length()) {
+    uint32_t pluginCount = mPlugins.Length() + mHiddenPlugins.Length();
+    if (newPluginTags.Length() == pluginCount) {
       return;
     }
   }
 
   mPlugins.Clear();
+  mHiddenPlugins.Clear();
 
   nsCOMPtr<nsIDOMNavigator> navigator;
   mWindow->GetNavigator(getter_AddRefs(navigator));
 
   if (!navigator) {
     return;
   }
 
@@ -164,40 +180,51 @@ nsPluginArray::Invalidate()
 {
   nsCOMPtr<nsIObserverService> obsService =
     mozilla::services::GetObserverService();
   if (obsService) {
     obsService->RemoveObserver(this, "plugin-info-updated");
   }
 }
 
+static nsPluginElement*
+FindPlugin(const nsTArray<nsRefPtr<nsPluginElement> >& aPlugins,
+           const nsAString& aName)
+{
+  for (uint32_t i = 0; i < aPlugins.Length(); ++i) {
+    nsAutoString pluginName;
+    nsPluginElement* plugin = aPlugins[i];
+    plugin->GetName(pluginName);
+
+    if (pluginName.Equals(aName)) {
+      return plugin;
+    }
+  }
+
+  return nullptr;
+}
+
 nsPluginElement*
 nsPluginArray::NamedGetter(const nsAString& aName, bool &aFound)
 {
   aFound = false;
 
   if (!AllowPlugins()) {
     return nullptr;
   }
 
   EnsurePlugins();
 
-  for (uint32_t i = 0; i < mPlugins.Length(); ++i) {
-    nsAutoString pluginName;
-    nsPluginElement* plugin = mPlugins[i];
-    plugin->GetName(pluginName);
-
-    if (pluginName.Equals(aName)) {
-      aFound = true;
-
-      return plugin;
-    }
+  nsPluginElement* plugin = FindPlugin(mPlugins, aName);
+  if (!plugin) {
+    plugin = FindPlugin(mHiddenPlugins, aName);
   }
 
-  return nullptr;
+  aFound = (plugin != nullptr);
+  return plugin;
 }
 
 uint32_t
 nsPluginArray::Length()
 {
   if (!AllowPlugins()) {
     return 0;
   }
@@ -237,37 +264,85 @@ nsPluginArray::Observe(nsISupports *aSub
 bool
 nsPluginArray::AllowPlugins() const
 {
   nsCOMPtr<nsIDocShell> docShell = do_GetInterface(mWindow);
 
   return docShell && docShell->PluginsAllowedInCurrentDoc();
 }
 
+static bool
+HasStringPrefix(const nsCString& str, const nsACString& prefix) {
+  return str.Compare(prefix.BeginReading(), false, prefix.Length()) == 0;
+}
+
+static bool
+IsPluginEnumerable(const nsTArray<nsCString>& enumerableNames,
+                   const nsPluginTag* pluginTag)
+{
+  const nsCString& pluginName = pluginTag->mName;
+
+  const uint32_t length = enumerableNames.Length();
+  for (uint32_t i = 0; i < length; i++) {
+    const nsCString& name = enumerableNames[i];
+    if (HasStringPrefix(pluginName, name)) {
+      return true; // don't hide plugin
+    }
+  }
+
+  return false; // hide plugin!
+}
+
 void
 nsPluginArray::EnsurePlugins()
 {
-  if (!mPlugins.IsEmpty()) {
+  if (!mPlugins.IsEmpty() || !mHiddenPlugins.IsEmpty()) {
     // We already have an array of plugin elements.
     return;
   }
 
   nsRefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
   if (!pluginHost) {
     // We have no plugin host.
     return;
   }
 
   nsTArray<nsRefPtr<nsPluginTag> > pluginTags;
   pluginHost->GetPlugins(pluginTags);
 
+  nsTArray<nsCString> enumerableNames;
+
+  const nsAdoptingCString& enumerableNamesPref =
+      Preferences::GetCString("plugins.enumerable_names");
+
+  bool disablePluginHiding = !enumerableNamesPref ||
+                             enumerableNamesPref.EqualsLiteral("*");
+
+  if (!disablePluginHiding) {
+    nsCCharSeparatedTokenizer tokens(enumerableNamesPref, ',');
+    while (tokens.hasMoreTokens()) {
+      const nsCSubstring& token = tokens.nextToken();
+      if (!token.IsEmpty()) {
+        enumerableNames.AppendElement(token);
+      }
+    }
+  }
+
   // need to wrap each of these with a nsPluginElement, which is
   // scriptable.
   for (uint32_t i = 0; i < pluginTags.Length(); ++i) {
-    mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+    nsPluginTag* pluginTag = pluginTags[i];
+
+    // Add the plugin to the list of hidden plugins or non-hidden plugins?
+    nsTArray<nsRefPtr<nsPluginElement> >& pluginArray =
+        (disablePluginHiding || IsPluginEnumerable(enumerableNames, pluginTag))
+        ? mPlugins
+        : mHiddenPlugins;
+
+    pluginArray.AppendElement(new nsPluginElement(mWindow, pluginTag));
   }
 }
 
 // nsPluginElement implementation.
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPluginElement)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPluginElement)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPluginElement)
--- a/dom/base/nsPluginArray.h
+++ b/dom/base/nsPluginArray.h
@@ -38,34 +38,46 @@ public:
 
   // nsPluginArray registers itself as an observer with a weak reference.
   // This can't be done in the constructor, because at that point its
   // refcount is 0 (and it gets destroyed upon registration). So, Init()
   // must be called after construction.
   void Init();
   void Invalidate();
 
-  void GetMimeTypes(nsTArray<nsRefPtr<nsMimeType> >& aMimeTypes);
+  void GetMimeTypes(nsTArray<nsRefPtr<nsMimeType> >& aMimeTypes,
+                    nsTArray<nsRefPtr<nsMimeType> >& aHiddenMimeTypes);
 
   // PluginArray WebIDL methods
 
   nsPluginElement* Item(uint32_t aIndex);
   nsPluginElement* NamedItem(const nsAString& aName);
   void Refresh(bool aReloadDocuments);
   nsPluginElement* IndexedGetter(uint32_t aIndex, bool &aFound);
   nsPluginElement* NamedGetter(const nsAString& aName, bool &aFound);
   uint32_t Length();
   void GetSupportedNames(nsTArray< nsString >& aRetval);
 
 private:
   bool AllowPlugins() const;
   void EnsurePlugins();
 
   nsCOMPtr<nsPIDOMWindow> mWindow;
+
+  // Many sites check whether a particular plugin is installed by enumerating
+  // all navigator.plugins, checking each plugin's name. These sites should
+  // just check navigator.plugins["Popular Plugin Name"] instead. mPlugins
+  // contains those popular plugins that must be exposed in navigator.plugins
+  // enumeration to avoid breaking web content.
   nsTArray<nsRefPtr<nsPluginElement> > mPlugins;
+
+  // mHiddenPlugins contains plugins that can be queried by
+  // navigator.plugins["Hidden Plugin Name"] but do not need to be exposed in
+  // navigator.plugins enumeration.
+  nsTArray<nsRefPtr<nsPluginElement> > mHiddenPlugins;
 };
 
 class nsPluginElement MOZ_FINAL : public nsISupports,
                                   public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsPluginElement)
--- a/dom/plugins/test/mochitest/test_secondPlugin.html
+++ b/dom/plugins/test/mochitest/test_secondPlugin.html
@@ -4,33 +4,69 @@
 
     <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
     <script type="text/javascript" src="utils.js"></script>
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   </head>
 
   <body onload="run()">
     <script class="testbody" type="application/javascript">
+      "use strict";
+
       SimpleTest.waitForExplicitFinish();
       setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
       setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
 
+      function findPlugin(pluginName) {
+          for (var i = 0; i < navigator.plugins.length; i++) {
+            var plugin = navigator.plugins[i];
+            if (plugin.name === pluginName) {
+              return plugin;
+            }
+          }
+          return null;
+      }
+
+      function findMimeType(mimeTypeType) {
+          for (var i = 0; i < navigator.mimeTypes.length; i++) {
+            var mimeType = navigator.mimeTypes[i];
+            if (mimeType.type === mimeTypeType) {
+              return mimeType;
+            }
+          }
+          return null;
+      }
+
       function run() {
-        var foundFirstPlugin = false;
-        var foundSecondPlugin = false;
-        for (var index in navigator.plugins) {
-          var plugin = navigator.plugins[index];
-          if (plugin.name == "Test Plug-in") foundFirstPlugin = true;
-          if (plugin.name == "Second Test Plug-in") foundSecondPlugin = true;
-        }
-        ok(foundFirstPlugin, "Should have a plugin named 'Test Plug-in'");
-        ok(foundSecondPlugin, "Should have a plugin named 'Second Test Plug-in'");
+        // Add "Test Plug-in" (but not "Second Test Plug-in") to the list of
+        // unhidden plugins. This test must modify the "plugins.enumerable_names"
+        // pref BEFORE accessing the navigator.plugins or navigator.mimeTypes
+        // arrays because they only read the pref when they first initialize
+        // their internal arrays!
+        var prefs = SpecialPowers.Cc["@mozilla.org/preferences-service;1"].getService(SpecialPowers.Ci.nsIPrefBranch);
+        var defaultEnumerableNamesPref = prefs.getCharPref("plugins.enumerable_names");
+        prefs.setCharPref("plugins.enumerable_names", defaultEnumerableNamesPref + ",Test Plug-in");
 
         var pluginElement = document.getElementById("plugin");
         is(pluginElement.identifierToStringTest("foo"), "foo", "Should be able to call a function provided by the plugin");
 
+        ok(navigator.plugins["Test Plug-in"], "Should have queried a non-hidden plugin named 'Test Plug-in'");
+        ok(navigator.plugins["Second Test Plug-in"], "Should have queried a hidden plugin named 'Test Plug-in'");
+
+        ok(findPlugin("Test Plug-in"), "Should have found a non-hidden plugin named 'Test Plug-in'");
+        ok(!findPlugin("Second Test Plug-in"), "Should NOT found a hidden plugin named 'Test Plug-in'");
+
+        ok(navigator.mimeTypes["application/x-test"], "Should have queried a non-hidden MIME type named 'application/x-test'");
+        ok(navigator.mimeTypes["application/x-second-test"], "Should have queried a MIME type named 'application/x-second-test'");
+
+        ok(findMimeType("application/x-test"), "Should have found a non-hidden MIME type named 'application/x-test'");
+        ok(!findMimeType("application/x-second-test"), "Should NOT have found a MIME type named 'application/x-second-test'");
+
+        // Restore original pref to hide "Test Plug-in" and "Second Test Plug-in".
+        prefs.setCharPref("plugins.enumerable_names", defaultEnumerableNamesPref);
+
         SimpleTest.finish();
       }
     </script>
 
     <object id="plugin" type="application/x-second-test" width=200 height=200></object>
   </body>
 </html>
--- a/dom/tests/mochitest/bugs/test_bug427744.html
+++ b/dom/tests/mochitest/bugs/test_bug427744.html
@@ -14,21 +14,20 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Bug 427744 **/
 
-var found = false;
-for (var i = 0; i < navigator.plugins.length; i++) {
-  if (navigator.plugins[i].name == "Test Plug-in") {
-    found = true;
-    is(navigator.plugins[i].version, "1.0.0.0", "Should have seen the right version");
-  }
-}
-ok(found, "Should have seen the test plugin");
+var firstPlugin = navigator.plugins["Test Plug-in"];
+ok(firstPlugin, "Should have seen the test plugin");
+is(firstPlugin.version, "1.0.0.0", "Should have seen the right test plugin version");
+
+var secondPlugin = navigator.plugins["Second Test Plug-in"];
+ok(secondPlugin, "Should have seen the second test plugin");
+is(secondPlugin.version, "1.0.0.0", "Should have seen the right second test plugin version");
 
 </script>
 </pre>
 </body>
 </html>
--- a/layout/tools/reftest/reftest.js
+++ b/layout/tools/reftest/reftest.js
@@ -631,27 +631,19 @@ function BuildConditionSandbox(aURL) {
     }
 
     // Set OSX to the Mac OS X version for Mac, and 0 otherwise.
     var osxmatch = /Mac OS X (\d+.\d+)$/.exec(hh.oscpu);
     sandbox.OSX = osxmatch ? parseFloat(osxmatch[1]) : 0;
 
     // see if we have the test plugin available,
     // and set a sandox prop accordingly
-    sandbox.haveTestPlugin = false;
-
     var navigator = gContainingWindow.navigator;
-    for (var i = 0; i < navigator.mimeTypes.length; i++) {
-        if (navigator.mimeTypes[i].type == "application/x-test" &&
-            navigator.mimeTypes[i].enabledPlugin != null &&
-            navigator.mimeTypes[i].enabledPlugin.name == "Test Plug-in") {
-            sandbox.haveTestPlugin = true;
-            break;
-        }
-    }
+    var testPlugin = navigator.plugins["Test Plug-in"];
+    sandbox.haveTestPlugin = !!testPlugin;
 
     // Set a flag on sandbox if the windows default theme is active
     var box = gContainingWindow.document.createElement("box");
     box.setAttribute("id", "_box_windowsDefaultTheme");
     gContainingWindow.document.documentElement.appendChild(box);
     sandbox.windowsDefaultTheme = (gContainingWindow.getComputedStyle(box, null).display == "none");
     gContainingWindow.document.documentElement.removeChild(box);
 
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -2027,16 +2027,25 @@ pref("dom.promise.enabled", true);
 // (0 is disabled)
 // Disabled on all platforms per bug 705748 until the found issues are
 // resolved.
 pref("hangmonitor.timeout", 0);
 
 pref("plugins.load_appdir_plugins", false);
 // If true, plugins will be click to play
 pref("plugins.click_to_play", false);
+
+// A comma-delimited list of plugin name prefixes matching plugins that will be
+// exposed when enumerating navigator.plugins[]. For example, prefix "Shockwave"
+// matches both Adobe Flash Player ("Shockwave Flash") and Adobe Shockwave
+// Player ("Shockwave for Director"). To hide all plugins from enumeration, use
+// the empty string "" to match no plugin names. To allow all plugins to be
+// enumerated, use the string "*" to match all plugin names.
+pref("plugins.enumerable_names", "*");
+
 // The default value for nsIPluginTag.enabledState (STATE_ENABLED = 2)
 pref("plugin.default.state", 2);
 
 // How long in minutes we will allow a plugin to work after the user has chosen
 // to allow it "now"
 pref("plugin.sessionPermissionNow.intervalInMinutes", 60);
 // How long in days we will allow a plugin to work after the user has chosen
 // to allow it persistently.