Bug 1032814 - Gecko media plugins should not automatically enable plugin from system locations. Instead, there is a programmatic API for registering available plugins which will be used by addons or the addon manager. For development, there is also an environment variable. r=josh/jesup
authorBenjamin Smedberg <benjamin@smedbergs.us>
Thu, 10 Jul 2014 14:48:11 -0400
changeset 193417 3141212527eb48ac1de815545eccdb9b5b98b68c
parent 193416 6684d05944b6558a083e4eca4dc6afdb8980a4f0
child 193418 5d91329df900fa0092387b14d2d3b01aaa529fa3
push id27117
push userryanvm@gmail.com
push dateThu, 10 Jul 2014 22:23:14 +0000
treeherdermozilla-central@e1a037c085d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjosh, jesup
bugs1032814
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1032814 - Gecko media plugins should not automatically enable plugin from system locations. Instead, there is a programmatic API for registering available plugins which will be used by addons or the addon manager. For development, there is also an environment variable. r=josh/jesup
browser/installer/package-manifest.in
content/media/gmp/GMPParent.h
content/media/gmp/GMPService.cpp
content/media/gmp/GMPService.h
content/media/gmp/mozIGeckoMediaPluginService.idl
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -162,16 +162,17 @@
 @BINPATH@/components/chrome.xpt
 @BINPATH@/components/commandhandler.xpt
 @BINPATH@/components/commandlines.xpt
 @BINPATH@/components/composer.xpt
 @BINPATH@/components/content_base.xpt
 @BINPATH@/components/content_events.xpt
 @BINPATH@/components/content_htmldoc.xpt
 @BINPATH@/components/content_html.xpt
+@BINPATH@/components/content_geckomediaplugins.xpt
 #ifdef MOZ_WEBRTC
 @BINPATH@/components/content_webrtc.xpt
 #endif
 @BINPATH@/components/content_xslt.xpt
 @BINPATH@/components/cookie.xpt
 @BINPATH@/components/directory.xpt
 @BINPATH@/components/docshell.xpt
 @BINPATH@/components/dom.xpt
--- a/content/media/gmp/GMPParent.h
+++ b/content/media/gmp/GMPParent.h
@@ -10,20 +10,20 @@
 #include "GMPVideoDecoderParent.h"
 #include "GMPVideoEncoderParent.h"
 #include "mozilla/gmp/PGMPParent.h"
 #include "nsCOMPtr.h"
 #include "nscore.h"
 #include "nsISupports.h"
 #include "nsString.h"
 #include "nsTArray.h"
+#include "nsIFile.h"
 
 class nsILineInputStream;
 class nsIThread;
-class nsIFile;
 
 namespace mozilla {
 namespace gmp {
 
 class GMPCapability
 {
 public:
   nsCString mAPIName;
@@ -72,16 +72,20 @@ public:
   // Returns true if a plugin can be or is being used across multiple origins.
   bool CanBeSharedCrossOrigin() const;
 
   // A GMP can be used from an origin if it's already been set to work with
   // that origin, or if it's not been set to work with any origin and has
   // not yet been loaded (i.e. it's not shared across origins).
   bool CanBeUsedFrom(const nsAString& aOrigin) const;
 
+  already_AddRefed<nsIFile> GetDirectory() {
+    return nsCOMPtr<nsIFile>(mDirectory).forget();
+  }
+
 private:
   ~GMPParent();
   bool EnsureProcessLoaded();
   nsresult ReadGMPMetaData();
   virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
   virtual PGMPVideoDecoderParent* AllocPGMPVideoDecoderParent() MOZ_OVERRIDE;
   virtual bool DeallocPGMPVideoDecoderParent(PGMPVideoDecoderParent* aActor) MOZ_OVERRIDE;
   virtual PGMPVideoEncoderParent* AllocPGMPVideoEncoderParent() MOZ_OVERRIDE;
--- a/content/media/gmp/GMPService.cpp
+++ b/content/media/gmp/GMPService.cpp
@@ -3,26 +3,22 @@
  * 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 "GMPService.h"
 #include "GMPParent.h"
 #include "GMPVideoDecoderParent.h"
 #include "nsIObserverService.h"
 #include "GeckoChildProcessHost.h"
-#if defined(XP_WIN)
-#include "nsIWindowsRegKey.h"
-#endif
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/SyncRunnable.h"
-#include "nsDirectoryServiceUtils.h"
-#include "nsDirectoryServiceDefs.h"
 #include "nsXPCOMPrivate.h"
-#include "nsISimpleEnumerator.h"
 #include "mozilla/Services.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIConsoleService.h"
 
 namespace mozilla {
 namespace gmp {
 
 static StaticRefPtr<GeckoMediaPluginService> sSingletonService;
 
 class GMPServiceCreateHelper MOZ_FINAL : public nsRunnable
 {
@@ -108,26 +104,16 @@ GeckoMediaPluginService::~GeckoMediaPlug
   MOZ_ASSERT(mPlugins.IsEmpty());
 }
 
 void
 GeckoMediaPluginService::Init()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  // Cache user directory while we're on the main thread. We do this because
-  // if we try to use "~" in a path during plugin lookup on a non-main thread,
-  // the nsIFile code will try to resolve it using NS_GetSpecialDirectory, which
-  // doesn't work on non-main threads.
-  nsCOMPtr<nsIFile> homeDir;
-  NS_GetSpecialDirectory(NS_OS_HOME_DIR, getter_AddRefs(homeDir));
-  if (homeDir) {
-    homeDir->GetPath(mHomePath);
-  }
-
   nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
   MOZ_ASSERT(obsService);
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false)));
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false)));
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginService::Observe(nsISupports* aSubject,
@@ -177,16 +163,19 @@ GeckoMediaPluginService::GetThread(nsITh
     if (mShuttingDown) {
       return NS_ERROR_FAILURE;
     }
 
     nsresult rv = NS_NewNamedThread("GMPThread", getter_AddRefs(mGMPThread));
     if (NS_FAILED(rv)) {
       return rv;
     }
+
+    // Tell the thread to initialize plugins
+    mGMPThread->Dispatch(NS_NewRunnableMethod(this, &GeckoMediaPluginService::LoadFromEnvironment), NS_DISPATCH_NORMAL);
   }
 
   NS_ADDREF(mGMPThread);
   *aThread = mGMPThread;
 
   return NS_OK;
 }
 
@@ -267,40 +256,92 @@ GeckoMediaPluginService::UnloadPlugins()
   mShuttingDownOnGMPThread = true;
 
   for (uint32_t i = 0; i < mPlugins.Length(); i++) {
     mPlugins[i]->UnloadProcess();
   }
   mPlugins.Clear();
 }
 
+void
+GeckoMediaPluginService::LoadFromEnvironment()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+  const char* env = PR_GetEnv("MOZ_GMP_PATH");
+  if (!env || !*env) {
+    return;
+  }
+
+  nsString allpaths;
+  if (NS_WARN_IF(NS_FAILED(NS_CopyNativeToUnicode(nsDependentCString(env), allpaths)))) {
+    return;
+  }
+
+  uint32_t pos = 0;
+  while (pos < allpaths.Length()) {
+    // Loop over multiple path entries separated by colons (*nix) or
+    // semicolons (Windows)
+    int32_t next = allpaths.FindChar(XPCOM_ENV_PATH_SEPARATOR[0], pos);
+    if (next == -1) {
+      AddOnGMPThread(nsDependentSubstring(allpaths, pos));
+      break;
+    } else {
+      AddOnGMPThread(nsDependentSubstring(allpaths, pos, next - pos));
+      pos = next + 1;
+    }
+  }
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginService::PathRunnable::Run()
+{
+  if (mAdd) {
+    mService->AddOnGMPThread(mPath);
+  } else {
+    mService->RemoveOnGMPThread(mPath);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginService::AddPluginDirectory(const nsAString& aDirectory)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsCOMPtr<nsIThread> thread;
+  nsresult rv = GetThread(getter_AddRefs(thread));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  nsCOMPtr<nsIRunnable> r = new PathRunnable(this, aDirectory, true);
+  thread->Dispatch(r, NS_DISPATCH_NORMAL);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginService::RemovePluginDirectory(const nsAString& aDirectory)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsCOMPtr<nsIThread> thread;
+  nsresult rv = GetThread(getter_AddRefs(thread));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  nsCOMPtr<nsIRunnable> r = new PathRunnable(this, aDirectory, false);
+  thread->Dispatch(r, NS_DISPATCH_NORMAL);
+  return NS_OK;
+}
+
 GMPParent*
 GeckoMediaPluginService::SelectPluginForAPI(const nsAString& aOrigin,
                                             const nsCString& aAPI,
                                             const nsTArray<nsCString>& aTags)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
 
-  GMPParent* gmp = SelectPluginFromListForAPI(aOrigin, aAPI, aTags);
-  if (gmp) {
-    return gmp;
-  }
-
-  RefreshPluginList();
-
-  return SelectPluginFromListForAPI(aOrigin, aAPI, aTags);
-}
-
-GMPParent*
-GeckoMediaPluginService::SelectPluginFromListForAPI(const nsAString& aOrigin,
-                                                    const nsCString& aAPI,
-                                                    const nsTArray<nsCString>& aTags)
-{
-  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
-
   for (uint32_t i = 0; i < mPlugins.Length(); i++) {
     GMPParent* gmp = mPlugins[i];
     bool supportsAllTags = true;
     for (uint32_t t = 0; t < aTags.Length(); t++) {
       const nsCString& tag = aTags[t];
       if (!gmp->SupportsAPI(aAPI, tag)) {
         supportsAllTags = false;
         break;
@@ -318,202 +359,56 @@ GeckoMediaPluginService::SelectPluginFro
         gmp->SetOrigin(aOrigin);
       }
       return gmp;
     }
   }
   return nullptr;
 }
 
-nsresult
-GeckoMediaPluginService::GetDirectoriesToSearch(nsTArray<nsCOMPtr<nsIFile>> &aDirs)
+
+void
+GeckoMediaPluginService::AddOnGMPThread(const nsAString& aDirectory)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
 
-#if defined(XP_MACOSX)
-  nsCOMPtr<nsIFile> searchDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
-  MOZ_ASSERT(!mHomePath.IsEmpty());
-  nsresult rv = searchDir->InitWithPath(mHomePath + NS_LITERAL_STRING("/Library/Internet Plug-Ins/"));
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  aDirs.AppendElement(searchDir);
-  searchDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
-  rv = searchDir->InitWithPath(NS_LITERAL_STRING("/Library/Internet Plug-Ins/"));
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  aDirs.AppendElement(searchDir);
-#elif defined(OS_POSIX)
-  nsCOMPtr<nsIFile> searchDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
-  nsresult rv = searchDir->InitWithPath(NS_LITERAL_STRING("/usr/lib/mozilla/plugins/"));
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  aDirs.AppendElement(searchDir);
-#endif
-  return NS_OK;
-}
-
-#if defined(XP_WIN)
-static nsresult
-GetPossiblePluginsForRegRoot(uint32_t aKey, nsTArray<nsCOMPtr<nsIFile>>& aDirs)
-{
-  nsCOMPtr<nsIWindowsRegKey> regKey = do_CreateInstance("@mozilla.org/windows-registry-key;1");
-  if (!regKey) {
-    return NS_ERROR_FAILURE;
-  }
-
-  nsresult rv = regKey->Open(aKey,
-                             NS_LITERAL_STRING("Software\\MozillaPlugins"),
-                             nsIWindowsRegKey::ACCESS_READ);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  uint32_t childCount = 0;
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(regKey->GetChildCount(&childCount)));
-  for (uint32_t index = 0; index < childCount; index++) {
-    nsAutoString childName;
-    rv = regKey->GetChildName(index, childName);
-    if (NS_FAILED(rv)) {
-      continue;
-    }
-
-    nsCOMPtr<nsIWindowsRegKey> childKey;
-    rv = regKey->OpenChild(childName, nsIWindowsRegKey::ACCESS_QUERY_VALUE,
-                           getter_AddRefs(childKey));
-    if (NS_FAILED(rv) || !childKey) {
-      continue;
-    }
-
-    nsAutoString path;
-    rv = childKey->ReadStringValue(NS_LITERAL_STRING("Path"), path);
-    if (NS_FAILED(rv)) {
-      continue;
-    }
-
-    nsCOMPtr<nsIFile> localFile;
-    if (NS_SUCCEEDED(NS_NewLocalFile(path, true, getter_AddRefs(localFile))) &&
-        localFile) {
-      bool isFileThere = false;
-      if (NS_SUCCEEDED(localFile->Exists(&isFileThere)) && isFileThere) {
-        aDirs.AppendElement(localFile);
-      }
-    }
-  }
-
-  regKey->Close();
-
-  return NS_OK;
-}
-#endif
-
-nsresult
-GeckoMediaPluginService::GetPossiblePlugins(nsTArray<nsCOMPtr<nsIFile>>& aDirs)
-{
-  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
-
-#if defined(XP_WIN)
-  // The ROOT_KEY_CURRENT_USER entry typically fails to open, causing this call to
-  // fail. Don't check any return values because if we find nothing we don't care.
-  GetPossiblePluginsForRegRoot(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, aDirs);
-  GetPossiblePluginsForRegRoot(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, aDirs);
-#endif
-  return NS_OK;
-}
-
-nsresult
-GeckoMediaPluginService::SearchDirectory(nsIFile* aSearchDir)
-{
-  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
-  MOZ_ASSERT(aSearchDir);
-
-  nsCOMPtr<nsISimpleEnumerator> iter;
-  nsresult rv = aSearchDir->GetDirectoryEntries(getter_AddRefs(iter));
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  bool hasMore;
-  while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
-    nsCOMPtr<nsISupports> supports;
-    rv = iter->GetNext(getter_AddRefs(supports));
-    if (NS_FAILED(rv)) {
-      continue;
-    }
-    nsCOMPtr<nsIFile> dirEntry(do_QueryInterface(supports, &rv));
-    if (NS_FAILED(rv)) {
-      continue;
-    }
-    ProcessPossiblePlugin(dirEntry);
-  }
-
-  return NS_OK;
-}
-
-void
-GeckoMediaPluginService::ProcessPossiblePlugin(nsIFile* aDir)
-{
-  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
-  MOZ_ASSERT(aDir);
-
-  bool isDirectory = false;
-  nsresult rv = aDir->IsDirectory(&isDirectory);
-  if (NS_FAILED(rv) || !isDirectory) {
-    return;
-  }
-
-  nsAutoString leafName;
-  rv = aDir->GetLeafName(leafName);
-  if (NS_FAILED(rv)) {
-    return;
-  }
-
-  NS_NAMED_LITERAL_STRING(prefix, "gmp-");
-  if (leafName.Length() <= prefix.Length() ||
-      !Substring(leafName, 0, prefix.Length()).Equals(prefix)) {
+  nsCOMPtr<nsIFile> directory;
+  nsresult rv = NS_NewLocalFile(aDirectory, false, getter_AddRefs(directory));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
   nsRefPtr<GMPParent> gmp = new GMPParent();
-  rv = gmp->Init(aDir);
+  rv = gmp->Init(directory);
   if (NS_FAILED(rv)) {
     return;
   }
 
   mPlugins.AppendElement(gmp);
 }
 
 void
-GeckoMediaPluginService::RefreshPluginList()
+GeckoMediaPluginService::RemoveOnGMPThread(const nsAString& aDirectory)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
 
-  for (uint32_t iPlusOne = mPlugins.Length(); iPlusOne > 0; iPlusOne--) {
-    if (mPlugins[iPlusOne - 1]->State() == GMPStateNotLoaded) {
-      mPlugins.RemoveElementAt(iPlusOne - 1);
-    }
-  }
-
-  nsTArray<nsCOMPtr<nsIFile>> searchDirs;
-  nsresult rv = GetDirectoriesToSearch(searchDirs);
-  if (NS_FAILED(rv)) {
+  nsCOMPtr<nsIFile> directory;
+  nsresult rv = NS_NewLocalFile(aDirectory, false, getter_AddRefs(directory));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
-  for (uint32_t i = 0; i < searchDirs.Length(); i++) {
-    SearchDirectory(searchDirs[i]);
+  for (uint32_t i = 0; i < mPlugins.Length(); ++i) {
+    nsCOMPtr<nsIFile> pluginpath = mPlugins[i]->GetDirectory();
+    bool equals;
+    if (NS_SUCCEEDED(directory->Equals(pluginpath, &equals)) && equals) {
+      mPlugins[i]->UnloadProcess();
+      mPlugins.RemoveElementAt(i);
+      return;
+    }
   }
-
-  nsTArray<nsCOMPtr<nsIFile>> possiblePlugins;
-  rv = GetPossiblePlugins(possiblePlugins);
-  if (NS_FAILED(rv)) {
-    return;
-  }
-
-  for (uint32_t i = 0; i < possiblePlugins.Length(); i++) {
-    ProcessPossiblePlugin(possiblePlugins[i]);
-  }
+  NS_WARNING("Removing GMP which was never added.");
+  nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+  cs->LogStringMessage(MOZ_UTF16("Removing GMP which was never added."));
 }
 
 } // namespace gmp
 } // namespace mozilla
--- a/content/media/gmp/GMPService.h
+++ b/content/media/gmp/GMPService.h
@@ -8,18 +8,18 @@
 
 #include "mozIGeckoMediaPluginService.h"
 #include "nsIObserver.h"
 #include "nsTArray.h"
 #include "mozilla/Mutex.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsIThread.h"
+#include "nsThreadUtils.h"
 
-class nsIFile;
 template <class> struct already_AddRefed;
 
 namespace mozilla {
 namespace gmp {
 
 class GMPParent;
 
 class GeckoMediaPluginService MOZ_FINAL : public mozIGeckoMediaPluginService
@@ -33,34 +33,49 @@ public:
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_MOZIGECKOMEDIAPLUGINSERVICE
   NS_DECL_NSIOBSERVER
 
 private:
   ~GeckoMediaPluginService();
 
-  GMPParent* SelectPluginFromListForAPI(const nsAString& aOrigin,
-                                        const nsCString& aAPI,
-                                        const nsTArray<nsCString>& aTags);
   GMPParent* SelectPluginForAPI(const nsAString& aOrigin,
                                 const nsCString& aAPI,
                                 const nsTArray<nsCString>& aTags);
+
   void UnloadPlugins();
 
-  void RefreshPluginList();
+  void LoadFromEnvironment();
   void ProcessPossiblePlugin(nsIFile* aDir);
-  nsresult SearchDirectory(nsIFile* aSearchDir);
-  nsresult GetPossiblePlugins(nsTArray<nsCOMPtr<nsIFile>>& aDirs);
-  nsresult GetDirectoriesToSearch(nsTArray<nsCOMPtr<nsIFile>>& aDirs);
+
+  void AddOnGMPThread(const nsAString& aSearchDir);
+  void RemoveOnGMPThread(const nsAString& aSearchDir);
+
+  class PathRunnable : public nsRunnable
+  {
+  public:
+    PathRunnable(GeckoMediaPluginService* service, const nsAString& path,
+                 bool add)
+      : mService(service)
+      , mPath(path)
+      , mAdd(add)
+    { }
+
+    NS_DECL_NSIRUNNABLE
+
+  private:
+    nsRefPtr<GeckoMediaPluginService> mService;
+    nsString mPath;
+    bool mAdd;
+  };
 
   nsTArray<nsRefPtr<GMPParent>> mPlugins;
   Mutex mMutex; // Protects mGMPThread and mShuttingDown
   nsCOMPtr<nsIThread> mGMPThread;
   bool mShuttingDown;
   bool mShuttingDownOnGMPThread;
-  nsString mHomePath;
 };
 
 } // namespace gmp
 } // namespace mozilla
 
 #endif // GMPService_h_
--- a/content/media/gmp/mozIGeckoMediaPluginService.idl
+++ b/content/media/gmp/mozIGeckoMediaPluginService.idl
@@ -16,31 +16,50 @@ class GMPVideoHost;
 %}
 
 [ptr] native GMPVideoDecoder(GMPVideoDecoder);
 [ptr] native GMPVideoEncoder(GMPVideoEncoder);
 [ptr] native GMPVideoHost(GMPVideoHost);
 [ptr] native MessageLoop(MessageLoop);
 [ptr] native TagArray(nsTArray<nsCString>);
 
-[uuid(BF5A9086-70F5-4D38-832D-1609BBF963CD)]
+[scriptable, uuid(63fc797f-9d01-43f4-8b93-5b1fe713c2f8)]
 interface mozIGeckoMediaPluginService : nsISupports
 {
-  // Returns the GMP thread.
-  // Callable from any thread.
+  /**
+   * The GMP thread. Callable from any thread.
+   */
   readonly attribute nsIThread thread;
 
-  // Returns a video decoder that supports the specified tags.
-  // The array of tags should at least contain a codec tag, and optionally
-  // other tags such as for EME keysystem.
-  // Callable only on GMP thread.
+  /**
+   * Get a video decoder that supports the specified tags.
+   * The array of tags should at least contain a codec tag, and optionally
+   * other tags such as for EME keysystem.
+   * Callable only on GMP thread.
+   */
+  [noscript]
   GMPVideoDecoder getGMPVideoDecoder(in TagArray tags,
                                      [optional] in AString origin,
                                      out GMPVideoHost outVideoHost);
 
-  // Returns a video encoder that supports the specified tags.
-  // The array of tags should at least contain a codec tag, and optionally
-  // other tags.
-  // Callable only on GMP thread.
+  /**
+   * Get a video encoder that supports the specified tags.
+   * The array of tags should at least contain a codec tag, and optionally
+   * other tags.
+   * Callable only on GMP thread.
+   */
+  [noscript]
   GMPVideoEncoder getGMPVideoEncoder(in TagArray tags,
                                      [optional] in AString origin,
                                      out GMPVideoHost outVideoHost);
+
+  /**
+   * Add a directory to scan for gecko media plugins.
+   * @note Main-thread API.
+   */
+  void addPluginDirectory(in AString directory);
+
+  /**
+   * Remove a directory for gecko media plugins.
+   * @note Main-thread API.
+   */
+  void removePluginDirectory(in AString directory);
 };