Bug 1312540 - Maintain a cache of GMPs capabilities in content processes. r=gerald,r=billm draft
authorChris Pearce <cpearce@mozilla.com>
Tue, 01 Nov 2016 16:25:19 +1300
changeset 432362 713c276e73223e94ee11c82dcd6876487f1f33ef
parent 432230 3e73fd638e687a4d7f46613586e5156b8e2af846
child 432363 e59fe8503a54d0e99184c0f76c5df8243b6b7693
push id34282
push usercpearce@mozilla.com
push dateTue, 01 Nov 2016 20:25:28 +0000
reviewersgerald, billm
bugs1312540
milestone52.0a1
Bug 1312540 - Maintain a cache of GMPs capabilities in content processes. r=gerald,r=billm In order to avoid doing a synchronous call from content process to chrome process in order to determine what GMPs are usable, maintain a cache of GMP capabilities in the content processes. We must seed the cache when content processes are created, as the GMP service is started up and GMPs are added to it before the first (or any subsequent) content process is created. MozReview-Commit-ID: Eb4Pu81XHmn
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/PContent.ipdl
dom/media/gmp/GMPParent.h
dom/media/gmp/GMPServiceChild.cpp
dom/media/gmp/GMPServiceChild.h
dom/media/gmp/GMPServiceParent.cpp
dom/media/gmp/GMPServiceParent.h
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -56,16 +56,17 @@
 #include "mozilla/plugins/PluginInstanceParent.h"
 #include "mozilla/plugins/PluginModuleParent.h"
 #include "mozilla/widget/WidgetMessageUtils.h"
 #include "nsBaseDragService.h"
 #include "mozilla/media/MediaChild.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/WebBrowserPersistDocumentChild.h"
 #include "imgLoader.h"
+#include "GMPServiceChild.h"
 
 #if defined(MOZ_CONTENT_SANDBOX)
 #if defined(XP_WIN)
 #define TARGET_SANDBOX_EXPORTS
 #include "mozilla/sandboxTarget.h"
 #elif defined(XP_LINUX)
 #include "mozilla/Sandbox.h"
 #include "mozilla/SandboxInfo.h"
@@ -1173,16 +1174,23 @@ ContentChild::AllocPContentBridgeParent(
 PGMPServiceChild*
 ContentChild::AllocPGMPServiceChild(mozilla::ipc::Transport* aTransport,
                                     base::ProcessId aOtherProcess)
 {
   return GMPServiceChild::Create(aTransport, aOtherProcess);
 }
 
 bool
+ContentChild::RecvGMPsChanged(nsTArray<GMPCapabilityData>&& capabilities)
+{
+  GeckoMediaPluginServiceChild::UpdateGMPCapabilities(Move(capabilities));
+  return true;
+}
+
+bool
 ContentChild::RecvInitRendering(Endpoint<PCompositorBridgeChild>&& aCompositor,
                                 Endpoint<PImageBridgeChild>&& aImageBridge,
                                 Endpoint<PVRManagerChild>&& aVRBridge)
 {
   if (!CompositorBridgeChild::InitForContent(Move(aCompositor))) {
     return false;
   }
   if (!ImageBridgeChild::InitForContent(Move(aImageBridge))) {
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -158,16 +158,19 @@ public:
   AllocPContentBridgeChild(mozilla::ipc::Transport* transport,
                            base::ProcessId otherProcess) override;
 
   PGMPServiceChild*
   AllocPGMPServiceChild(mozilla::ipc::Transport* transport,
                         base::ProcessId otherProcess) override;
 
   bool
+  RecvGMPsChanged(nsTArray<GMPCapabilityData>&& capabilities) override;
+
+  bool
   RecvInitRendering(
     Endpoint<PCompositorBridgeChild>&& aCompositor,
     Endpoint<PImageBridgeChild>&& aImageBridge,
     Endpoint<PVRManagerChild>&& aVRBridge) override;
 
   bool
   RecvReinitRendering(
     Endpoint<PCompositorBridgeChild>&& aCompositor,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1410,16 +1410,19 @@ ContentParent::Init()
     nsCOMPtr<nsISupports> gatherer;
     rv = profiler->GetProfileGatherer(getter_AddRefs(gatherer));
     MOZ_ASSERT(NS_SUCCEEDED(rv));
     mGatherer = static_cast<ProfileGatherer*>(gatherer.get());
 
     StartProfiler(currentProfilerParams);
   }
 #endif
+
+  RefPtr<GeckoMediaPluginServiceParent> gmps(GeckoMediaPluginServiceParent::GetSingleton());
+  gmps->UpdateContentProcessGMPCapabilities();
 }
 
 void
 ContentParent::ForwardKnownInfo()
 {
   MOZ_ASSERT(mMetamorphosed);
   if (!mMetamorphosed) {
     return;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -332,16 +332,29 @@ union GetFilesResponseResult
 
 struct BlobURLRegistrationData
 {
     nsCString url;
     PBlob blob;
     Principal principal;
 };
 
+struct GMPAPITags
+{
+    nsCString api;
+    nsCString[] tags;
+};
+
+struct GMPCapabilityData
+{
+    nsCString name;
+    nsCString version;
+    GMPAPITags[] capabilities;
+};
+
 nested(upto inside_cpow) sync protocol PContent
 {
     parent spawns PPluginModule;
 
     parent opens PProcessHangMonitor;
     parent opens PGMPService;
     child opens PBackground;
 
@@ -659,16 +672,19 @@ child:
 
     async GetFilesResponse(nsID aID, GetFilesResponseResult aResult);
 
     async BlobURLRegistration(nsCString aURI, PBlob aBlob,
                               Principal aPrincipal);
 
     async BlobURLUnregistration(nsCString aURI);
 
+
+    async GMPsChanged(GMPCapabilityData[] capabilities);
+
 parent:
     /**
      * Tell the content process some attributes of itself.  This is
      * among the first information queried by content processes after
      * startup.  (The message is sync to allow the content process to
      * control when it receives the information.)
      *
      * |id| is a unique ID among all subprocesses.  When |isForApp &&
--- a/dom/media/gmp/GMPParent.h
+++ b/dom/media/gmp/GMPParent.h
@@ -155,16 +155,18 @@ public:
 
   bool GetGMPContentParent(UniquePtr<GetGMPContentParentCallback>&& aCallback);
   already_AddRefed<GMPContentParent> ForgetGMPContentParent();
 
   bool EnsureProcessLoaded(base::ProcessId* aID);
 
   bool Bridge(GMPServiceParent* aGMPServiceParent);
 
+  const nsTArray<GMPCapability>& GetCapabilities() const { return mCapabilities; }
+
 private:
   ~GMPParent();
 
   RefPtr<GeckoMediaPluginServiceParent> mService;
   bool EnsureProcessLoaded();
   RefPtr<GenericPromise> ReadGMPMetaData();
   RefPtr<GenericPromise> ReadGMPInfoFile(nsIFile* aFile);
   RefPtr<GenericPromise> ParseChromiumManifest(nsString aJSON); // Main thread.
--- a/dom/media/gmp/GMPServiceChild.cpp
+++ b/dom/media/gmp/GMPServiceChild.cpp
@@ -1,15 +1,17 @@
 /* -*- 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 "GMPServiceChild.h"
 #include "mozilla/dom/ContentChild.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
 #include "mozIGeckoMediaPluginService.h"
 #include "mozIGeckoMediaPluginChromeService.h"
 #include "nsCOMPtr.h"
 #include "GMPParent.h"
 #include "GMPContentParent.h"
 #include "nsXPCOMPrivate.h"
 #include "mozilla/SyncRunnable.h"
 #include "runnable_utils.h"
@@ -123,16 +125,94 @@ GeckoMediaPluginServiceChild::GetContent
 
   UniquePtr<GetServiceChildCallback> callback(
     new GetContentParentFromDone(aHelper, aNodeId, aAPI, aTags, Move(aCallback)));
   GetServiceChild(Move(callback));
 
   return true;
 }
 
+typedef mozilla::dom::GMPCapabilityData GMPCapabilityData;
+typedef mozilla::dom::GMPAPITags GMPAPITags;
+
+struct GMPCapabilityAndVersion
+{
+  explicit GMPCapabilityAndVersion(const GMPCapabilityData& aCapabilities)
+    : mName(aCapabilities.name())
+    , mVersion(aCapabilities.version())
+  {
+    for (const GMPAPITags& tags : aCapabilities.capabilities()) {
+      GMPCapability cap;
+      cap.mAPIName = tags.api();
+      for (const nsCString& tag : tags.tags()) {
+        cap.mAPITags.AppendElement(tag);
+      }
+      mCapabilities.AppendElement(Move(cap));
+    }
+  }
+
+  nsCString ToString() const
+  {
+    nsCString s;
+    s.Append(mName);
+    s.Append(" version=");
+    s.Append(mVersion);
+    s.Append(" tags=[");
+    nsCString tags;
+    for (const GMPCapability& cap : mCapabilities) {
+      if (!tags.IsEmpty()) {
+        tags.Append(" ");
+      }
+      tags.Append(cap.mAPIName);
+      for (const nsCString& tag : cap.mAPITags) {
+        tags.Append(":");
+        tags.Append(tag);
+      }
+    }
+    s.Append(tags);
+    s.Append("]");
+    return s;
+  }
+
+  nsCString mName;
+  nsCString mVersion;
+  nsTArray<GMPCapability> mCapabilities;
+};
+
+StaticAutoPtr<nsTArray<GMPCapabilityAndVersion>> sGMPCapabilities;
+
+static nsCString
+GMPCapabilitiesToString()
+{
+  nsCString s;
+  for (const GMPCapabilityAndVersion& gmp : *sGMPCapabilities) {
+    if (!s.IsEmpty()) {
+      s.Append(", ");
+    }
+    s.Append(gmp.ToString());
+  }
+  return s;
+}
+
+/* static */
+void
+GeckoMediaPluginServiceChild::UpdateGMPCapabilities(nsTArray<GMPCapabilityData>&& aCapabilities)
+{
+  if (!sGMPCapabilities) {
+    sGMPCapabilities = new nsTArray<GMPCapabilityAndVersion>();
+    ClearOnShutdown(&sGMPCapabilities);
+  }
+  sGMPCapabilities->Clear();
+  for (const GMPCapabilityData& plugin : aCapabilities) {
+    sGMPCapabilities->AppendElement(GMPCapabilityAndVersion(plugin));
+  }
+
+  LOGD(("UpdateGMPCapabilities {%s}", GMPCapabilitiesToString().get()));
+}
+
 NS_IMETHODIMP
 GeckoMediaPluginServiceChild::GetPluginVersionForAPI(const nsACString& aAPI,
                                                      nsTArray<nsCString>* aTags,
                                                      bool* aHasPlugin,
                                                      nsACString& aOutVersion)
 {
   dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
   if (!contentChild) {
--- a/dom/media/gmp/GMPServiceChild.h
+++ b/dom/media/gmp/GMPServiceChild.h
@@ -6,16 +6,17 @@
 #ifndef GMPServiceChild_h_
 #define GMPServiceChild_h_
 
 #include "GMPService.h"
 #include "base/process.h"
 #include "mozilla/ipc/Transport.h"
 #include "mozilla/gmp/PGMPServiceChild.h"
 #include "nsRefPtrHashtable.h"
+#include "mozilla/dom/ContentChild.h"
 
 namespace mozilla {
 namespace gmp {
 
 class GMPContentParent;
 class GMPServiceChild;
 
 class GetServiceChildCallback
@@ -50,16 +51,18 @@ public:
                        UniquePtr<GetNodeIdCallback>&& aCallback) override;
 
   NS_DECL_NSIOBSERVER
 
   void SetServiceChild(UniquePtr<GMPServiceChild>&& aServiceChild);
 
   void RemoveGMPContentParent(GMPContentParent* aGMPContentParent);
 
+  static void UpdateGMPCapabilities(nsTArray<mozilla::dom::GMPCapabilityData>&& aCapabilities);
+
 protected:
   void InitializePlugins(AbstractThread*) override
   {
     // Nothing to do here.
   }
   bool GetContentParentFrom(GMPCrashHelper* aHelper,
                             const nsACString& aNodeId,
                             const nsCString& aAPI,
--- a/dom/media/gmp/GMPServiceParent.cpp
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -3,16 +3,17 @@
  * 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 "GMPServiceParent.h"
 #include "GMPService.h"
 #include "prio.h"
 #include "base/task.h"
 #include "mozilla/Logging.h"
+#include "mozilla/dom/ContentParent.h"
 #include "GMPParent.h"
 #include "GMPVideoDecoderParent.h"
 #include "nsAutoPtr.h"
 #include "nsIObserverService.h"
 #include "GeckoChildProcessHost.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/SyncRunnable.h"
@@ -829,51 +830,102 @@ private:
 };
 
 NS_IMETHODIMP
 GeckoMediaPluginServiceParent::PathRunnable::Run()
 {
   mService->RemoveOnGMPThread(mPath,
                               mOperation == REMOVE_AND_DELETE_FROM_DISK,
                               mDefer);
+
+  mService->UpdateContentProcessGMPCapabilities();
+
 #ifndef MOZ_WIDGET_GONK // Bug 1214967: disabled on B2G due to inscrutable test failures.
   // For e10s, we must fire a notification so that all ContentParents notify
   // their children to update the codecs that the GMPDecoderModule can use.
   NS_DispatchToMainThread(new NotifyObserversTask("gmp-changed"), NS_DISPATCH_NORMAL);
   // For non-e10s, and for decoding in the chrome process, must update GMP
   // PDM's codecs list directly.
   NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
     GMPDecoderModule::UpdateUsableCodecs();
   }));
 #endif
   return NS_OK;
 }
 
+void
+GeckoMediaPluginServiceParent::UpdateContentProcessGMPCapabilities()
+{
+  if (!NS_IsMainThread()) {
+    nsCOMPtr<nsIRunnable> task =
+      NewRunnableMethod(this, &GeckoMediaPluginServiceParent::UpdateContentProcessGMPCapabilities);
+    NS_DispatchToMainThread(task);
+    return;
+  }
+
+  typedef mozilla::dom::GMPCapabilityData GMPCapabilityData;
+  typedef mozilla::dom::GMPAPITags GMPAPITags;
+  typedef mozilla::dom::ContentParent ContentParent;
+
+  nsTArray<GMPCapabilityData> caps;
+  {
+    MutexAutoLock lock(mMutex);
+    for (const RefPtr<GMPParent>& gmp : mPlugins) {
+      // We have multiple instances of a GMPParent for a given GMP in the
+      // list, one per origin. So filter the list so that we don't include
+      // the same GMP's capabilities twice.
+      NS_ConvertUTF16toUTF8 name(gmp->GetPluginBaseName());
+      bool found = false;
+      for (const GMPCapabilityData& cap : caps) {
+        if (cap.name().Equals(name)) {
+          found = true;
+          break;
+        }
+      }
+      if (found) {
+        continue;
+      }
+      GMPCapabilityData x;
+      x.name() = name;
+      x.version() = gmp->GetVersion();
+      for (const GMPCapability& tag : gmp->GetCapabilities()) {
+        x.capabilities().AppendElement(GMPAPITags(tag.mAPIName, tag.mAPITags));
+      }
+      caps.AppendElement(Move(x));
+    }
+  }
+  for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+    Unused << cp->SendGMPsChanged(caps);
+  }
+}
+
 RefPtr<GenericPromise>
 GeckoMediaPluginServiceParent::AsyncAddPluginDirectory(const nsAString& aDirectory)
 {
   RefPtr<AbstractThread> thread(GetAbstractGMPThread());
   if (!thread) {
     return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   nsString dir(aDirectory);
+  RefPtr<GeckoMediaPluginServiceParent> self = this;
   return InvokeAsync(thread, this, __func__, &GeckoMediaPluginServiceParent::AddOnGMPThread, dir)
     ->Then(AbstractThread::MainThread(), __func__,
-      [dir]() -> void {
+      [dir, self]() -> void {
         LOGD(("GeckoMediaPluginServiceParent::AsyncAddPluginDirectory %s succeeded",
               NS_ConvertUTF16toUTF8(dir).get()));
         MOZ_ASSERT(NS_IsMainThread());
         // For e10s, we must fire a notification so that all ContentParents notify
         // their children to update the codecs that the GMPDecoderModule can use.
         nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
         MOZ_ASSERT(obsService);
         if (obsService) {
           obsService->NotifyObservers(nullptr, "gmp-changed", nullptr);
         }
+        self->UpdateContentProcessGMPCapabilities();
         // For non-e10s, and for decoding in the chrome process, must update GMP
         // PDM's codecs list directly.
         GMPDecoderModule::UpdateUsableCodecs();
       },
       [dir]() -> void {
         LOGD(("GeckoMediaPluginServiceParent::AsyncAddPluginDirectory %s failed",
               NS_ConvertUTF16toUTF8(dir).get()));
       })
--- a/dom/media/gmp/GMPServiceParent.h
+++ b/dom/media/gmp/GMPServiceParent.h
@@ -67,16 +67,18 @@ public:
   already_AddRefed<GMPStorage> GetMemoryStorageFor(const nsACString& aNodeId);
   nsresult ForgetThisSiteNative(const nsAString& aSite,
                                 const mozilla::OriginAttributesPattern& aPattern);
 
   // Notifies that some user of this class is created/destroyed.
   void ServiceUserCreated();
   void ServiceUserDestroyed();
 
+  void UpdateContentProcessGMPCapabilities();
+
 private:
   friend class GMPServiceParent;
 
   virtual ~GeckoMediaPluginServiceParent();
 
   void ClearStorage();
 
   already_AddRefed<GMPParent> SelectPluginForAPI(const nsACString& aNodeId,