Bug 1268984 - Ensure GMPs are re-inserted in GMPServiceParent::mPlugins in the same order in ReAddOnGMPThread. r=jesup draft
authorChris Pearce <cpearce@mozilla.com>
Wed, 04 May 2016 13:57:20 +1200
changeset 364158 ec041b5e59f72ae13a8cf5a6a051b86ef9f0a780
parent 362943 0a25833062a880f369e6f9f622413a94cc671bf4
child 364159 b1156751924ab54ab8862cb2b22bb001476ff5e5
child 364165 3d84f8689458ac12b555f7cfbcefaccf294b32e4
push id17371
push usercpearce@mozilla.com
push dateFri, 06 May 2016 01:44:18 +0000
reviewersjesup
bugs1268984
milestone49.0a1
Bug 1268984 - Ensure GMPs are re-inserted in GMPServiceParent::mPlugins in the same order in ReAddOnGMPThread. r=jesup The GMP which GeckoMediaPluginServiceParent::FindPluginForAPIFrom() returns depends on the order in which GMPs lie in GMPServiceParent::mPlugins. However when we shutdown a GMPParent we remove and then re-append the GMPParent to mPlugins. This means the order in which GMPs lie in the list changes. So when WebRTC requests an H.264 decoder, the first time it will get OpenH264, since that's first in the list. But once we dispose of that decoder, its GMPParent will be cloned and the clone will be appended to the end of the list. This means the next time WebRTC requests a decoder, it'll get whatever was next in the list. This could be the Adobe GMP, which seems to be able to handle whatever WebRTC is putting into it. However, if you do this enough times, you'll get the Widevine CDM, which can't handle whatever WebRTC is putting into it. So a quick hack to fix this is in ReAddOnGMPThread is to re-insert the clone of the GMP into the slot in mPlugins that the original occupied. Then WebRTC will always get OpenH264 whenever it requests for an H.264 decoder, as the order of the GMPParents in mPlugins won't change. MozReview-Commit-ID: Ii4AMqDqAo9
dom/media/gmp/GMPServiceParent.cpp
dom/media/gmp/GMPServiceParent.h
--- a/dom/media/gmp/GMPServiceParent.cpp
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -905,17 +905,17 @@ GeckoMediaPluginServiceParent::GetPlugin
     MutexAutoLock lock(mMutex);
     nsCString api(aAPI);
     size_t index = 0;
 
     // We must parse the version number into a float for comparison. Yuck.
     double maxParsedVersion = -1.;
 
     *aHasPlugin = false;
-    while (GMPParent* gmp = FindPluginForAPIFrom(index, api, *aTags, &index)) {
+    while (RefPtr<GMPParent> gmp = FindPluginForAPIFrom(index, api, *aTags, &index)) {
       *aHasPlugin = true;
       double parsedVersion = atof(gmp->GetVersion().get());
       if (maxParsedVersion < 0 || parsedVersion > maxParsedVersion) {
         maxParsedVersion = parsedVersion;
         aOutVersion = gmp->GetVersion();
       }
       index++;
     }
@@ -938,66 +938,66 @@ GeckoMediaPluginServiceParent::EnsurePlu
     nsresult rv = GMPDispatch(new mozilla::Runnable(), NS_DISPATCH_SYNC);
     NS_ENSURE_SUCCESS(rv, rv);
     MOZ_ASSERT(mScannedPluginOnDisk, "Should have scanned MOZ_GMP_PATH by now");
   }
 
   return NS_OK;
 }
 
-GMPParent*
+already_AddRefed<GMPParent>
 GeckoMediaPluginServiceParent::FindPluginForAPIFrom(size_t aSearchStartIndex,
                                                     const nsCString& aAPI,
                                                     const nsTArray<nsCString>& aTags,
                                                     size_t* aOutPluginIndex)
 {
   mMutex.AssertCurrentThreadOwns();
   for (size_t i = aSearchStartIndex; i < mPlugins.Length(); i++) {
-    GMPParent* gmp = mPlugins[i];
+    RefPtr<GMPParent> gmp = mPlugins[i];
     bool supportsAllTags = true;
     for (size_t t = 0; t < aTags.Length(); t++) {
       const nsCString& tag = aTags.ElementAt(t);
       if (!gmp->SupportsAPI(aAPI, tag)) {
         supportsAllTags = false;
         break;
       }
     }
     if (!supportsAllTags) {
       continue;
     }
     if (aOutPluginIndex) {
       *aOutPluginIndex = i;
     }
-    return gmp;
+    return gmp.forget();
   }
   return nullptr;
 }
 
-GMPParent*
+already_AddRefed<GMPParent>
 GeckoMediaPluginServiceParent::SelectPluginForAPI(const nsACString& aNodeId,
                                                   const nsCString& aAPI,
                                                   const nsTArray<nsCString>& aTags)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread,
              "Can't clone GMP plugins on non-GMP threads.");
 
   GMPParent* gmpToClone = nullptr;
   {
     MutexAutoLock lock(mMutex);
     size_t index = 0;
-    GMPParent* gmp = nullptr;
+    RefPtr<GMPParent> gmp;
     while ((gmp = FindPluginForAPIFrom(index, aAPI, aTags, &index))) {
       if (aNodeId.IsEmpty()) {
         if (gmp->CanBeSharedCrossNodeIds()) {
-          return gmp;
+          return gmp.forget();
         }
       } else if (gmp->CanBeUsedFrom(aNodeId)) {
         MOZ_ASSERT(!aNodeId.IsEmpty());
         gmp->SetNodeId(aNodeId);
-        return gmp;
+        return gmp.forget();
       }
 
       if (!gmpToClone ||
           (gmpToClone->IsMarkedForDeletion() && !gmp->IsMarkedForDeletion())) {
         // This GMP has the correct type but has the wrong nodeId; hold on to it
         // in case we need to clone it.
         // Prefer GMPs in-use for the case where an upgraded plugin version is
         // waiting for the old one to die. If the old plugin is in use, we
@@ -1009,21 +1009,25 @@ GeckoMediaPluginServiceParent::SelectPlu
       // Loop around and try the next plugin; it may be usable from aNodeId.
       index++;
     }
   }
 
   // Plugin exists, but we can't use it due to cross-origin separation. Create a
   // new one.
   if (gmpToClone) {
-    GMPParent* clone = ClonePlugin(gmpToClone);
+    RefPtr<GMPParent> clone = ClonePlugin(gmpToClone);
+    {
+      MutexAutoLock lock(mMutex);
+      mPlugins.AppendElement(clone);
+    }
     if (!aNodeId.IsEmpty()) {
       clone->SetNodeId(aNodeId);
     }
-    return clone;
+    return clone.forget();
   }
 
   return nullptr;
 }
 
 RefPtr<GMPParent>
 CreateGMPParent()
 {
@@ -1034,33 +1038,30 @@ CreateGMPParent()
       return nullptr;
     }
     NS_WARNING("Loading media plugin despite lack of sandboxing.");
   }
 #endif
   return new GMPParent();
 }
 
-GMPParent*
+already_AddRefed<GMPParent>
 GeckoMediaPluginServiceParent::ClonePlugin(const GMPParent* aOriginal)
 {
   MOZ_ASSERT(aOriginal);
 
   RefPtr<GMPParent> gmp = CreateGMPParent();
   nsresult rv = gmp ? gmp->CloneFrom(aOriginal) : NS_ERROR_NOT_AVAILABLE;
 
   if (NS_FAILED(rv)) {
     NS_WARNING("Can't Create GMPParent");
     return nullptr;
   }
 
-  MutexAutoLock lock(mMutex);
-  mPlugins.AppendElement(gmp);
-
-  return gmp.get();
+  return gmp.forget();
 }
 
 RefPtr<GenericPromise>
 GeckoMediaPluginServiceParent::AddOnGMPThread(nsString aDirectory)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
   LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, NS_LossyConvertUTF16toASCII(aDirectory).get()));
 
@@ -1193,25 +1194,31 @@ GeckoMediaPluginServiceParent::PluginTer
 void
 GeckoMediaPluginServiceParent::ReAddOnGMPThread(const RefPtr<GMPParent>& aOld)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
   LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, (void*) aOld));
 
   RefPtr<GMPParent> gmp;
   if (!mShuttingDownOnGMPThread) {
-    // Don't re-add plugin if we're shutting down. Let the old plugin die.
+    // We're not shutting down, so replace the old plugin in the list with a
+    // clone which is in a pristine state. Note: We place the plugin in
+    // the same slot in the array as a hack to ensure if we re-request with
+    // the same capabilities we get an instance of the same plugin.
     gmp = ClonePlugin(aOld);
+    MutexAutoLock lock(mMutex);
+    MOZ_ASSERT(mPlugins.Contains(aOld));
+    if (mPlugins.Contains(aOld)) {
+      mPlugins[mPlugins.IndexOf(aOld)] = gmp;
+    }
+  } else {
+    // We're shutting down; don't re-add plugin, let the old plugin die.
+    MutexAutoLock lock(mMutex);
+    mPlugins.RemoveElement(aOld);
   }
-  // Note: both are now in the list
-  // Until we give up the GMPThread, we're safe even if we unlock temporarily
-  // since off-main-thread users just test for existance; they don't modify the list.
-  MutexAutoLock lock(mMutex);
-  mPlugins.RemoveElement(aOld);
-
   // Schedule aOld to be destroyed.  We can't destroy it from here since we
   // may be inside ActorDestroyed() for it.
   NS_DispatchToCurrentThread(WrapRunnableNM(&Dummy, aOld));
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginServiceParent::GetStorageDir(nsIFile** aOutFile)
 {
--- a/dom/media/gmp/GMPServiceParent.h
+++ b/dom/media/gmp/GMPServiceParent.h
@@ -64,23 +64,24 @@ public:
 
 private:
   friend class GMPServiceParent;
 
   virtual ~GeckoMediaPluginServiceParent();
 
   void ClearStorage();
 
-  GMPParent* SelectPluginForAPI(const nsACString& aNodeId,
-                                const nsCString& aAPI,
-                                const nsTArray<nsCString>& aTags);
-  GMPParent* FindPluginForAPIFrom(size_t aSearchStartIndex,
-                                  const nsCString& aAPI,
-                                  const nsTArray<nsCString>& aTags,
-                                  size_t* aOutPluginIndex);
+  already_AddRefed<GMPParent> SelectPluginForAPI(const nsACString& aNodeId,
+                                                 const nsCString& aAPI,
+                                                 const nsTArray<nsCString>& aTags);
+
+  already_AddRefed<GMPParent> FindPluginForAPIFrom(size_t aSearchStartIndex,
+                                                   const nsCString& aAPI,
+                                                   const nsTArray<nsCString>& aTags,
+                                                   size_t* aOutPluginIndex);
 
   nsresult GetNodeId(const nsAString& aOrigin, const nsAString& aTopLevelOrigin,
                      const nsAString& aGMPName,
                      bool aInPrivateBrowsing, nsACString& aOutId);
 
   void UnloadPlugins();
   void CrashPlugins();
   void NotifySyncShutdownComplete();
@@ -112,17 +113,19 @@ protected:
   RefPtr<GenericPromise::AllPromiseType> LoadFromEnvironment();
   RefPtr<GenericPromise> AddOnGMPThread(nsString aDirectory);
   bool GetContentParentFrom(const nsACString& aNodeId,
                             const nsCString& aAPI,
                             const nsTArray<nsCString>& aTags,
                             UniquePtr<GetGMPContentParentCallback>&& aCallback)
     override;
 private:
-  GMPParent* ClonePlugin(const GMPParent* aOriginal);
+  // Creates a copy of aOriginal. Note that the caller is responsible for
+  // adding this to GeckoMediaPluginServiceParent::mPlugins.
+  already_AddRefed<GMPParent> ClonePlugin(const GMPParent* aOriginal);
   nsresult EnsurePluginsOnDiskScanned();
   nsresult InitStorage();
 
   class PathRunnable : public Runnable
   {
   public:
     enum EOperation {
       REMOVE,