Bug 1178963 part 3. Make nsPluginHost aware of fake plugins. r=peterv
authorJohn Schoenick <jschoenick@mozilla.com>
Wed, 20 May 2015 15:30:05 +0200
changeset 254636 4d96b3d42fd9663eacd36c1ef39c55e6ee7af452
parent 254635 a73ee6e0fc92722fbb1256b3eb43d8dc7a2267a1
child 254637 a983b06b1be393b73b6aef5cb2a2b6729145633e
push id14246
push userryanvm@gmail.com
push dateMon, 27 Jul 2015 14:44:11 +0000
treeherderfx-team@c2e8b03c28bc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs1178963
milestone42.0a1
Bug 1178963 part 3. Make nsPluginHost aware of fake plugins. r=peterv
dom/plugins/base/nsIPluginHost.idl
dom/plugins/base/nsPluginHost.cpp
dom/plugins/base/nsPluginHost.h
dom/plugins/base/nsPluginTags.cpp
dom/plugins/base/nsPluginTags.h
dom/webidl/FakePluginTagInit.webidl
dom/webidl/moz.build
--- a/dom/plugins/base/nsIPluginHost.idl
+++ b/dom/plugins/base/nsIPluginHost.idl
@@ -31,21 +31,21 @@ interface nsIPluginPlayPreviewInfo : nsI
 interface nsIClearSiteDataCallback : nsISupports
 {
     /**
      * callback with the result from a call to clearSiteData
      */
     void callback(in nsresult rv);
 };
 
-[scriptable, uuid(2bed7533-50e3-45dd-9e47-102e2c6f5ea8)]
+[scriptable, uuid(50677599-323f-4c7d-a450-307bdb7acbf0)]
 interface nsIPluginHost : nsISupports
 {
   /**
-   * Causes the plugins directory to be searched again for new plugin 
+   * Causes the plugins directory to be searched again for new plugin
    * libraries.
    */
   void reloadPlugins();
 
   void getPluginTags([optional] out unsigned long aPluginCount,
     [retval, array, size_is(aPluginCount)] out nsIPluginTag aResults);
 
   /*
@@ -59,16 +59,17 @@ interface nsIPluginHost : nsISupports
   const uint32_t FLAG_CLEAR_ALL = 0;
   const uint32_t FLAG_CLEAR_CACHE = 1;
 
   /*
    * For use with Get*ForType functions
    */
   const uint32_t EXCLUDE_NONE     = 0;
   const uint32_t EXCLUDE_DISABLED = 1 << 0;
+  const uint32_t EXCLUDE_FAKE     = 1 << 1;
 
   /*
    * Clear site data for a given plugin.
    *
    * @param plugin: the plugin to clear data for, such as one returned by
    *                nsIPluginHost.getPluginTags.
    * @param domain: the domain to clear data for. If this argument is null,
    *                clear data for all domains. Otherwise, it must be a domain
@@ -122,42 +123,80 @@ interface nsIPluginHost : nsISupports
 
   nsIPluginPlayPreviewInfo getPlayPreviewInfo(in AUTF8String mimeType);
 
   /**
    * Get the "permission string" for the plugin.  This is a string that can be
    * passed to the permission manager to see whether the plugin is allowed to
    * run, for example.  This will typically be based on the plugin's "nice name"
    * and its blocklist state.
+   *
+   * @mimeType The MIME type we're interested in.
+   * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE.
    */
   ACString getPermissionStringForType(in AUTF8String mimeType,
                                       [optional] in uint32_t excludeFlags);
 
   /**
    * Get the nsIPluginTag for this MIME type. This method works with both
    * enabled and disabled/blocklisted plugins, but an enabled plugin will
    * always be returned if available.
    *
+   * A fake plugin tag, if one exists and is available, will be returned in
+   * preference to NPAPI plugin tags unless excluded by the excludeFlags.
+   *
+   * @mimeType The MIME type we're interested in.
+   * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE.
+   *
    * @throws NS_ERROR_NOT_AVAILABLE if no plugin is available for this MIME
    *         type.
    */
   nsIPluginTag getPluginTagForType(in AUTF8String mimeType,
                                    [optional] in uint32_t excludeFlags);
 
   /**
    * Get the nsIPluginTag enabled state for this MIME type.  See
    * nsIPluginTag.enabledState.
+   *
+   * @mimeType The MIME type we're interested in.
+   * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE.
    */
   unsigned long getStateForType(in AUTF8String mimeType,
                                 [optional] in uint32_t excludeFlags);
 
   /**
    * Get the blocklist state for a MIME type.  See nsIPluginTag.blocklistState.
+   *
+   * @mimeType The MIME type we're interested in.
+   * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE.
    */
   uint32_t getBlocklistStateForType(in AUTF8String aMimeType,
                                     [optional] in uint32_t excludeFlags);
 
   /**
+   * Create a fake plugin tag, register it, and return it.  The argument is a
+   * FakePluginTagInit dictionary.  See documentation in
+   * FakePluginTagInit.webidl for what it should look like.  Will throw
+   * NS_ERROR_UNEXPECTED if there is already a fake plugin registered with the
+   * given handler URI.
+   */
+  [implicit_jscontext]
+  nsIFakePluginTag registerFakePlugin(in jsval initDictionary);
+
+  /**
+   * Get a reference to an existing fake plugin tag for the given MIME type, if
+   * any.  Can return null.
+   */
+  nsIFakePluginTag getFakePlugin(in AUTF8String mimeType);
+
+  /**
+   * Unregister a fake plugin.  The argument can be the .handlerURI.spec of an
+   * existing nsIFakePluginTag, or just a known handler URI string that was
+   * passed in the FakePluginTagInit when registering.
+   */
+  void unregisterFakePlugin(in AUTF8String handlerURI);
+
+  /**
    * Returns true if plugins with the given mimetype will run out of process.
    */
   boolean isPluginOOP(in AUTF8String aMimeType);
 };
 
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -47,16 +47,17 @@
 #include "nsIScriptChannel.h"
 #include "nsIBlocklistService.h"
 #include "nsVersionComparator.h"
 #include "nsIObjectLoadingContent.h"
 #include "nsIWritablePropertyBag2.h"
 #include "nsICategoryManager.h"
 #include "nsPluginStreamListenerPeer.h"
 #include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/FakePluginTagInitBinding.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/plugins/PluginAsyncSurrogate.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/plugins/PluginTypes.h"
 #include "mozilla/Preferences.h"
 
 #include "nsEnumeratorUtils.h"
 #include "nsXPCOM.h"
@@ -114,16 +115,17 @@
 #endif
 
 #include "npapi.h"
 
 using namespace mozilla;
 using mozilla::TimeStamp;
 using mozilla::plugins::PluginTag;
 using mozilla::plugins::PluginAsyncSurrogate;
+using mozilla::dom::FakePluginTagInit;
 
 // Null out a strong ref to a linked list iteratively to avoid
 // exhausting the stack (bug 486349).
 #define NS_ITERATIVE_UNREF_LIST(type_, list_, mNext_)                \
   {                                                                  \
     while (list_) {                                                  \
       type_ temp = list_->mNext_;                                    \
       list_->mNext_ = nullptr;                                       \
@@ -1044,39 +1046,55 @@ nsPluginHost::TrySetUpPluginInstance(con
   return rv;
 }
 
 bool
 nsPluginHost::HavePluginForType(const nsACString & aMimeType,
                                 PluginFilter aFilter)
 {
   bool checkEnabled = aFilter & eExcludeDisabled;
-  return FindNativePluginForType(aMimeType, checkEnabled);
+  bool allowFake = !(aFilter & eExcludeFake);
+  return FindPluginForType(aMimeType, allowFake, checkEnabled);
+}
+
+nsIInternalPluginTag*
+nsPluginHost::FindPluginForType(const nsACString& aMimeType,
+                                bool aIncludeFake, bool aCheckEnabled)
+{
+  if (aIncludeFake) {
+    nsFakePluginTag* fakeTag = FindFakePluginForType(aMimeType, aCheckEnabled);
+    if (fakeTag) {
+      return fakeTag;
+    }
+  }
+
+  return FindNativePluginForType(aMimeType, aCheckEnabled);
 }
 
 NS_IMETHODIMP
 nsPluginHost::GetPluginTagForType(const nsACString& aMimeType,
                                   uint32_t aExcludeFlags,
                                   nsIPluginTag** aResult)
 {
+  bool includeFake = !(aExcludeFlags & eExcludeFake);
   bool includeDisabled = !(aExcludeFlags & eExcludeDisabled);
 
-  nsPluginTag *tag = FindNativePluginForType(aMimeType, true);
-
-  // Prefer enabled, but select disabled if none is found
-  if (includeDisabled && !tag) {
-    tag = FindNativePluginForType(aMimeType, false);
+  // First look for an enabled plugin.
+  nsRefPtr<nsIInternalPluginTag> tag = FindPluginForType(aMimeType, includeFake,
+                                                         true);
+  if (!tag && includeDisabled) {
+    tag = FindPluginForType(aMimeType, includeFake, false);
   }
 
-  if (!tag) {
-    return NS_ERROR_NOT_AVAILABLE;
+  if (tag) {
+    tag.forget(aResult);
+    return NS_OK;
   }
 
-  NS_ADDREF(*aResult = tag);
-  return NS_OK;
+  return NS_ERROR_NOT_AVAILABLE;
 }
 
 NS_IMETHODIMP
 nsPluginHost::GetStateForType(const nsACString &aMimeType,
                               uint32_t aExcludeFlags,
                               uint32_t* aResult)
 {
   nsCOMPtr<nsIPluginTag> tag;
@@ -1147,62 +1165,80 @@ nsPluginHost::GetPermissionStringForType
 }
 
 bool
 nsPluginHost::HavePluginForExtension(const nsACString & aExtension,
                                      /* out */ nsACString & aMimeType,
                                      PluginFilter aFilter)
 {
   bool checkEnabled = aFilter & eExcludeDisabled;
-  return FindNativePluginForExtension(aExtension, aMimeType, checkEnabled);
+  bool allowFake = !(aFilter & eExcludeFake);
+  return FindNativePluginForExtension(aExtension, aMimeType, checkEnabled) ||
+    (allowFake &&
+     FindFakePluginForExtension(aExtension, aMimeType, checkEnabled));
 }
 
 void
 nsPluginHost::GetPlugins(nsTArray<nsCOMPtr<nsIInternalPluginTag>>& aPluginArray,
                          bool aIncludeDisabled)
 {
   aPluginArray.Clear();
 
   LoadPlugins();
 
+  // Append fake plugins, then normal plugins.
+
+  uint32_t numFake = mFakePlugins.Length();
+  for (uint32_t i = 0; i < numFake; i++) {
+    aPluginArray.AppendElement(mFakePlugins[i]);
+  }
+
+  // Regular plugins
   nsPluginTag* plugin = mPlugins;
   while (plugin != nullptr) {
     if (plugin->IsEnabled() || aIncludeDisabled) {
       aPluginArray.AppendElement(plugin);
     }
     plugin = plugin->mNext;
   }
 }
 
+// FIXME-jsplugins Check users for order of fake v non-fake
 NS_IMETHODIMP
 nsPluginHost::GetPluginTags(uint32_t* aPluginCount, nsIPluginTag*** aResults)
 {
   LoadPlugins();
 
   uint32_t count = 0;
+  uint32_t fakeCount = mFakePlugins.Length();
   nsRefPtr<nsPluginTag> plugin = mPlugins;
   while (plugin != nullptr) {
     count++;
     plugin = plugin->mNext;
   }
 
   *aResults = static_cast<nsIPluginTag**>
-                         (moz_xmalloc(count * sizeof(**aResults)));
+                         (moz_xmalloc((fakeCount + count) * sizeof(**aResults)));
   if (!*aResults)
     return NS_ERROR_OUT_OF_MEMORY;
 
-  *aPluginCount = count;
+  *aPluginCount = count + fakeCount;
 
   plugin = mPlugins;
   for (uint32_t i = 0; i < count; i++) {
     (*aResults)[i] = plugin;
     NS_ADDREF((*aResults)[i]);
     plugin = plugin->mNext;
   }
 
+  for (uint32_t i = 0; i < fakeCount; i++) {
+    (*aResults)[i + count] = static_cast<nsIInternalPluginTag*>(mFakePlugins[i]);
+    NS_ADDREF((*aResults)[i + count]);
+  }
+
   return NS_OK;
 }
 
 nsPluginTag*
 nsPluginHost::FindPreferredPlugin(const InfallibleTArray<nsPluginTag*>& matches)
 {
   // We prefer the plugin with the highest version number.
   /// XXX(johns): This seems to assume the only time multiple plugins will have
@@ -1220,16 +1256,57 @@ nsPluginHost::FindPreferredPlugin(const 
     if (mozilla::Version(matches[i]->Version().get()) > preferredPlugin->Version().get()) {
       preferredPlugin = matches[i];
     }
   }
 
   return preferredPlugin;
 }
 
+nsFakePluginTag*
+nsPluginHost::FindFakePluginForExtension(const nsACString & aExtension,
+                                         /* out */ nsACString & aMimeType,
+                                         bool aCheckEnabled)
+{
+  if (aExtension.IsEmpty()) {
+    return nullptr;
+  }
+
+  int32_t numFakePlugins = mFakePlugins.Length();
+  for (int32_t i = 0; i < numFakePlugins; i++) {
+    nsFakePluginTag *plugin = mFakePlugins[i];
+    bool active;
+    if ((!aCheckEnabled ||
+         (NS_SUCCEEDED(plugin->GetActive(&active)) && active)) &&
+        plugin->HasExtension(aExtension, aMimeType)) {
+      return plugin;
+    }
+  }
+
+  return nullptr;
+}
+
+nsFakePluginTag*
+nsPluginHost::FindFakePluginForType(const nsACString & aMimeType,
+                                    bool aCheckEnabled)
+{
+  int32_t numFakePlugins = mFakePlugins.Length();
+  for (int32_t i = 0; i < numFakePlugins; i++) {
+    nsFakePluginTag *plugin = mFakePlugins[i];
+    bool active;
+    if ((!aCheckEnabled ||
+         (NS_SUCCEEDED(plugin->GetActive(&active)) && active)) &&
+        plugin->HasMimeType(aMimeType)) {
+      return plugin;
+    }
+  }
+
+  return nullptr;
+}
+
 nsPluginTag*
 nsPluginHost::FindNativePluginForType(const nsACString & aMimeType,
                                       bool aCheckEnabled)
 {
   if (aMimeType.IsEmpty()) {
     return nullptr;
   }
 
@@ -1527,16 +1604,60 @@ nsPluginHost::EnumerateSiteData(const ns
       break;
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsPluginHost::RegisterFakePlugin(JS::Handle<JS::Value> aInitDictionary,
+                                 JSContext* aCx,
+                                 nsIFakePluginTag **aResult)
+{
+  FakePluginTagInit initDictionary;
+  if (!initDictionary.Init(aCx, aInitDictionary)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsRefPtr<nsFakePluginTag> newTag;
+  nsresult rv = nsFakePluginTag::Create(initDictionary, getter_AddRefs(newTag));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  for (auto existingTag : mFakePlugins) {
+    if (newTag->HandlerURIMatches(existingTag->HandlerURI())) {
+      return NS_ERROR_UNEXPECTED;
+    }
+  }
+
+  mFakePlugins.AppendElement(newTag);
+  // FIXME-jsplugins do we need to register with the category manager here?  For
+  // shumway, for now, probably not.
+  newTag.forget(aResult);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPluginHost::UnregisterFakePlugin(const nsACString& aHandlerURI)
+{
+  nsCOMPtr<nsIURI> handlerURI;
+  nsresult rv = NS_NewURI(getter_AddRefs(handlerURI), aHandlerURI);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  for (uint32_t i = 0; i < mFakePlugins.Length(); ++i) {
+    if (mFakePlugins[i]->HandlerURIMatches(handlerURI)) {
+      mFakePlugins.RemoveElementAt(i);
+      return NS_OK;
+    }
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsPluginHost::RegisterPlayPreviewMimeType(const nsACString& mimeType,
                                           bool ignoreCTP,
                                           const nsACString& redirectURL,
                                           const nsACString& whitelist)
 {
   nsAutoCString mt(mimeType);
   nsAutoCString url(redirectURL);
   if (url.Length() == 0) {
@@ -1578,16 +1699,31 @@ nsPluginHost::GetPlayPreviewInfo(const n
       NS_ADDREF(*aResult);
       return NS_OK;
     }
   }
   *aResult = nullptr;
   return NS_ERROR_NOT_AVAILABLE;
 }
 
+// FIXME-jsplugins Is this method actually needed?
+NS_IMETHODIMP
+nsPluginHost::GetFakePlugin(const nsACString & aMimeType,
+                            nsIFakePluginTag** aResult)
+{
+  nsRefPtr<nsFakePluginTag> result = FindFakePluginForType(aMimeType, false);
+  if (result) {
+    result.forget(aResult);
+    return NS_OK;
+  }
+
+  *aResult = nullptr;
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
 #define ClearDataFromSitesClosure_CID {0x9fb21761, 0x2403, 0x41ad, {0x9e, 0xfd, 0x36, 0x7e, 0xc4, 0x4f, 0xa4, 0x5e}}
 
 
 // Class to hold all the data we need need for IterateMatchesAndClear and ClearDataFromSites
 class ClearDataFromSitesClosure : public nsIClearSiteDataCallback, public nsIGetSitesWithDataCallback {
 public:
   ClearDataFromSitesClosure(nsIPluginTag* plugin, const nsACString& domain, uint64_t flags,
                             int64_t maxAge, nsCOMPtr<nsIClearSiteDataCallback> callback,
@@ -1644,16 +1780,17 @@ NS_IMPL_ADDREF(ClearDataFromSitesClosure
 NS_IMPL_RELEASE(ClearDataFromSitesClosure)
 
 NS_INTERFACE_MAP_BEGIN(ClearDataFromSitesClosure)
   NS_INTERFACE_MAP_ENTRY(nsIClearSiteDataCallback)
   NS_INTERFACE_MAP_ENTRY(nsIGetSitesWithDataCallback)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIClearSiteDataCallback)
 NS_INTERFACE_MAP_END
 
+// FIXME-jsplugins what should this do for fake plugins?
 NS_IMETHODIMP
 nsPluginHost::ClearSiteData(nsIPluginTag* plugin, const nsACString& domain,
                             uint64_t flags, int64_t maxAge, nsIClearSiteDataCallback* callbackFunc)
 {
   nsCOMPtr<nsIClearSiteDataCallback> callback(callbackFunc);
   // maxAge must be either a nonnegative integer or -1.
   NS_ENSURE_ARG(maxAge >= 0 || maxAge == -1);
 
@@ -1831,36 +1968,47 @@ nsPluginHost::GetSpecialType(const nsACS
 
   return eSpecialType_None;
 }
 
 // Check whether or not a tag is a live, valid tag, and that it's loaded.
 bool
 nsPluginHost::IsLiveTag(nsIPluginTag* aPluginTag)
 {
+  nsCOMPtr<nsIInternalPluginTag> internalTag(do_QueryInterface(aPluginTag));
+  uint32_t fakeCount = mFakePlugins.Length();
+  for (uint32_t i = 0; i < fakeCount; i++) {
+    if (mFakePlugins[i] == internalTag) {
+      return true;
+    }
+  }
+
   nsPluginTag* tag;
   for (tag = mPlugins; tag; tag = tag->mNext) {
-    if (tag == aPluginTag) {
+    if (tag == internalTag) {
       return true;
     }
   }
   return false;
 }
 
+// FIXME-jsplugins what should happen with jsplugins here, if anything?
 nsPluginTag*
 nsPluginHost::HaveSamePlugin(const nsPluginTag* aPluginTag)
 {
   for (nsPluginTag* tag = mPlugins; tag; tag = tag->mNext) {
     if (tag->HasSameNameAndMimes(aPluginTag)) {
         return tag;
     }
   }
   return nullptr;
 }
 
+// Don't have to worry about fake plugins here, since this is only used during
+// the plugin directory scan, which doesn't pick up fake plugins.
 nsPluginTag*
 nsPluginHost::FirstPluginWithPath(const nsCString& path)
 {
   for (nsPluginTag* tag = mPlugins; tag; tag = tag->mNext) {
     if (tag->mFullPath.Equals(path)) {
       return tag;
     }
   }
@@ -2591,18 +2739,26 @@ nsPluginHost::FindPluginsForContent(uint
   }
 
   nsTArray<nsCOMPtr<nsIInternalPluginTag>> plugins;
   GetPlugins(plugins, true);
 
   for (size_t i = 0; i < plugins.Length(); i++) {
     nsCOMPtr<nsIInternalPluginTag> basetag = plugins[i];
 
-    /// XXX(johns) Bug FIXME-jsplugins - We need to cleanup the various plugintag classes
-    ///            to be more sane and avoid this dance
+    nsCOMPtr<nsIFakePluginTag> faketag = do_QueryInterface(basetag);
+    if (faketag) {
+      /// FIXME-jsplugins - We need to make content processes properly
+      /// aware of jsplugins (and add a nsIInternalPluginTag->AsNative() to
+      /// avoid this hacky static cast)
+      continue;
+    }
+
+    /// FIXME-jsplugins - We need to cleanup the various plugintag classes
+    /// to be more sane and avoid this dance
     nsPluginTag *tag = static_cast<nsPluginTag *>(basetag.get());
 
     if (!nsNPAPIPlugin::RunPluginOOP(tag)) {
       // Don't expose non-OOP plugins to content processes since we have no way
       // to bridge them over.
       continue;
     }
 
@@ -2616,16 +2772,17 @@ nsPluginHost::FindPluginsForContent(uint
                                       tag->mIsFlashPlugin,
                                       tag->FileName(),
                                       tag->Version(),
                                       tag->mLastModifiedTime,
                                       tag->IsFromExtension()));
   }
 }
 
+// This function is not relevant for fake plugins.
 void
 nsPluginHost::UpdatePluginInfo(nsPluginTag* aPluginTag)
 {
   MOZ_ASSERT(XRE_IsParentProcess());
 
   ReadPluginInfo();
   WritePluginInfo();
   NS_ITERATIVE_UNREF_LIST(nsRefPtr<nsPluginTag>, mCachedPlugins, mNext);
@@ -2689,16 +2846,25 @@ nsPluginHost::RegisterWithCategoryManage
   if (aType == ePluginRegister) {
     catMan->AddCategoryEntry("Gecko-Content-Viewers",
                              aMimeType.get(),
                              contractId,
                              false, /* persist: broken by bug 193031 */
                              mOverrideInternalTypes,
                              nullptr);
   } else {
+    if (aType == ePluginMaybeUnregister) {
+      // Bail out if this type is still used by an enabled plugin
+      if (HavePluginForType(aMimeType)) {
+        return;
+      }
+    } else {
+      MOZ_ASSERT(aType == ePluginUnregister, "Unknown nsRegisterType");
+    }
+
     // Only delete the entry if a plugin registered for it
     nsXPIDLCString value;
     nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers",
                                            aMimeType.get(),
                                            getter_Copies(value));
     if (NS_SUCCEEDED(rv) && strcmp(value, contractId) == 0) {
       catMan->DeleteCategoryEntry("Gecko-Content-Viewers",
                                   aMimeType.get(),
--- a/dom/plugins/base/nsPluginHost.h
+++ b/dom/plugins/base/nsPluginHost.h
@@ -7,28 +7,28 @@
 #define nsPluginHost_h_
 
 #include "nsIPluginHost.h"
 #include "nsIObserver.h"
 #include "nsCOMPtr.h"
 #include "prlink.h"
 #include "prclist.h"
 #include "nsIPluginTag.h"
+#include "nsPluginPlayPreviewInfo.h"
 #include "nsPluginsDir.h"
 #include "nsPluginDirServiceProvider.h"
 #include "nsAutoPtr.h"
 #include "nsWeakPtr.h"
 #include "nsIPrompt.h"
 #include "nsWeakReference.h"
 #include "MainThreadUtils.h"
 #include "nsTArray.h"
 #include "nsTObserverArray.h"
 #include "nsITimer.h"
 #include "nsPluginTags.h"
-#include "nsPluginPlayPreviewInfo.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIIDNService.h"
 #include "nsCRT.h"
 
 #ifdef XP_WIN
 #include <minwindef.h>
 #include "nsIWindowsRegKey.h"
 #endif
@@ -74,16 +74,17 @@ public:
 };
 
 class nsPluginHost final : public nsIPluginHost,
                            public nsIObserver,
                            public nsITimerCallback,
                            public nsSupportsWeakReference
 {
   friend class nsPluginTag;
+  friend class nsFakePluginTag;
   virtual ~nsPluginHost();
 
 public:
   nsPluginHost();
 
   static already_AddRefed<nsPluginHost> GetInst();
 
   NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
@@ -98,17 +99,18 @@ public:
 
   nsresult SetUpPluginInstance(const nsACString &aMimeType,
                                nsIURI *aURL,
                                nsPluginInstanceOwner *aOwner);
 
   // Acts like a bitfield
   enum PluginFilter {
     eExcludeNone     = nsIPluginHost::EXCLUDE_NONE,
-    eExcludeDisabled = nsIPluginHost::EXCLUDE_DISABLED
+    eExcludeDisabled = nsIPluginHost::EXCLUDE_DISABLED,
+    eExcludeFake     = nsIPluginHost::EXCLUDE_FAKE
   };
   // FIXME-jsplugins comment about fake
   bool HavePluginForType(const nsACString & aMimeType,
                          PluginFilter aFilter = eExcludeDisabled);
 
   // FIXME-jsplugins what if fake has different extensions
   bool HavePluginForExtension(const nsACString & aExtension,
                               /* out */ nsACString & aMimeType,
@@ -261,37 +263,60 @@ private:
 
   nsresult TrySetUpPluginInstance(const nsACString &aMimeType, nsIURI *aURL,
                                   nsPluginInstanceOwner *aOwner);
 
   // FIXME-jsplugins comment here about when things may be fake
   nsPluginTag*
   FindPreferredPlugin(const InfallibleTArray<nsPluginTag*>& matches);
 
-  // Return an nsPluginTag for this type, if any.  If aCheckEnabled is
-  // true, only enabled plugins will be returned.
+  // Find a plugin for the given type.  If aIncludeFake is true a fake plugin
+  // will be preferred if one exists; otherwise a fake plugin will never be
+  // returned.  If aCheckEnabled is false, disabled plugins can be returned.
+  nsIInternalPluginTag* FindPluginForType(const nsACString& aMimeType,
+                                          bool aIncludeFake, bool aCheckEnabled);
+
+  // Find specifically a fake plugin for the given type.  If aCheckEnabled is
+  // false, disabled plugins can be returned.
+  nsFakePluginTag* FindFakePluginForType(const nsACString & aMimeType,
+                                         bool aCheckEnabled);
+
+  // Find specifically a fake plugin for the given extension.  If aCheckEnabled
+  // is false, disabled plugins can be returned.  aMimeType will be filled in
+  // with the MIME type the plugin is registered for.
+  nsFakePluginTag* FindFakePluginForExtension(const nsACString & aExtension,
+                                              /* out */ nsACString & aMimeType,
+                                              bool aCheckEnabled);
+
+  // Find specifically a native (NPAPI) plugin for the given type.  If
+  // aCheckEnabled is false, disabled plugins can be returned.
   nsPluginTag* FindNativePluginForType(const nsACString & aMimeType,
                                        bool aCheckEnabled);
 
+  // Find specifically a native (NPAPI) plugin for the given extension.  If
+  // aCheckEnabled is false, disabled plugins can be returned.  aMimeType will
+  // be filled in with the MIME type the plugin is registered for.
   nsPluginTag* FindNativePluginForExtension(const nsACString & aExtension,
                                             /* out */ nsACString & aMimeType,
                                             bool aCheckEnabled);
 
   nsresult
   FindStoppedPluginForURL(nsIURI* aURL, nsIPluginInstanceOwner *aOwner);
 
   nsresult FindPluginsInContent(bool aCreatePluginList, bool * aPluginsChanged);
 
   nsresult
   FindPlugins(bool aCreatePluginList, bool * aPluginsChanged);
 
   // FIXME revisit, no ns prefix
   // Registers or unregisters the given mime type with the category manager
-  // (performs no checks - see UpdateCategoryManager)
-  enum nsRegisterType { ePluginRegister, ePluginUnregister };
+  enum nsRegisterType { ePluginRegister,
+                        ePluginUnregister,
+                        // Checks if this type should still be registered first
+                        ePluginMaybeUnregister };
   void RegisterWithCategoryManager(const nsCString& aMimeType,
                                    nsRegisterType aType);
 
   void AddPluginTag(nsPluginTag* aPluginTag);
 
   nsresult
   ScanPluginsDirectory(nsIFile *pluginsDir,
                        bool aCreatePluginList,
@@ -344,16 +369,19 @@ private:
   // On certain platforms, we only want to load certain plugins. This function
   // centralizes loading rules.
   bool ShouldAddPlugin(nsPluginTag* aPluginTag);
 
   nsRefPtr<nsPluginTag> mPlugins;
   nsRefPtr<nsPluginTag> mCachedPlugins;
   nsRefPtr<nsInvalidPluginTag> mInvalidPlugins;
   nsTArray< nsRefPtr<nsPluginPlayPreviewInfo> > mPlayPreviewMimeTypes;
+
+  nsTArray< nsRefPtr<nsFakePluginTag> > mFakePlugins;
+
   bool mPluginsLoaded;
 
   // set by pref plugin.override_internal_types
   bool mOverrideInternalTypes;
 
   // set by pref plugin.disable
   bool mPluginsDisabled;
   // set by pref plugins.click_to_play
--- a/dom/plugins/base/nsPluginTags.cpp
+++ b/dom/plugins/base/nsPluginTags.cpp
@@ -17,18 +17,20 @@
 #include "nsNPAPIPlugin.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/unused.h"
 #include "nsNetUtil.h"
 #include <cctype>
 #include "mozilla/dom/EncodingUtils.h"
 #include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/FakePluginTagInitBinding.h"
 
 using mozilla::dom::EncodingUtils;
+using mozilla::dom::FakePluginTagInit;
 using namespace mozilla;
 
 // These legacy flags are used in the plugin registry. The states are now
 // stored in prefs, but we still need to be able to import them.
 #define NS_PLUGIN_FLAG_ENABLED      0x0001    // is this plugin enabled?
 // no longer used                   0x0002    // reuse only if regenerating pluginreg.dat
 #define NS_PLUGIN_FLAG_FROMCACHE    0x0004    // this plugintag info was loaded from cache
 // no longer used                   0x0008    // reuse only if regenerating pluginreg.dat
@@ -781,16 +783,53 @@ NS_INTERFACE_TABLE_HEAD(nsFakePluginTag)
                                        nsIInternalPluginTag)
     NS_INTERFACE_TABLE_ENTRY(nsFakePluginTag, nsIInternalPluginTag)
     NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsFakePluginTag, nsISupports,
                                        nsIInternalPluginTag)
     NS_INTERFACE_TABLE_ENTRY(nsFakePluginTag, nsIFakePluginTag)
   NS_INTERFACE_TABLE_END
 NS_INTERFACE_TABLE_TAIL
 
+/* static */
+nsresult
+nsFakePluginTag::Create(const FakePluginTagInit& aInitDictionary,
+                        nsFakePluginTag** aPluginTag)
+{
+  NS_ENSURE_TRUE(!aInitDictionary.mMimeEntries.IsEmpty(), NS_ERROR_INVALID_ARG);
+
+  nsRefPtr<nsFakePluginTag> tag = new nsFakePluginTag();
+  nsresult rv = NS_NewURI(getter_AddRefs(tag->mHandlerURI),
+                          aInitDictionary.mHandlerURI);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  CopyUTF16toUTF8(aInitDictionary.mNiceName, tag->mNiceName);
+  CopyUTF16toUTF8(aInitDictionary.mFullPath, tag->mFullPath);
+  CopyUTF16toUTF8(aInitDictionary.mName, tag->mName);
+  CopyUTF16toUTF8(aInitDictionary.mDescription, tag->mDescription);
+  CopyUTF16toUTF8(aInitDictionary.mFileName, tag->mFileName);
+  CopyUTF16toUTF8(aInitDictionary.mVersion, tag->mVersion);
+
+  for (const FakePluginMimeEntry& mimeEntry : aInitDictionary.mMimeEntries) {
+    CopyUTF16toUTF8(mimeEntry.mType, *tag->mMimeTypes.AppendElement());
+    CopyUTF16toUTF8(mimeEntry.mDescription,
+                    *tag->mMimeDescriptions.AppendElement());
+    CopyUTF16toUTF8(mimeEntry.mExtension, *tag->mExtensions.AppendElement());
+  }
+
+  tag.forget(aPluginTag);
+  return NS_OK;
+}
+
+bool
+nsFakePluginTag::HandlerURIMatches(nsIURI* aURI)
+{
+  bool equals = false;
+  return NS_SUCCEEDED(mHandlerURI->Equals(aURI, &equals)) && equals;
+}
+
 NS_IMETHODIMP
 nsFakePluginTag::GetHandlerURI(nsIURI **aResult)
 {
   NS_IF_ADDREF(*aResult = mHandlerURI);
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/dom/plugins/base/nsPluginTags.h
+++ b/dom/plugins/base/nsPluginTags.h
@@ -15,16 +15,22 @@
 #include "nsITimer.h"
 #include "nsString.h"
 
 class nsIURI;
 struct PRLibrary;
 struct nsPluginInfo;
 class nsNPAPIPlugin;
 
+namespace mozilla {
+namespace dom {
+struct FakePluginTagInit;
+} // namespace dom
+} // namespace mozilla
+
 // An interface representing plugin tags internally.
 #define NS_IINTERNALPLUGINTAG_IID \
 { 0xe8fdd227, 0x27da, 0x46ee,     \
   { 0xbe, 0xf3, 0x1a, 0xef, 0x5a, 0x8f, 0xc5, 0xb4 } }
 
 class nsIInternalPluginTag : public nsIPluginTag
 {
 public:
@@ -189,22 +195,28 @@ private:
 class nsFakePluginTag : public nsIInternalPluginTag,
                         public nsIFakePluginTag
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPLUGINTAG
   NS_DECL_NSIFAKEPLUGINTAG
 
-  nsFakePluginTag();
+  static nsresult Create(const mozilla::dom::FakePluginTagInit& aInitDictionary,
+                         nsFakePluginTag** aPluginTag);
 
   bool IsEnabled() override;
   const nsCString& GetNiceFileName() override;
 
+  bool HandlerURIMatches(nsIURI* aURI);
+
+  nsIURI* HandlerURI() const { return mHandlerURI; }
+
 private:
+  nsFakePluginTag();
   virtual ~nsFakePluginTag();
 
   // The URI of the handler for our fake plugin.
   // FIXME-jsplugins do we need to sanity check these?
   nsCOMPtr<nsIURI>    mHandlerURI;
 
   nsCString     mFullPath;
   nsCString     mNiceName;
new file mode 100644
--- /dev/null
+++ b/dom/webidl/FakePluginTagInit.webidl
@@ -0,0 +1,39 @@
+/* -*- Mode: IDL; 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/.
+ */
+
+/**
+ * A fake plugin is fundamentally identified by its handlerURI.
+ *
+ * In addition to that, a fake plugin registration needs to provide at least one
+ * FakePluginMimeEntry so we'll know what types(s) the plugin is registered for.
+ * Other information is optional, though having usable niceName is highly
+ * recommended.
+ */
+dictionary FakePluginTagInit {
+  required DOMString handlerURI;
+  required sequence<FakePluginMimeEntry> mimeEntries;
+
+  // The niceName should really be provided, and be unique, if possible; it can
+  // be used as a key to persist state for this plug-in.
+  DOMString niceName = "";
+
+  // Other things can be provided but don't really matter that much.
+  DOMString fullPath = "";
+  DOMString name = "";
+  DOMString description = "";
+  DOMString fileName = "";
+  DOMString version = "";
+};
+
+/**
+ * A single MIME entry for the fake plugin.
+ */
+dictionary FakePluginMimeEntry {
+  required DOMString type;
+  DOMString description = "";
+  DOMString extension = "";
+};
+
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -141,16 +141,17 @@ WEBIDL_FILES = [
     'Element.webidl',
     'EngineeringMode.webidl',
     'Event.webidl',
     'EventHandler.webidl',
     'EventListener.webidl',
     'EventSource.webidl',
     'EventTarget.webidl',
     'ExtendableEvent.webidl',
+    'FakePluginTagInit.webidl',
     'Fetch.webidl',
     'FetchEvent.webidl',
     'File.webidl',
     'FileList.webidl',
     'FileMode.webidl',
     'FileReader.webidl',
     'FileReaderSync.webidl',
     'FocusEvent.webidl',