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
☠☠ backed out by bf3916efc0bf ☠ ☠
authorBenjamin Smedberg <benjamin@smedbergs.us>
Thu, 10 Jul 2014 10:32:59 -0400
changeset 193400 91f25a63dea90712cd525597cc64b257c4a83b4f
parent 193399 1a4c6cb3174310e15f0e42bf343c9caa5ac2ff6d
child 193401 31ac9fc7c6684d84e52f28288cb9352eb6af4995
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
@@ -9,17 +9,16 @@
 #include "mozIGeckoMediaPluginService.h"
 #include "nsIObserver.h"
 #include "nsTArray.h"
 #include "mozilla/Mutex.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsIThread.h"
 
-class nsIFile;
 template <class> struct already_AddRefed;
 
 namespace mozilla {
 namespace gmp {
 
 class GMPParent;
 
 class GeckoMediaPluginService MOZ_FINAL : public mozIGeckoMediaPluginService
@@ -33,34 +32,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);
 };