Bug 1057908 - GeckoMediaPluginService needs to be proxied from Content processes to parent process. Part 2 - support asynchronous GMP API getters. r=jwwang,rjesup.
authorPeter Van der Beken <peterv@propagandism.org>
Tue, 10 Feb 2015 11:48:29 +0100
changeset 254455 223de26bbb6f7501290e1184a1e4c49caf4837ab
parent 254454 809bc8418e5f4c11a44e88bf46a09eed039bf55d
child 254456 d778005df07235b5743764029b8fd60f94223636
push id4830
push userjlund@mozilla.com
push dateMon, 29 Jun 2015 20:18:48 +0000
treeherdermozilla-esr52@4c2175bb0420 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwwang, rjesup
bugs1057908
milestone40.0a1
Bug 1057908 - GeckoMediaPluginService needs to be proxied from Content processes to parent process. Part 2 - support asynchronous GMP API getters. r=jwwang,rjesup.
dom/media/eme/CDMProxy.cpp
dom/media/eme/CDMProxy.h
dom/media/fmp4/gmp/GMPAudioDecoder.cpp
dom/media/fmp4/gmp/GMPAudioDecoder.h
dom/media/fmp4/gmp/GMPVideoDecoder.cpp
dom/media/fmp4/gmp/GMPVideoDecoder.h
dom/media/gmp/GMPService.cpp
dom/media/gmp/GMPService.h
dom/media/gmp/mozIGeckoMediaPluginService.idl
dom/media/gtest/TestGMPCrossOrigin.cpp
media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.cpp
media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.h
--- a/dom/media/eme/CDMProxy.cpp
+++ b/dom/media/eme/CDMProxy.cpp
@@ -20,16 +20,17 @@
 
 namespace mozilla {
 
 CDMProxy::CDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem)
   : mKeys(aKeys)
   , mKeySystem(aKeySystem)
   , mCDM(nullptr)
   , mDecryptionJobCount(0)
+  , mShutdownCalled(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_COUNT_CTOR(CDMProxy);
 }
 
 CDMProxy::~CDMProxy()
 {
   MOZ_COUNT_DTOR(CDMProxy);
@@ -65,69 +66,107 @@ CDMProxy::Init(PromiseId aPromiseId,
   nsAutoPtr<InitData> data(new InitData());
   data->mPromiseId = aPromiseId;
   data->mOrigin = aOrigin;
   data->mTopLevelOrigin = aTopLevelOrigin;
   data->mInPrivateBrowsing = aInPrivateBrowsing;
   nsCOMPtr<nsIRunnable> task(
     NS_NewRunnableMethodWithArg<nsAutoPtr<InitData>>(this,
                                                      &CDMProxy::gmp_Init,
-                                                     data));
+                                                     Move(data)));
   mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
 }
 
 #ifdef DEBUG
 bool
 CDMProxy::IsOnGMPThread()
 {
   return NS_GetCurrentThread() == mGMPThread;
 }
 #endif
 
 void
-CDMProxy::gmp_Init(nsAutoPtr<InitData> aData)
+CDMProxy::gmp_InitDone(GMPDecryptorProxy* aCDM, nsAutoPtr<InitData>&& aData)
+{
+  EME_LOG("CDMProxy::gmp_InitDone");
+  if (!aCDM || mShutdownCalled) {
+    if (aCDM) {
+      aCDM->Close();
+    }
+    RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  mCDM = aCDM;
+  mCallback = new CDMCallbackProxy(this);
+  mCDM->Init(mCallback);
+  nsCOMPtr<nsIRunnable> task(
+    NS_NewRunnableMethodWithArg<uint32_t>(this,
+                                          &CDMProxy::OnCDMCreated,
+                                          aData->mPromiseId));
+  NS_DispatchToMainThread(task);
+}
+
+class gmp_InitDoneCallback : public GetGMPDecryptorCallback
+{
+public:
+  gmp_InitDoneCallback(CDMProxy* aCDMProxy,
+                       nsAutoPtr<CDMProxy::InitData>&& aData)
+    : mCDMProxy(aCDMProxy),
+      mData(Move(aData))
+  {
+  }
+
+  void Done(GMPDecryptorProxy* aCDM)
+  {
+    mCDMProxy->gmp_InitDone(aCDM, Move(mData));
+  }
+
+private:
+  nsRefPtr<CDMProxy> mCDMProxy;
+  nsAutoPtr<CDMProxy::InitData> mData;
+};
+
+void
+CDMProxy::gmp_Init(nsAutoPtr<InitData>&& aData)
 {
   MOZ_ASSERT(IsOnGMPThread());
 
+  uint32_t promiseID = aData->mPromiseId;
   nsCOMPtr<mozIGeckoMediaPluginService> mps =
     do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   if (!mps) {
-    RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
+    RejectPromise(promiseID, NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   nsresult rv = mps->GetNodeId(aData->mOrigin,
                                aData->mTopLevelOrigin,
                                aData->mInPrivateBrowsing,
                                mNodeId);
   MOZ_ASSERT(!GetNodeId().IsEmpty());
   if (NS_FAILED(rv)) {
-    RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
+    RejectPromise(promiseID, NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   EME_LOG("CDMProxy::gmp_Init (%s, %s) %s NodeId=%s",
           NS_ConvertUTF16toUTF8(aData->mOrigin).get(),
           NS_ConvertUTF16toUTF8(aData->mTopLevelOrigin).get(),
           (aData->mInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"),
           GetNodeId().get());
 
   nsTArray<nsCString> tags;
   tags.AppendElement(NS_ConvertUTF16toUTF8(mKeySystem));
-  rv = mps->GetGMPDecryptor(&tags, GetNodeId(), &mCDM);
-  if (NS_FAILED(rv) || !mCDM) {
-    RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
-  } else {
-    mCallback = new CDMCallbackProxy(this);
-    mCDM->Init(mCallback);
-    nsCOMPtr<nsIRunnable> task(
-      NS_NewRunnableMethodWithArg<uint32_t>(this,
-                                            &CDMProxy::OnCDMCreated,
-                                            aData->mPromiseId));
-    NS_DispatchToMainThread(task);
+
+  UniquePtr<GetGMPDecryptorCallback> callback(new gmp_InitDoneCallback(this,
+                                                                       Move(aData)));
+  rv = mps->GetGMPDecryptor(&tags, GetNodeId(), Move(callback));
+  if (NS_FAILED(rv)) {
+    RejectPromise(promiseID, NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 }
 
 void
 CDMProxy::OnCDMCreated(uint32_t aPromiseId)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mKeys.IsNull()) {
@@ -336,16 +375,18 @@ CDMProxy::Shutdown()
   }
 }
 
 void
 CDMProxy::gmp_Shutdown()
 {
   MOZ_ASSERT(IsOnGMPThread());
 
+  mShutdownCalled = true;
+
   // Abort any pending decrypt jobs, to awaken any clients waiting on a job.
   for (size_t i = 0; i < mDecryptionJobs.Length(); i++) {
     DecryptJob* job = mDecryptionJobs[i];
     job->mClient->Decrypted(GMPAbortedErr, nullptr);
   }
   mDecryptionJobs.Clear();
 
   if (mCDM) {
--- a/dom/media/eme/CDMProxy.h
+++ b/dom/media/eme/CDMProxy.h
@@ -169,26 +169,28 @@ public:
   void GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
                              nsTArray<nsCString>& aSessionIds);
 
 #ifdef DEBUG
   bool IsOnGMPThread();
 #endif
 
 private:
+  friend class gmp_InitDoneCallback;
 
   struct InitData {
     uint32_t mPromiseId;
     nsAutoString mOrigin;
     nsAutoString mTopLevelOrigin;
     bool mInPrivateBrowsing;
   };
 
   // GMP thread only.
-  void gmp_Init(nsAutoPtr<InitData> aData);
+  void gmp_Init(nsAutoPtr<InitData>&& aData);
+  void gmp_InitDone(GMPDecryptorProxy* aCDM, nsAutoPtr<InitData>&& aData);
 
   // GMP thread only.
   void gmp_Shutdown();
 
   // Main thread only.
   void OnCDMCreated(uint32_t aPromiseId);
 
   struct CreateSessionData {
@@ -315,14 +317,18 @@ private:
   nsTArray<nsAutoPtr<DecryptJob>> mDecryptionJobs;
 
   // Number of buffers we've decrypted. Used to uniquely identify
   // decryption jobs sent to CDM. Note we can't just use the length of
   // mDecryptionJobs as that shrinks as jobs are completed and removed
   // from it.
   // GMP thread only.
   uint32_t mDecryptionJobCount;
+
+  // True if CDMProxy::gmp_Shutdown was called.
+  // GMP thread only.
+  bool mShutdownCalled;
 };
 
 
 } // namespace mozilla
 
 #endif // CDMProxy_h_
--- a/dom/media/fmp4/gmp/GMPAudioDecoder.cpp
+++ b/dom/media/fmp4/gmp/GMPAudioDecoder.cpp
@@ -128,43 +128,71 @@ GMPAudioDecoder::InitTags(nsTArray<nsCSt
 }
 
 nsCString
 GMPAudioDecoder::GetNodeId()
 {
   return NS_LITERAL_CSTRING("");
 }
 
+void
+GMPAudioDecoder::GetGMPAPI(GMPInitDoneRunnable* aInitDone)
+{
+  MOZ_ASSERT(IsOnGMPThread());
+
+  nsTArray<nsCString> tags;
+  InitTags(tags);
+  UniquePtr<GetGMPAudioDecoderCallback> callback(
+    new GMPInitDoneCallback(this, aInitDone));
+  if (NS_FAILED(mMPS->GetGMPAudioDecoder(&tags, GetNodeId(), Move(callback)))) {
+    aInitDone->Dispatch();
+  }
+}
+
+void
+GMPAudioDecoder::GMPInitDone(GMPAudioDecoderProxy* aGMP)
+{
+  MOZ_ASSERT(aGMP);
+  nsTArray<uint8_t> codecSpecific;
+  codecSpecific.AppendElements(mConfig.audio_specific_config->Elements(),
+                               mConfig.audio_specific_config->Length());
+
+  nsresult rv = aGMP->InitDecode(kGMPAudioCodecAAC,
+                                 mConfig.channel_count,
+                                 mConfig.bits_per_sample,
+                                 mConfig.samples_per_second,
+                                 codecSpecific,
+                                 mAdapter);
+  if (NS_SUCCEEDED(rv)) {
+    mGMP = aGMP;
+  }
+}
+
 nsresult
 GMPAudioDecoder::Init()
 {
   MOZ_ASSERT(IsOnGMPThread());
 
   mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   MOZ_ASSERT(mMPS);
 
-  nsTArray<nsCString> tags;
-  InitTags(tags);
-  nsresult rv = mMPS->GetGMPAudioDecoder(&tags, GetNodeId(), &mGMP);
-  NS_ENSURE_SUCCESS(rv, rv);
-  MOZ_ASSERT(mGMP);
-
-  nsTArray<uint8_t> codecSpecific;
-  codecSpecific.AppendElements(mConfig.audio_specific_config->Elements(),
-                               mConfig.audio_specific_config->Length());
+  nsCOMPtr<nsIThread> gmpThread = NS_GetCurrentThread();
 
-  rv = mGMP->InitDecode(kGMPAudioCodecAAC,
-                        mConfig.channel_count,
-                        mConfig.bits_per_sample,
-                        mConfig.samples_per_second,
-                        codecSpecific,
-                        mAdapter);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsRefPtr<GMPInitDoneRunnable> initDone(new GMPInitDoneRunnable());
+  gmpThread->Dispatch(
+    NS_NewRunnableMethodWithArg<GMPInitDoneRunnable*>(this,
+                                                      &GMPAudioDecoder::GetGMPAPI,
+                                                      initDone),
+    NS_DISPATCH_NORMAL);
 
-  return NS_OK;
+  while (!initDone->IsDone()) {
+    NS_ProcessNextEvent(gmpThread, true);
+  }
+
+  return mGMP ? NS_OK : NS_ERROR_FAILURE;
 }
 
 nsresult
 GMPAudioDecoder::Input(mp4_demuxer::MP4Sample* aSample)
 {
   MOZ_ASSERT(IsOnGMPThread());
 
   nsAutoPtr<mp4_demuxer::MP4Sample> sample(aSample);
--- a/dom/media/fmp4/gmp/GMPAudioDecoder.h
+++ b/dom/media/fmp4/gmp/GMPAudioDecoder.h
@@ -75,16 +75,71 @@ public:
   virtual nsresult Drain() override;
   virtual nsresult Shutdown() override;
 
 protected:
   virtual void InitTags(nsTArray<nsCString>& aTags);
   virtual nsCString GetNodeId();
 
 private:
+  class GMPInitDoneRunnable : public nsRunnable
+  {
+  public:
+    GMPInitDoneRunnable()
+      : mInitDone(false),
+        mThread(do_GetCurrentThread())
+    {
+    }
+
+    NS_IMETHOD Run()
+    {
+      mInitDone = true;
+      return NS_OK;
+    }
+
+    void Dispatch()
+    {
+      mThread->Dispatch(this, NS_DISPATCH_NORMAL);
+    }
+
+    bool IsDone()
+    {
+      MOZ_ASSERT(nsCOMPtr<nsIThread>(do_GetCurrentThread()) == mThread);
+      return mInitDone;
+    }
+
+  private:
+    bool mInitDone;
+    nsCOMPtr<nsIThread> mThread;
+  };
+  void GetGMPAPI(GMPInitDoneRunnable* aInitDone);
+  class GMPInitDoneCallback : public GetGMPAudioDecoderCallback
+  {
+  public:
+    GMPInitDoneCallback(GMPAudioDecoder* aDecoder,
+                        GMPInitDoneRunnable* aGMPInitDone)
+      : mDecoder(aDecoder)
+      , mGMPInitDone(aGMPInitDone)
+    {
+    }
+
+    virtual void Done(GMPAudioDecoderProxy* aGMP)
+    {
+      if (aGMP) {
+        mDecoder->GMPInitDone(aGMP);
+      }
+      mGMPInitDone->Dispatch();
+    }
+
+  private:
+    nsRefPtr<GMPAudioDecoder> mDecoder;
+    nsRefPtr<GMPInitDoneRunnable> mGMPInitDone;
+  };
+  void GMPInitDone(GMPAudioDecoderProxy* aGMP);
+
   const mp4_demuxer::AudioDecoderConfig& mConfig;
   MediaDataDecoderCallbackProxy* mCallback;
   nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
   GMPAudioDecoderProxy* mGMP;
   nsAutoPtr<AudioCallbackAdapter> mAdapter;
 };
 
 } // namespace mozilla
--- a/dom/media/fmp4/gmp/GMPVideoDecoder.cpp
+++ b/dom/media/fmp4/gmp/GMPVideoDecoder.cpp
@@ -152,61 +152,91 @@ GMPVideoDecoder::CreateFrame(mp4_demuxer
   frame->SetTimeStamp(aSample->composition_timestamp);
   frame->SetCompleteFrame(true);
   frame->SetDuration(aSample->duration);
   frame->SetFrameType(aSample->is_sync_point ? kGMPKeyFrame : kGMPDeltaFrame);
 
   return frame;
 }
 
-nsresult
-GMPVideoDecoder::Init()
+void
+GMPVideoDecoder::GetGMPAPI(GMPInitDoneRunnable* aInitDone)
 {
   MOZ_ASSERT(IsOnGMPThread());
 
-  mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
-  MOZ_ASSERT(mMPS);
-
   nsTArray<nsCString> tags;
   InitTags(tags);
-  nsresult rv = mMPS->GetGMPVideoDecoder(&tags, GetNodeId(), &mHost, &mGMP);
-  NS_ENSURE_SUCCESS(rv, rv);
-  MOZ_ASSERT(mHost && mGMP);
+  UniquePtr<GetGMPVideoDecoderCallback> callback(
+    new GMPInitDoneCallback(this, aInitDone));
+  if (NS_FAILED(mMPS->GetGMPVideoDecoder(&tags, GetNodeId(), Move(callback)))) {
+    aInitDone->Dispatch();
+  }
+}
 
-  // GMP implementations have interpreted the meaning of GMP_BufferLength32
-  // differently.  The OpenH264 GMP expects GMP_BufferLength32 to behave as
-  // specified in the GMP API, where each buffer is prefixed by a 32-bit
-  // host-endian buffer length that includes the size of the buffer length
-  // field.  Other existing GMPs currently expect GMP_BufferLength32 (when
-  // combined with kGMPVideoCodecH264) to mean "like AVCC but restricted to
-  // 4-byte NAL lengths" (i.e. buffer lengths are specified in big-endian
-  // and do not include the length of the buffer length field.
-  mConvertNALUnitLengths = mGMP->GetDisplayName().EqualsLiteral("gmpopenh264");
+void
+GMPVideoDecoder::GMPInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost)
+{
+  MOZ_ASSERT(aHost && aGMP);
 
   GMPVideoCodec codec;
   memset(&codec, 0, sizeof(codec));
 
   codec.mGMPApiVersion = kGMPVersion33;
 
   codec.mCodecType = kGMPVideoCodecH264;
   codec.mWidth = mConfig.display_width;
   codec.mHeight = mConfig.display_height;
 
   nsTArray<uint8_t> codecSpecific;
   codecSpecific.AppendElement(0); // mPacketizationMode.
   codecSpecific.AppendElements(mConfig.extra_data->Elements(),
                                mConfig.extra_data->Length());
 
-  rv = mGMP->InitDecode(codec,
-                        codecSpecific,
-                        mAdapter,
-                        PR_GetNumberOfProcessors());
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsresult rv = aGMP->InitDecode(codec,
+                                 codecSpecific,
+                                 mAdapter,
+                                 PR_GetNumberOfProcessors());
+  if (NS_SUCCEEDED(rv)) {
+    mGMP = aGMP;
+    mHost = aHost;
+
+    // GMP implementations have interpreted the meaning of GMP_BufferLength32
+    // differently.  The OpenH264 GMP expects GMP_BufferLength32 to behave as
+    // specified in the GMP API, where each buffer is prefixed by a 32-bit
+    // host-endian buffer length that includes the size of the buffer length
+    // field.  Other existing GMPs currently expect GMP_BufferLength32 (when
+    // combined with kGMPVideoCodecH264) to mean "like AVCC but restricted to
+    // 4-byte NAL lengths" (i.e. buffer lengths are specified in big-endian
+    // and do not include the length of the buffer length field.
+    mConvertNALUnitLengths = mGMP->GetDisplayName().EqualsLiteral("gmpopenh264");
+  }
+}
 
-  return NS_OK;
+nsresult
+GMPVideoDecoder::Init()
+{
+  MOZ_ASSERT(IsOnGMPThread());
+
+  mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+  MOZ_ASSERT(mMPS);
+
+  nsCOMPtr<nsIThread> gmpThread = NS_GetCurrentThread();
+
+  nsRefPtr<GMPInitDoneRunnable> initDone(new GMPInitDoneRunnable());
+  gmpThread->Dispatch(
+    NS_NewRunnableMethodWithArg<GMPInitDoneRunnable*>(this,
+                                                      &GMPVideoDecoder::GetGMPAPI,
+                                                      initDone),
+    NS_DISPATCH_NORMAL);
+
+  while (!initDone->IsDone()) {
+    NS_ProcessNextEvent(gmpThread, true);
+  }
+
+  return mGMP ? NS_OK : NS_ERROR_FAILURE;
 }
 
 nsresult
 GMPVideoDecoder::Input(mp4_demuxer::MP4Sample* aSample)
 {
   MOZ_ASSERT(IsOnGMPThread());
 
   nsAutoPtr<mp4_demuxer::MP4Sample> sample(aSample);
--- a/dom/media/fmp4/gmp/GMPVideoDecoder.h
+++ b/dom/media/fmp4/gmp/GMPVideoDecoder.h
@@ -91,16 +91,71 @@ public:
   virtual nsresult Shutdown() override;
 
 protected:
   virtual void InitTags(nsTArray<nsCString>& aTags);
   virtual nsCString GetNodeId();
   virtual GMPUnique<GMPVideoEncodedFrame>::Ptr CreateFrame(mp4_demuxer::MP4Sample* aSample);
 
 private:
+  class GMPInitDoneRunnable : public nsRunnable
+  {
+  public:
+    GMPInitDoneRunnable()
+      : mInitDone(false),
+        mThread(do_GetCurrentThread())
+    {
+    }
+
+    NS_IMETHOD Run()
+    {
+      mInitDone = true;
+      return NS_OK;
+    }
+
+    void Dispatch()
+    {
+      mThread->Dispatch(this, NS_DISPATCH_NORMAL);
+    }
+
+    bool IsDone()
+    {
+      MOZ_ASSERT(nsCOMPtr<nsIThread>(do_GetCurrentThread()) == mThread);
+      return mInitDone;
+    }
+
+  private:
+    bool mInitDone;
+    nsCOMPtr<nsIThread> mThread;
+  };
+  void GetGMPAPI(GMPInitDoneRunnable* aInitDone);
+  class GMPInitDoneCallback : public GetGMPVideoDecoderCallback
+  {
+  public:
+    GMPInitDoneCallback(GMPVideoDecoder* aDecoder,
+                        GMPInitDoneRunnable* aGMPInitDone)
+      : mDecoder(aDecoder)
+      , mGMPInitDone(aGMPInitDone)
+    {
+    }
+
+    virtual void Done(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost)
+    {
+      if (aGMP) {
+        mDecoder->GMPInitDone(aGMP, aHost);
+      }
+      mGMPInitDone->Dispatch();
+    }
+
+  private:
+    nsRefPtr<GMPVideoDecoder> mDecoder;
+    nsRefPtr<GMPInitDoneRunnable> mGMPInitDone;
+  };
+  void GMPInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost);
+
   const mp4_demuxer::VideoDecoderConfig& mConfig;
   MediaDataDecoderCallbackProxy* mCallback;
   nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
   GMPVideoDecoderProxy* mGMP;
   GMPVideoHost* mHost;
   nsAutoPtr<VideoCallbackAdapter> mAdapter;
   bool mConvertNALUnitLengths;
 };
--- a/dom/media/gmp/GMPService.cpp
+++ b/dom/media/gmp/GMPService.cpp
@@ -276,57 +276,91 @@ GeckoMediaPluginService::GetThread(nsITh
   }
 
   NS_ADDREF(mGMPThread);
   *aThread = mGMPThread;
 
   return NS_OK;
 }
 
+class GetGMPParentForAudioDecoderDone
+{
+public:
+  explicit GetGMPParentForAudioDecoderDone(UniquePtr<GetGMPAudioDecoderCallback>&& aCallback)
+   : mCallback(Move(aCallback))
+  {
+  }
+
+  void Done(GMPParent* aGMPParent)
+  {
+    GMPAudioDecoderParent* gmpADP = nullptr;
+    aGMPParent->GetGMPAudioDecoder(&gmpADP);
+    mCallback->Done(gmpADP);
+  }
+
+private:
+  UniquePtr<GetGMPAudioDecoderCallback> mCallback;
+};
+
 NS_IMETHODIMP
 GeckoMediaPluginService::GetGMPAudioDecoder(nsTArray<nsCString>* aTags,
                                             const nsACString& aNodeId,
-                                            GMPAudioDecoderProxy** aGMPAD)
+                                            UniquePtr<GetGMPAudioDecoderCallback>&& aCallback)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
   NS_ENSURE_ARG(aTags && aTags->Length() > 0);
-  NS_ENSURE_ARG(aGMPAD);
+  NS_ENSURE_ARG(aCallback);
 
   if (mShuttingDownOnGMPThread) {
     return NS_ERROR_FAILURE;
   }
 
   nsRefPtr<GMPParent> gmp = SelectPluginForAPI(aNodeId,
                                                NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
                                                *aTags);
   if (!gmp) {
     return NS_ERROR_FAILURE;
   }
 
-  GMPAudioDecoderParent* gmpADP;
-  nsresult rv = gmp->GetGMPAudioDecoder(&gmpADP);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  *aGMPAD = gmpADP;
+  GetGMPParentForAudioDecoderDone(Move(aCallback)).Done(gmp);
 
   return NS_OK;
 }
 
+class GetGMPParentForVideoDecoderDone
+{
+public:
+  explicit GetGMPParentForVideoDecoderDone(UniquePtr<GetGMPVideoDecoderCallback>&& aCallback)
+   : mCallback(Move(aCallback))
+  {
+  }
+
+  void Done(GMPParent* aGMPParent)
+  {
+    GMPVideoDecoderParent* gmpVDP = nullptr;
+    GMPVideoHostImpl* videoHost = nullptr;
+    nsresult rv = aGMPParent->GetGMPVideoDecoder(&gmpVDP);
+    if (NS_SUCCEEDED(rv)) {
+      videoHost = &gmpVDP->Host();
+    }
+    mCallback->Done(gmpVDP, videoHost);
+  }
+
+private:
+  UniquePtr<GetGMPVideoDecoderCallback> mCallback;
+};
+
 NS_IMETHODIMP
 GeckoMediaPluginService::GetGMPVideoDecoder(nsTArray<nsCString>* aTags,
                                             const nsACString& aNodeId,
-                                            GMPVideoHost** aOutVideoHost,
-                                            GMPVideoDecoderProxy** aGMPVD)
+                                            UniquePtr<GetGMPVideoDecoderCallback>&& aCallback)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
   NS_ENSURE_ARG(aTags && aTags->Length() > 0);
-  NS_ENSURE_ARG(aOutVideoHost);
-  NS_ENSURE_ARG(aGMPVD);
+  NS_ENSURE_ARG(aCallback);
 
   if (mShuttingDownOnGMPThread) {
     return NS_ERROR_FAILURE;
   }
 
   nsRefPtr<GMPParent> gmp = SelectPluginForAPI(aNodeId,
                                                NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
                                                *aTags);
@@ -334,101 +368,121 @@ GeckoMediaPluginService::GetGMPVideoDeco
   nsCString api = (*aTags)[0];
   LOGD(("%s: %p returning %p for api %s", __FUNCTION__, (void *)this, (void *)gmp, api.get()));
 #endif
   if (!gmp) {
     return NS_ERROR_FAILURE;
   }
 
 
-  GMPVideoDecoderParent* gmpVDP;
-  nsresult rv = gmp->GetGMPVideoDecoder(&gmpVDP);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  *aGMPVD = gmpVDP;
-  *aOutVideoHost = &gmpVDP->Host();
+  GetGMPParentForVideoDecoderDone(Move(aCallback)).Done(gmp);
 
   return NS_OK;
 }
 
+class GetGMPParentForVideoEncoderDone
+{
+public:
+  explicit GetGMPParentForVideoEncoderDone(UniquePtr<GetGMPVideoEncoderCallback>&& aCallback)
+   : mCallback(Move(aCallback))
+  {
+  }
+
+  void Done(GMPParent* aGMPParent)
+  {
+    GMPVideoEncoderParent* gmpVEP = nullptr;
+    GMPVideoHostImpl* videoHost = nullptr;
+    nsresult rv = aGMPParent->GetGMPVideoEncoder(&gmpVEP);
+    if (NS_SUCCEEDED(rv)) {
+      videoHost = &gmpVEP->Host();
+    }
+    mCallback->Done(gmpVEP, videoHost);
+  }
+
+private:
+  UniquePtr<GetGMPVideoEncoderCallback> mCallback;
+};
+
 NS_IMETHODIMP
 GeckoMediaPluginService::GetGMPVideoEncoder(nsTArray<nsCString>* aTags,
                                             const nsACString& aNodeId,
-                                            GMPVideoHost** aOutVideoHost,
-                                            GMPVideoEncoderProxy** aGMPVE)
+                                            UniquePtr<GetGMPVideoEncoderCallback>&& aCallback)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
   NS_ENSURE_ARG(aTags && aTags->Length() > 0);
-  NS_ENSURE_ARG(aOutVideoHost);
-  NS_ENSURE_ARG(aGMPVE);
+  NS_ENSURE_ARG(aCallback);
 
   if (mShuttingDownOnGMPThread) {
     return NS_ERROR_FAILURE;
   }
 
   nsRefPtr<GMPParent> gmp = SelectPluginForAPI(aNodeId,
                                                NS_LITERAL_CSTRING(GMP_API_VIDEO_ENCODER),
                                                *aTags);
 #ifdef PR_LOGGING
   nsCString api = (*aTags)[0];
   LOGD(("%s: %p returning %p for api %s", __FUNCTION__, (void *)this, (void *)gmp, api.get()));
 #endif
   if (!gmp) {
     return NS_ERROR_FAILURE;
   }
 
-  GMPVideoEncoderParent* gmpVEP;
-  nsresult rv = gmp->GetGMPVideoEncoder(&gmpVEP);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  *aGMPVE = gmpVEP;
-  *aOutVideoHost = &gmpVEP->Host();
+  GetGMPParentForVideoEncoderDone(Move(aCallback)).Done(gmp);
 
   return NS_OK;
 }
 
+class GetGMPParentForDecryptorDone
+{
+public:
+  explicit GetGMPParentForDecryptorDone(UniquePtr<GetGMPDecryptorCallback>&& aCallback)
+   : mCallback(Move(aCallback))
+  {
+  }
+
+  void Done(GMPParent* aGMPParent)
+  {
+    GMPDecryptorParent* ksp = nullptr;
+    aGMPParent->GetGMPDecryptor(&ksp);
+    mCallback->Done(ksp);
+  }
+
+private:
+  UniquePtr<GetGMPDecryptorCallback> mCallback;
+};
+
 NS_IMETHODIMP
 GeckoMediaPluginService::GetGMPDecryptor(nsTArray<nsCString>* aTags,
                                          const nsACString& aNodeId,
-                                         GMPDecryptorProxy** aDecryptor)
+                                         UniquePtr<GetGMPDecryptorCallback>&& aCallback)
 {
 #if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
   if (!SandboxInfo::Get().CanSandboxMedia()) {
     NS_WARNING("GeckoMediaPluginService::GetGMPDecryptor: "
                "EME decryption not available without sandboxing support.");
     return NS_ERROR_NOT_AVAILABLE;
   }
 #endif
 
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
   NS_ENSURE_ARG(aTags && aTags->Length() > 0);
-  NS_ENSURE_ARG(aDecryptor);
+  NS_ENSURE_ARG(aCallback);
 
   if (mShuttingDownOnGMPThread) {
     return NS_ERROR_FAILURE;
   }
 
   nsRefPtr<GMPParent> gmp = SelectPluginForAPI(aNodeId,
                                                NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
                                                *aTags);
   if (!gmp) {
     return NS_ERROR_FAILURE;
   }
 
-  GMPDecryptorParent* ksp;
-  nsresult rv = gmp->GetGMPDecryptor(&ksp);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  *aDecryptor = static_cast<GMPDecryptorProxy*>(ksp);
+  GetGMPParentForDecryptorDone(Move(aCallback)).Done(gmp);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginService::HasPluginForAPI(const nsACString& aAPI,
                                          nsTArray<nsCString>* aTags,
                                          bool* aOutHavePlugin)
--- a/dom/media/gmp/GMPService.h
+++ b/dom/media/gmp/GMPService.h
@@ -39,28 +39,30 @@ public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
   // mozIGeckoMediaPluginService
   NS_IMETHOD GetThread(nsIThread** aThread) override;
   NS_IMETHOD HasPluginForAPI(const nsACString& aAPI, nsTArray<nsCString>* aTags,
                              bool *aRetVal) override;
   NS_IMETHOD GetGMPVideoDecoder(nsTArray<nsCString>* aTags,
                                 const nsACString& aNodeId,
-                                GMPVideoHost** aOutVideoHost,
-                                GMPVideoDecoderProxy** aGMPVD) override;
+                                UniquePtr<GetGMPVideoDecoderCallback>&& aCallback)
+    override;
   NS_IMETHOD GetGMPVideoEncoder(nsTArray<nsCString>* aTags,
                                 const nsACString& aNodeId,
-                                GMPVideoHost **aOutVideoHost,
-                                GMPVideoEncoderProxy** aGMPVE) override;
+                                UniquePtr<GetGMPVideoEncoderCallback>&& aCallback)
+    override;
   NS_IMETHOD GetGMPAudioDecoder(nsTArray<nsCString>* aTags,
                                 const nsACString& aNodeId,
-                                GMPAudioDecoderProxy **aGMPAD) override;
+                                UniquePtr<GetGMPAudioDecoderCallback>&& aCallback)
+    override;
   NS_IMETHOD GetGMPDecryptor(nsTArray<nsCString>* aTags,
                              const nsACString& aNodeId,
-                             GMPDecryptorProxy** aDecryptor) override;
+                             UniquePtr<GetGMPDecryptorCallback>&& aCallback)
+    override;
 
   int32_t AsyncShutdownTimeoutMs();
 
   class PluginCrashCallback
   {
   public:
     NS_INLINE_DECL_REFCOUNTING(PluginCrashCallback)
 
--- a/dom/media/gmp/mozIGeckoMediaPluginService.idl
+++ b/dom/media/gmp/mozIGeckoMediaPluginService.idl
@@ -2,33 +2,54 @@
 /* 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 "nsISupports.idl"
 #include "nsIThread.idl"
 
 %{C++
+#include "mozilla/UniquePtr.h"
 #include "nsTArray.h"
 #include "nsStringGlue.h"
 class GMPAudioDecoderProxy;
 class GMPDecryptorProxy;
 class GMPVideoDecoderProxy;
 class GMPVideoEncoderProxy;
 class GMPVideoHost;
+
+template<class T>
+class GMPGetterCallback
+{
+public:
+  GMPGetterCallback() { MOZ_COUNT_CTOR(GMPGetterCallback<T>); }
+  virtual ~GMPGetterCallback() { MOZ_COUNT_DTOR(GMPGetterCallback<T>); }
+  virtual void Done(T*) = 0;
+};
+template<class T>
+class GMPVideoGetterCallback
+{
+public:
+  GMPVideoGetterCallback() { MOZ_COUNT_CTOR(GMPVideoGetterCallback<T>); }
+  virtual ~GMPVideoGetterCallback() { MOZ_COUNT_DTOR(GMPVideoGetterCallback<T>); }
+  virtual void Done(T*, GMPVideoHost*) = 0;
+};
+typedef GMPGetterCallback<GMPDecryptorProxy> GetGMPDecryptorCallback;
+typedef GMPGetterCallback<GMPAudioDecoderProxy> GetGMPAudioDecoderCallback;
+typedef GMPVideoGetterCallback<GMPVideoDecoderProxy> GetGMPVideoDecoderCallback;
+typedef GMPVideoGetterCallback<GMPVideoEncoderProxy> GetGMPVideoEncoderCallback;
 %}
 
-[ptr] native GMPVideoDecoderProxy(GMPVideoDecoderProxy);
-[ptr] native GMPVideoEncoderProxy(GMPVideoEncoderProxy);
-[ptr] native GMPVideoHost(GMPVideoHost);
 [ptr] native TagArray(nsTArray<nsCString>);
-[ptr] native GMPDecryptorProxy(GMPDecryptorProxy);
-[ptr] native GMPAudioDecoderProxy(GMPAudioDecoderProxy);
+native GetGMPDecryptorCallback(mozilla::UniquePtr<GetGMPDecryptorCallback>&&);
+native GetGMPAudioDecoderCallback(mozilla::UniquePtr<GetGMPAudioDecoderCallback>&&);
+native GetGMPVideoDecoderCallback(mozilla::UniquePtr<GetGMPVideoDecoderCallback>&&);
+native GetGMPVideoEncoderCallback(mozilla::UniquePtr<GetGMPVideoEncoderCallback>&&);
 
-[scriptable, uuid(fed4d2d8-87d8-42fe-a141-98c15b5f7a1e)]
+[scriptable, uuid(11bb248b-7a4b-4f62-bebb-8211551d8c20)]
 interface mozIGeckoMediaPluginService : nsISupports
 {
 
   /**
    * The GMP thread. Callable from any thread.
    */
   readonly attribute nsIThread thread;
 
@@ -47,45 +68,73 @@ interface mozIGeckoMediaPluginService : 
   void getPluginVersionForAPI(in ACString api, in TagArray tags,
                               out boolean hasPlugin, out ACString version);
 
   /**
    * 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.
+   * This is an asynchronous operation, the Done method of the callback object
+   * will be called on the GMP thread with the result (which might be null in
+   * the case of failure). This method always takes ownership of the callback
+   * object, but if this method returns an error then the Done method of the
+   * callback object will not be called at all.
    */
   [noscript]
-  GMPVideoDecoderProxy getGMPVideoDecoder(in TagArray tags,
-                                          [optional] in ACString nodeId,
-                                          out GMPVideoHost outVideoHost);
+  void getGMPVideoDecoder(in TagArray tags,
+                          [optional] in ACString nodeId,
+                          in GetGMPVideoDecoderCallback callback);
 
   /**
    * 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.
+   * This is an asynchronous operation, the Done method of the callback object
+   * will be called on the GMP thread with the result (which might be null in
+   * the case of failure). This method always takes ownership of the callback
+   * object, but if this method returns an error then the Done method of the
+   * callback object will not be called at all.
    */
   [noscript]
-  GMPVideoEncoderProxy getGMPVideoEncoder(in TagArray tags,
-		                                      [optional] in ACString nodeId,
-		                                      out GMPVideoHost outVideoHost);
+  void getGMPVideoEncoder(in TagArray tags,
+		                      [optional] in ACString nodeId,
+		                      in GetGMPVideoEncoderCallback callback);
 
-  // Returns an audio 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.
-  GMPAudioDecoderProxy getGMPAudioDecoder(in TagArray tags,
-                                          [optional] in ACString nodeId);
+  /**
+   * Returns an audio 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.
+   * This is an asynchronous operation, the Done method of the callback object
+   * will be called on the GMP thread with the result (which might be null in
+   * the case of failure). This method always takes ownership of the callback
+   * object, but if this method returns an error then the Done method of the
+   * callback object will not be called at all.
+   */
+  [noscript]
+  void getGMPAudioDecoder(in TagArray tags,
+                          [optional] in ACString nodeId,
+                          in GetGMPAudioDecoderCallback callback);
 
-  // Returns a decryption session manager that supports the specified tags.
-  // The array of tags should at least contain a key system tag, and optionally
-  // other tags.
-  // Callable only on GMP thread.
-  GMPDecryptorProxy getGMPDecryptor(in TagArray tags, in ACString nodeId);
+  /**
+   * Returns a decryption session manager that supports the specified tags.
+   * The array of tags should at least contain a key system tag, and optionally
+   * other tags.
+   * Callable only on GMP thread.
+   * This is an asynchronous operation, the Done method of the callback object
+   * will be called on the GMP thread with the result (which might be null in
+   * the case of failure). This method always takes ownership of the callback
+   * object, but if this method returns an error then the Done method of the
+   * callback object will not be called at all.
+   */
+  [noscript]
+  void getGMPDecryptor(in TagArray tags, in ACString nodeId,
+                       in GetGMPDecryptorCallback callback);
 
   /**
    * Gets the NodeId for a (origin, urlbarOrigin, isInprivateBrowsing) tuple.
    */
   ACString getNodeId(in AString origin,
                      in AString topLevelOrigin,
                      in bool inPrivateBrowsingMode);
 };
--- a/dom/media/gtest/TestGMPCrossOrigin.cpp
+++ b/dom/media/gtest/TestGMPCrossOrigin.cpp
@@ -23,97 +23,244 @@
 #include "mozilla/WindowsVersion.h"
 #endif
 
 using namespace std;
 
 using namespace mozilla;
 using namespace mozilla::gmp;
 
+class GMPTestMonitor
+{
+public:
+  GMPTestMonitor()
+    : mFinished(false)
+  {
+  }
+
+  void AwaitFinished()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    while (!mFinished) {
+      NS_ProcessNextEvent(nullptr, true);
+    }
+    mFinished = false;
+  }
+
+private:
+  void MarkFinished()
+  {
+    mFinished = true;
+  }
+
+public:
+  void SetFinished()
+  {
+    NS_DispatchToMainThread(NS_NewNonOwningRunnableMethod(this,
+                                                          &GMPTestMonitor::MarkFinished));
+  }
+
+private:
+  bool mFinished;
+};
+
 struct GMPTestRunner
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPTestRunner)
 
-  void DoTest(void (GMPTestRunner::*aTestMethod)());
-  void RunTestGMPTestCodec();
-  void RunTestGMPCrossOrigin();
+  void DoTest(void (GMPTestRunner::*aTestMethod)(GMPTestMonitor&));
+  void RunTestGMPTestCodec1(GMPTestMonitor& aMonitor);
+  void RunTestGMPTestCodec2(GMPTestMonitor& aMonitor);
+  void RunTestGMPTestCodec3(GMPTestMonitor& aMonitor);
+  void RunTestGMPCrossOrigin1(GMPTestMonitor& aMonitor);
+  void RunTestGMPCrossOrigin2(GMPTestMonitor& aMonitor);
+  void RunTestGMPCrossOrigin3(GMPTestMonitor& aMonitor);
+  void RunTestGMPCrossOrigin4(GMPTestMonitor& aMonitor);
 
 private:
   ~GMPTestRunner() { }
 };
 
-void
-GMPTestRunner::RunTestGMPTestCodec()
+template<class T, class Base,
+         nsresult (NS_STDCALL GeckoMediaPluginService::*Getter)(nsTArray<nsCString>*,
+                                                                const nsACString&,
+                                                                UniquePtr<Base>&&)>
+class RunTestGMPVideoCodec : public Base
 {
-  nsRefPtr<GeckoMediaPluginService> service =
-    GeckoMediaPluginService::GetGeckoMediaPluginService();
+public:
+  virtual void Done(T* aGMP, GMPVideoHost* aHost)
+  {
+    EXPECT_TRUE(aGMP);
+    EXPECT_TRUE(aHost);
+    if (aGMP) {
+      aGMP->Close();
+    }
+    mMonitor.SetFinished();
+  }
 
-  GMPVideoHost* host = nullptr;
-  GMPVideoDecoderProxy* decoder = nullptr;
-  GMPVideoDecoderProxy* decoder2 = nullptr;
-  GMPVideoEncoderProxy* encoder = nullptr;
+  static void Run(GMPTestMonitor& aMonitor, const nsCString& aOrigin)
+  {
+    UniquePtr<GMPCallbackType> callback(new RunTestGMPVideoCodec(aMonitor));
+    Get(aOrigin, Move(callback));
+  }
 
-  nsTArray<nsCString> tags;
-  tags.AppendElement(NS_LITERAL_CSTRING("h264"));
+protected:
+  typedef T GMPCodecType;
+  typedef Base GMPCallbackType;
+
+  explicit RunTestGMPVideoCodec(GMPTestMonitor& aMonitor)
+    : mMonitor(aMonitor)
+  {
+  }
 
-  service->GetGMPVideoDecoder(&tags, NS_LITERAL_CSTRING("o"), &host, &decoder2);
-  service->GetGMPVideoDecoder(&tags, NS_LITERAL_CSTRING(""), &host, &decoder);
+  static nsresult Get(const nsACString& aNodeId, UniquePtr<Base>&& aCallback)
+  {
+    nsTArray<nsCString> tags;
+    tags.AppendElement(NS_LITERAL_CSTRING("h264"));
 
-  service->GetGMPVideoEncoder(&tags, NS_LITERAL_CSTRING(""), &host, &encoder);
+    nsRefPtr<GeckoMediaPluginService> service =
+      GeckoMediaPluginService::GetGeckoMediaPluginService();
+    return ((*service).*Getter)(&tags, aNodeId, Move(aCallback));
+  }
+
+protected:
+  GMPTestMonitor& mMonitor;
+};
 
-  EXPECT_TRUE(host);
-  EXPECT_TRUE(decoder);
-  EXPECT_TRUE(decoder2);
-  EXPECT_TRUE(encoder);
+typedef RunTestGMPVideoCodec<GMPVideoDecoderProxy,
+                             GetGMPVideoDecoderCallback,
+                             &GeckoMediaPluginService::GetGMPVideoDecoder>
+  RunTestGMPVideoDecoder;
+typedef RunTestGMPVideoCodec<GMPVideoEncoderProxy,
+                             GetGMPVideoEncoderCallback,
+                             &GeckoMediaPluginService::GetGMPVideoEncoder>
+  RunTestGMPVideoEncoder;
 
-  if (decoder) decoder->Close();
-  if (decoder2) decoder2->Close();
-  if (encoder) encoder->Close();
+void
+GMPTestRunner::RunTestGMPTestCodec1(GMPTestMonitor& aMonitor)
+{
+  RunTestGMPVideoDecoder::Run(aMonitor, NS_LITERAL_CSTRING("o"));
+}
+
+void
+GMPTestRunner::RunTestGMPTestCodec2(GMPTestMonitor& aMonitor)
+{
+  RunTestGMPVideoDecoder::Run(aMonitor, NS_LITERAL_CSTRING(""));
 }
 
 void
-GMPTestRunner::RunTestGMPCrossOrigin()
+GMPTestRunner::RunTestGMPTestCodec3(GMPTestMonitor& aMonitor)
+{
+  RunTestGMPVideoEncoder::Run(aMonitor, NS_LITERAL_CSTRING(""));
+}
+
+template<class Base>
+class RunTestGMPCrossOrigin : public Base
 {
-  nsRefPtr<GeckoMediaPluginService> service =
-    GeckoMediaPluginService::GetGeckoMediaPluginService();
+public:
+  virtual void Done(typename Base::GMPCodecType* aGMP, GMPVideoHost* aHost)
+  {
+    EXPECT_TRUE(aGMP);
 
-  GMPVideoHost* host = nullptr;
-  nsTArray<nsCString> tags;
-  tags.AppendElement(NS_LITERAL_CSTRING("h264"));
+    UniquePtr<typename Base::GMPCallbackType> callback(
+      new Step2(Base::mMonitor, aGMP, mShouldBeEqual));
+    nsresult rv = Base::Get(mOrigin2, Move(callback));
+    EXPECT_TRUE(NS_SUCCEEDED(rv));
+    if (NS_FAILED(rv)) {
+      Base::mMonitor.SetFinished();
+    }
+  }
 
-  GMPVideoDecoderProxy* decoder1 = nullptr;
-  GMPVideoDecoderProxy* decoder2 = nullptr;
-  GMPVideoEncoderProxy* encoder1 = nullptr;
-  GMPVideoEncoderProxy* encoder2 = nullptr;
+  static void Run(GMPTestMonitor& aMonitor, const nsCString& aOrigin1,
+                  const nsCString& aOrigin2)
+  {
+    UniquePtr<typename Base::GMPCallbackType> callback(
+      new RunTestGMPCrossOrigin<Base>(aMonitor, aOrigin1, aOrigin2));
+    nsresult rv = Base::Get(aOrigin1, Move(callback));
+    EXPECT_TRUE(NS_SUCCEEDED(rv));
+    if (NS_FAILED(rv)) {
+      aMonitor.SetFinished();
+    }
+  }
 
-  service->GetGMPVideoDecoder(&tags, NS_LITERAL_CSTRING("origin1"), &host, &decoder1);
-  service->GetGMPVideoDecoder(&tags, NS_LITERAL_CSTRING("origin2"), &host, &decoder2);
-  EXPECT_TRUE(!!decoder1 && !!decoder2 &&
-              decoder1->ParentID() != decoder2->ParentID());
+private:
+  RunTestGMPCrossOrigin(GMPTestMonitor& aMonitor, const nsCString& aOrigin1,
+                        const nsCString& aOrigin2)
+    : Base(aMonitor),
+      mGMP(nullptr),
+      mOrigin2(aOrigin2),
+      mShouldBeEqual(aOrigin1.Equals(aOrigin2))
+  {
+  }
 
-  service->GetGMPVideoEncoder(&tags, NS_LITERAL_CSTRING("origin1"), &host, &encoder1);
-  service->GetGMPVideoEncoder(&tags, NS_LITERAL_CSTRING("origin2"), &host, &encoder2);
-  EXPECT_TRUE(!!encoder1 && !!encoder2 &&
-              encoder1->ParentID() != encoder2->ParentID());
+  class Step2 : public Base
+  {
+  public:
+    Step2(GMPTestMonitor& aMonitor,
+          typename Base::GMPCodecType* aGMP,
+          bool aShouldBeEqual)
+      : Base(aMonitor),
+        mGMP(aGMP),
+        mShouldBeEqual(aShouldBeEqual)
+    {
+    }
+    virtual void Done(typename Base::GMPCodecType* aGMP, GMPVideoHost* aHost)
+    {
+      EXPECT_TRUE(aGMP);
+      if (aGMP) {
+        EXPECT_TRUE(mGMP &&
+                    (mGMP->ParentID() == aGMP->ParentID()) == mShouldBeEqual);
+      }
+      if (mGMP) {
+        mGMP->Close();
+      }
+      Base::Done(aGMP, aHost);
+    }
 
-  if (decoder2) decoder2->Close();
-  if (encoder2) encoder2->Close();
+  private:
+    typename Base::GMPCodecType* mGMP;
+    bool mShouldBeEqual;
+  };
 
-  service->GetGMPVideoDecoder(&tags, NS_LITERAL_CSTRING("origin1"), &host, &decoder2);
-  EXPECT_TRUE(!!decoder1 && !!decoder2 &&
-              decoder1->ParentID() == decoder2->ParentID());
+  typename Base::GMPCodecType* mGMP;
+  nsCString mOrigin2;
+  bool mShouldBeEqual;
+};
+
+typedef RunTestGMPCrossOrigin<RunTestGMPVideoDecoder>
+  RunTestGMPVideoDecoderCrossOrigin;
+typedef RunTestGMPCrossOrigin<RunTestGMPVideoEncoder>
+  RunTestGMPVideoEncoderCrossOrigin;
+
+void
+GMPTestRunner::RunTestGMPCrossOrigin1(GMPTestMonitor& aMonitor)
+{
+  RunTestGMPVideoDecoderCrossOrigin::Run(
+    aMonitor, NS_LITERAL_CSTRING("origin1"), NS_LITERAL_CSTRING("origin2"));
+}
 
-  service->GetGMPVideoEncoder(&tags, NS_LITERAL_CSTRING("origin1"), &host, &encoder2);
-  EXPECT_TRUE(!!encoder1 && !!encoder2 &&
-              encoder1->ParentID() == encoder2->ParentID());
+void
+GMPTestRunner::RunTestGMPCrossOrigin2(GMPTestMonitor& aMonitor)
+{
+  RunTestGMPVideoEncoderCrossOrigin::Run(
+    aMonitor, NS_LITERAL_CSTRING("origin1"), NS_LITERAL_CSTRING("origin2"));
+}
 
-  if (decoder1) decoder1->Close();
-  if (decoder2) decoder2->Close();
-  if (encoder1) encoder1->Close();
-  if (encoder2) encoder2->Close();
+void
+GMPTestRunner::RunTestGMPCrossOrigin3(GMPTestMonitor& aMonitor)
+{
+  RunTestGMPVideoDecoderCrossOrigin::Run(
+    aMonitor, NS_LITERAL_CSTRING("origin1"), NS_LITERAL_CSTRING("origin1"));
+}
+
+void
+GMPTestRunner::RunTestGMPCrossOrigin4(GMPTestMonitor& aMonitor)
+{
+  RunTestGMPVideoEncoderCrossOrigin::Run(
+    aMonitor, NS_LITERAL_CSTRING("origin1"), NS_LITERAL_CSTRING("origin1"));
 }
 
 static already_AddRefed<nsIThread>
 GetGMPThread()
 {
   nsRefPtr<GeckoMediaPluginService> service =
     GeckoMediaPluginService::GetGeckoMediaPluginService();
   nsCOMPtr<nsIThread> thread;
@@ -433,90 +580,152 @@ class GMPStorageTest : public GMPDecrypt
     const nsString origin1 = NS_LITERAL_STRING("example1.com");
     const nsString origin2 = NS_LITERAL_STRING("example2.org");
     nsCString nodeId3 = GetNodeId(origin1, origin2, false);
     EXPECT_TRUE(!aNodeId1.Equals(nodeId3));
 
     SetFinished();
   }
 
+  class CreateDecryptorDone : public GetGMPDecryptorCallback
+  {
+  public:
+    CreateDecryptorDone(GMPStorageTest* aRunner, nsIRunnable* aContinuation)
+      : mRunner(aRunner),
+        mContinuation(aContinuation)
+    {
+    }
+
+    virtual void Done(GMPDecryptorProxy* aDecryptor) override
+    {
+      mRunner->mDecryptor = aDecryptor;
+      EXPECT_TRUE(!!mRunner->mDecryptor);
+
+      if (mRunner->mDecryptor) {
+        mRunner->mDecryptor->Init(mRunner);
+      }
+      nsCOMPtr<nsIThread> thread(GetGMPThread());
+      thread->Dispatch(mContinuation, NS_DISPATCH_NORMAL);
+    }
+
+  private:
+    nsRefPtr<GMPStorageTest> mRunner;
+    nsCOMPtr<nsIRunnable> mContinuation;
+  };
+
   void CreateDecryptor(const nsAString& aOrigin,
                        const nsAString& aTopLevelOrigin,
-                       bool aInPBMode) {
+                       bool aInPBMode,
+                       const nsCString& aUpdate)
+  {
+    nsTArray<nsCString> updates;
+    updates.AppendElement(aUpdate);
+    CreateDecryptor(aOrigin, aTopLevelOrigin, aInPBMode, Move(updates));
+  }
+  class Updates : public nsRunnable
+  {
+  public:
+    Updates(GMPStorageTest* aRunner, nsTArray<nsCString>&& aUpdates)
+      : mRunner(aRunner),
+        mUpdates(aUpdates)
+    {
+    }
+
+    NS_IMETHOD Run()
+    {
+      for (auto& update : mUpdates) {
+        mRunner->Update(update);
+      }
+      return NS_OK;
+    }
+
+  private:
+    nsRefPtr<GMPStorageTest> mRunner;
+    nsTArray<nsCString> mUpdates;
+  };
+  void CreateDecryptor(const nsAString& aOrigin,
+                       const nsAString& aTopLevelOrigin,
+                       bool aInPBMode,
+                       nsTArray<nsCString>&& aUpdates) {
+    nsCOMPtr<nsIRunnable> updates(new Updates(this, Move(aUpdates)));
+    CreateDecryptor(aOrigin, aTopLevelOrigin, aInPBMode, updates);
+  }
+  void CreateDecryptor(const nsAString& aOrigin,
+                       const nsAString& aTopLevelOrigin,
+                       bool aInPBMode,
+                       nsIRunnable* aContinuation) {
     nsRefPtr<GeckoMediaPluginService> service =
       GeckoMediaPluginService::GetGeckoMediaPluginService();
     EXPECT_TRUE(service);
 
     mNodeId = GetNodeId(aOrigin, aTopLevelOrigin, aInPBMode);
     EXPECT_TRUE(!mNodeId.IsEmpty());
 
     nsTArray<nsCString> tags;
     tags.AppendElement(NS_LITERAL_CSTRING("fake"));
 
-    nsresult rv = service->GetGMPDecryptor(&tags, mNodeId, &mDecryptor);
+    UniquePtr<GetGMPDecryptorCallback> callback(
+      new CreateDecryptorDone(this, aContinuation));
+    nsresult rv =
+      service->GetGMPDecryptor(&tags, mNodeId, Move(callback));
     EXPECT_TRUE(NS_SUCCEEDED(rv));
-    EXPECT_TRUE(!!mDecryptor);
-
-    if (mDecryptor) {
-      mDecryptor->Init(this);
-    }
   }
 
   void TestBasicStorage() {
     AssertIsOnGMPThread();
     EXPECT_TRUE(IsGMPStorageIsEmpty());
 
     nsRefPtr<GeckoMediaPluginService> service =
       GeckoMediaPluginService::GetGeckoMediaPluginService();
 
-    CreateDecryptor(NS_LITERAL_STRING("example1.com"),
-                    NS_LITERAL_STRING("example2.com"),
-                    false);
-
     // Send a message to the fake GMP for it to run its own tests internally.
     // It sends us a "test-storage complete" message when its passed, or
     // some other message if its tests fail.
     Expect(NS_LITERAL_CSTRING("test-storage complete"),
            NS_NewRunnableMethod(this, &GMPStorageTest::SetFinished));
-    Update(NS_LITERAL_CSTRING("test-storage"));
+
+    CreateDecryptor(NS_LITERAL_STRING("example1.com"),
+                    NS_LITERAL_STRING("example2.com"),
+                    false,
+                    NS_LITERAL_CSTRING("test-storage"));
   }
 
   /**
    * 1. Generate storage data for some sites.
    * 2. Forget about one of the sites.
    * 3. Check if the storage data for the forgotten site are erased correctly.
    * 4. Check if the storage data for other sites remain unchanged.
    */
   void TestForgetThisSite() {
     AssertIsOnGMPThread();
     EXPECT_TRUE(IsGMPStorageIsEmpty());
 
     // Generate storage data for some site.
-    CreateDecryptor(NS_LITERAL_STRING("example1.com"),
-                    NS_LITERAL_STRING("example2.com"),
-                    false);
-
     nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(
         this, &GMPStorageTest::TestForgetThisSite_AnotherSite);
     Expect(NS_LITERAL_CSTRING("test-storage complete"), r);
-    Update(NS_LITERAL_CSTRING("test-storage"));
+
+    CreateDecryptor(NS_LITERAL_STRING("example1.com"),
+                    NS_LITERAL_STRING("example2.com"),
+                    false,
+                    NS_LITERAL_CSTRING("test-storage"));
   }
 
   void TestForgetThisSite_AnotherSite() {
     Shutdown();
 
     // Generate storage data for another site.
-    CreateDecryptor(NS_LITERAL_STRING("example3.com"),
-                    NS_LITERAL_STRING("example4.com"),
-                    false);
-
     nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(
         this, &GMPStorageTest::TestForgetThisSite_CollectSiteInfo);
     Expect(NS_LITERAL_CSTRING("test-storage complete"), r);
-    Update(NS_LITERAL_CSTRING("test-storage"));
+
+    CreateDecryptor(NS_LITERAL_STRING("example3.com"),
+                    NS_LITERAL_STRING("example4.com"),
+                    false,
+                    NS_LITERAL_CSTRING("test-storage"));
   }
 
   struct NodeInfo {
     explicit NodeInfo(const nsACString& aSite) : siteToForget(aSite) {}
     nsCString siteToForget;
     nsTArray<nsCString> expectedRemainingNodeIds;
   };
 
@@ -618,71 +827,68 @@ class GMPStorageTest : public GMPDecrypt
    * 4. Check if all directories in $profileDir/gmp/id/ and
    *    $profileDir/gmp/storage are removed.
    */
   void TestClearRecentHistory1() {
     AssertIsOnGMPThread();
     EXPECT_TRUE(IsGMPStorageIsEmpty());
 
     // Generate storage data for some site.
-    CreateDecryptor(NS_LITERAL_STRING("example1.com"),
-                    NS_LITERAL_STRING("example2.com"),
-                    false);
-
     nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(
         this, &GMPStorageTest::TestClearRecentHistory1_Clear);
     Expect(NS_LITERAL_CSTRING("test-storage complete"), r);
-    Update(NS_LITERAL_CSTRING("test-storage"));
 
-  }
+    CreateDecryptor(NS_LITERAL_STRING("example1.com"),
+                    NS_LITERAL_STRING("example2.com"),
+                    false,
+                    NS_LITERAL_CSTRING("test-storage"));
+}
 
   /**
    * 1. Generate some storage data.
    * 2. Find the max mtime |t| in $profileDir/gmp/storage/.
    * 3. Pass |t| to clear recent history.
    * 4. Check if all directories in $profileDir/gmp/id/ and
    *    $profileDir/gmp/storage are removed.
    */
   void TestClearRecentHistory2() {
     AssertIsOnGMPThread();
     EXPECT_TRUE(IsGMPStorageIsEmpty());
 
     // Generate storage data for some site.
-    CreateDecryptor(NS_LITERAL_STRING("example1.com"),
-                    NS_LITERAL_STRING("example2.com"),
-                    false);
-
     nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(
         this, &GMPStorageTest::TestClearRecentHistory2_Clear);
     Expect(NS_LITERAL_CSTRING("test-storage complete"), r);
-    Update(NS_LITERAL_CSTRING("test-storage"));
 
+    CreateDecryptor(NS_LITERAL_STRING("example1.com"),
+                    NS_LITERAL_STRING("example2.com"),
+                    false,
+                    NS_LITERAL_CSTRING("test-storage"));
   }
 
   /**
    * 1. Generate some storage data.
    * 2. Find the max mtime |t| in $profileDir/gmp/storage/.
    * 3. Pass |t+1| to clear recent history.
    * 4. Check if all directories in $profileDir/gmp/id/ and
    *    $profileDir/gmp/storage remain unchanged.
    */
   void TestClearRecentHistory3() {
     AssertIsOnGMPThread();
     EXPECT_TRUE(IsGMPStorageIsEmpty());
 
     // Generate storage data for some site.
-    CreateDecryptor(NS_LITERAL_STRING("example1.com"),
-                    NS_LITERAL_STRING("example2.com"),
-                    false);
-
     nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(
         this, &GMPStorageTest::TestClearRecentHistory3_Clear);
     Expect(NS_LITERAL_CSTRING("test-storage complete"), r);
-    Update(NS_LITERAL_CSTRING("test-storage"));
 
+    CreateDecryptor(NS_LITERAL_STRING("example1.com"),
+                    NS_LITERAL_STRING("example2.com"),
+                    false,
+                    NS_LITERAL_CSTRING("test-storage"));
   }
 
   class MaxMTimeFinder {
   public:
     MaxMTimeFinder() : mMaxTime(0) {}
     void operator()(nsIFile* aFile) {
       PRTime lastModified;
       nsresult rv = aFile->GetLastModifiedTime(&lastModified);
@@ -770,228 +976,251 @@ class GMPStorageTest : public GMPDecrypt
     EXPECT_EQ(c2.GetCount(), 1);
 
     SetFinished();
   }
 
   void TestCrossOriginStorage() {
     EXPECT_TRUE(!mDecryptor);
 
-    // Open decryptor on one, origin, write a record, and test that that
-    // record can't be read on another origin.
-    CreateDecryptor(NS_LITERAL_STRING("example3.com"),
-                    NS_LITERAL_STRING("example4.com"),
-                    false);
-
     // Send the decryptor the message "store recordid $time"
     // Wait for the decrytor to send us "stored recordid $time"
     auto t = time(0);
     nsCString response("stored crossOriginTestRecordId ");
     response.AppendInt((int64_t)t);
     Expect(response, NS_NewRunnableMethod(this,
       &GMPStorageTest::TestCrossOriginStorage_RecordStoredContinuation));
 
     nsCString update("store crossOriginTestRecordId ");
     update.AppendInt((int64_t)t);
-    Update(update);
+
+    // Open decryptor on one, origin, write a record, and test that that
+    // record can't be read on another origin.
+    CreateDecryptor(NS_LITERAL_STRING("example3.com"),
+                    NS_LITERAL_STRING("example4.com"),
+                    false,
+                    update);
   }
 
   void TestCrossOriginStorage_RecordStoredContinuation() {
     // Close the old decryptor, and create a new one on a different origin,
     // and try to read the record.
     Shutdown();
 
+    Expect(NS_LITERAL_CSTRING("retrieve crossOriginTestRecordId succeeded (length 0 bytes)"),
+           NS_NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+
     CreateDecryptor(NS_LITERAL_STRING("example5.com"),
                     NS_LITERAL_STRING("example6.com"),
-                    false);
-
-    Expect(NS_LITERAL_CSTRING("retrieve crossOriginTestRecordId succeeded (length 0 bytes)"),
-           NS_NewRunnableMethod(this, &GMPStorageTest::SetFinished));
-    Update(NS_LITERAL_CSTRING("retrieve crossOriginTestRecordId"));
+                    false,
+                    NS_LITERAL_CSTRING("retrieve crossOriginTestRecordId"));
   }
 
   void TestPBStorage() {
-    // Open decryptor on one, origin, write a record, close decryptor,
-    // open another, and test that record can be read, close decryptor,
-    // then send pb-last-context-closed notification, then open decryptor
-    // and check that it can't read that data; it should have been purged.
-    CreateDecryptor(NS_LITERAL_STRING("pb1.com"),
-                    NS_LITERAL_STRING("pb2.com"),
-                    true);
-
     // Send the decryptor the message "store recordid $time"
     // Wait for the decrytor to send us "stored recordid $time"
     nsCString response("stored pbdata test-pb-data");
     Expect(response, NS_NewRunnableMethod(this,
       &GMPStorageTest::TestPBStorage_RecordStoredContinuation));
 
-    nsCString update("store pbdata test-pb-data");
-    Update(update);
+    // Open decryptor on one, origin, write a record, close decryptor,
+    // open another, and test that record can be read, close decryptor,
+    // then send pb-last-context-closed notification, then open decryptor
+    // and check that it can't read that data; it should have been purged.
+    CreateDecryptor(NS_LITERAL_STRING("pb1.com"),
+                    NS_LITERAL_STRING("pb2.com"),
+                    true,
+                    NS_LITERAL_CSTRING("store pbdata test-pb-data"));
   }
 
   void TestPBStorage_RecordStoredContinuation() {
     Shutdown();
 
-    CreateDecryptor(NS_LITERAL_STRING("pb1.com"),
-                    NS_LITERAL_STRING("pb2.com"),
-                    true);
-
     Expect(NS_LITERAL_CSTRING("retrieve pbdata succeeded (length 12 bytes)"),
            NS_NewRunnableMethod(this,
               &GMPStorageTest::TestPBStorage_RecordRetrievedContinuation));
-    Update(NS_LITERAL_CSTRING("retrieve pbdata"));
+
+    CreateDecryptor(NS_LITERAL_STRING("pb1.com"),
+                    NS_LITERAL_STRING("pb2.com"),
+                    true,
+                    NS_LITERAL_CSTRING("retrieve pbdata"));
   }
 
   void TestPBStorage_RecordRetrievedContinuation() {
     Shutdown();
     SimulatePBModeExit();
 
-    CreateDecryptor(NS_LITERAL_STRING("pb1.com"),
-                    NS_LITERAL_STRING("pb2.com"),
-                    true);
-
     Expect(NS_LITERAL_CSTRING("retrieve pbdata succeeded (length 0 bytes)"),
            NS_NewRunnableMethod(this,
               &GMPStorageTest::SetFinished));
-    Update(NS_LITERAL_CSTRING("retrieve pbdata"));
+
+    CreateDecryptor(NS_LITERAL_STRING("pb1.com"),
+                    NS_LITERAL_STRING("pb2.com"),
+                    true,
+                    NS_LITERAL_CSTRING("retrieve pbdata"));
+  }
+
+  void NextAsyncShutdownTimeoutTest(nsIRunnable* aContinuation)
+  {
+    if (mDecryptor) {
+      Update(NS_LITERAL_CSTRING("shutdown-mode timeout"));
+      Shutdown();
+    }
+    nsCOMPtr<nsIThread> thread(GetGMPThread());
+    thread->Dispatch(aContinuation, NS_DISPATCH_NORMAL);
   }
 
   void CreateAsyncShutdownTimeoutGMP(const nsAString& aOrigin1,
-                                     const nsAString& aOrigin2) {
-    CreateDecryptor(aOrigin1, aOrigin2, false);
-    Update(NS_LITERAL_CSTRING("shutdown-mode timeout"));
-    Shutdown();
+                                     const nsAString& aOrigin2,
+                                     void (GMPStorageTest::*aCallback)()) {
+    nsCOMPtr<nsIRunnable> continuation(
+      NS_NewRunnableMethodWithArg<nsCOMPtr<nsIRunnable>>(
+        this,
+        &GMPStorageTest::NextAsyncShutdownTimeoutTest,
+        NS_NewRunnableMethod(this, aCallback)));
+
+    CreateDecryptor(aOrigin1, aOrigin2, false, continuation);
   }
 
   void TestAsyncShutdownTimeout() {
     // Create decryptors that timeout in their async shutdown.
     // If the gtest hangs on shutdown, test fails!
     CreateAsyncShutdownTimeoutGMP(NS_LITERAL_STRING("example7.com"),
-                                  NS_LITERAL_STRING("example8.com"));
+                                  NS_LITERAL_STRING("example8.com"),
+                                  &GMPStorageTest::TestAsyncShutdownTimeout2);
+  };
+
+  void TestAsyncShutdownTimeout2() {
     CreateAsyncShutdownTimeoutGMP(NS_LITERAL_STRING("example9.com"),
-                                  NS_LITERAL_STRING("example10.com"));
+                                  NS_LITERAL_STRING("example10.com"),
+                                  &GMPStorageTest::TestAsyncShutdownTimeout3);
+  };
+
+  void TestAsyncShutdownTimeout3() {
     CreateAsyncShutdownTimeoutGMP(NS_LITERAL_STRING("example11.com"),
-                                  NS_LITERAL_STRING("example12.com"));
-    SetFinished();
+                                  NS_LITERAL_STRING("example12.com"),
+                                  &GMPStorageTest::SetFinished);
   };
 
   void TestAsyncShutdownStorage() {
-    // Test that a GMP can write to storage during shutdown, and retrieve
-    // that written data in a subsequent session.
-    CreateDecryptor(NS_LITERAL_STRING("example13.com"),
-                    NS_LITERAL_STRING("example14.com"),
-                    false);
-
     // Instruct the GMP to write a token (the current timestamp, so it's
     // unique) during async shutdown, then shutdown the plugin, re-create
     // it, and check that the token was successfully stored.
     auto t = time(0);
     nsCString update("shutdown-mode token ");
     nsCString token;
     token.AppendInt((int64_t)t);
     update.Append(token);
 
     // Wait for a response from the GMP, so we know it's had time to receive
     // the token.
     nsCString response("shutdown-token received ");
     response.Append(token);
     Expect(response, NS_NewRunnableMethodWithArg<nsCString>(this,
       &GMPStorageTest::TestAsyncShutdownStorage_ReceivedShutdownToken, token));
 
-    Update(update);
+    // Test that a GMP can write to storage during shutdown, and retrieve
+    // that written data in a subsequent session.
+    CreateDecryptor(NS_LITERAL_STRING("example13.com"),
+                    NS_LITERAL_STRING("example14.com"),
+                    false,
+                    update);
   }
 
   void TestAsyncShutdownStorage_ReceivedShutdownToken(const nsCString& aToken) {
     ShutdownThen(NS_NewRunnableMethodWithArg<nsCString>(this,
       &GMPStorageTest::TestAsyncShutdownStorage_AsyncShutdownComplete, aToken));
   }
 
   void TestAsyncShutdownStorage_AsyncShutdownComplete(const nsCString& aToken) {
     // Create a new instance of the plugin, retrieve the token written
     // during shutdown and verify it is correct.
-    CreateDecryptor(NS_LITERAL_STRING("example13.com"),
-                    NS_LITERAL_STRING("example14.com"),
-                    false);
     nsCString response("retrieved shutdown-token ");
     response.Append(aToken);
     Expect(response,
            NS_NewRunnableMethod(this, &GMPStorageTest::SetFinished));
-    Update(NS_LITERAL_CSTRING("retrieve-shutdown-token"));
+
+    CreateDecryptor(NS_LITERAL_STRING("example13.com"),
+                    NS_LITERAL_STRING("example14.com"),
+                    false,
+                    NS_LITERAL_CSTRING("retrieve-shutdown-token"));
   }
 
 #if defined(XP_WIN)
   void TestOutputProtection() {
     Shutdown();
 
+    Expect(NS_LITERAL_CSTRING("OP tests completed"),
+           NS_NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+
     CreateDecryptor(NS_LITERAL_STRING("example15.com"),
                     NS_LITERAL_STRING("example16.com"),
-                    false);
-
-    Expect(NS_LITERAL_CSTRING("OP tests completed"),
-           NS_NewRunnableMethod(this, &GMPStorageTest::SetFinished));
-    Update(NS_LITERAL_CSTRING("test-op-apis"));
+                    false,
+                    NS_LITERAL_CSTRING("test-op-apis"));
   }
 #endif
 
   void TestPluginVoucher() {
+    Expect(NS_LITERAL_CSTRING("retrieved plugin-voucher: gmp-fake placeholder voucher"),
+           NS_NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+
     CreateDecryptor(NS_LITERAL_STRING("example17.com"),
                     NS_LITERAL_STRING("example18.com"),
-                    false);
-    Expect(NS_LITERAL_CSTRING("retrieved plugin-voucher: gmp-fake placeholder voucher"),
-           NS_NewRunnableMethod(this, &GMPStorageTest::SetFinished));
-    Update(NS_LITERAL_CSTRING("retrieve-plugin-voucher"));
+                    false,
+                    NS_LITERAL_CSTRING("retrieve-plugin-voucher"));
   }
 
   void TestGetRecordNamesInMemoryStorage() {
     TestGetRecordNames(true);
   }
 
   nsCString mRecordNames;
 
   void AppendIntPadded(nsACString& aString, uint32_t aInt) {
     if (aInt > 0 && aInt < 10) {
       aString.AppendLiteral("0");
     }
     aString.AppendInt(aInt);
   }
 
   void TestGetRecordNames(bool aPrivateBrowsing) {
-    CreateDecryptor(NS_LITERAL_STRING("foo.com"),
-                    NS_LITERAL_STRING("bar.com"),
-                    aPrivateBrowsing);
-
     // Create a number of records of different names.
     const uint32_t num = 100;
+    nsTArray<nsCString> updates(num);
     for (uint32_t i = 0; i < num; i++) {
       nsAutoCString response;
       response.AppendLiteral("stored data");
       AppendIntPadded(response, i);
       response.AppendLiteral(" test-data");
       AppendIntPadded(response, i);
 
       if (i != 0) {
         mRecordNames.AppendLiteral(",");
       }
       mRecordNames.AppendLiteral("data");
       AppendIntPadded(mRecordNames, i);
 
-      nsAutoCString update;
+      nsCString& update = *updates.AppendElement();
       update.AppendLiteral("store data");
       AppendIntPadded(update, i);
       update.AppendLiteral(" test-data");
       AppendIntPadded(update, i);
 
       nsIRunnable* continuation = nullptr;
       if (i + 1 == num) {
         continuation =
           NS_NewRunnableMethod(this, &GMPStorageTest::TestGetRecordNames_QueryNames);
       }
       Expect(response, continuation);
-      Update(update);
     }
+
+    CreateDecryptor(NS_LITERAL_STRING("foo.com"),
+                    NS_LITERAL_STRING("bar.com"),
+                    aPrivateBrowsing,
+                    Move(updates));
   }
 
   void TestGetRecordNames_QueryNames() {
     nsCString response("record-names ");
     response.Append(mRecordNames);
     Expect(response,
            NS_NewRunnableMethod(this, &GMPStorageTest::SetFinished));
     Update(NS_LITERAL_CSTRING("retrieve-record-names"));
@@ -1021,31 +1250,30 @@ class GMPStorageTest : public GMPDecrypt
       "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
       "long_record_name");
 
     NS_NAMED_LITERAL_CSTRING(data, "Just_some_arbitrary_data.");
 
     MOZ_ASSERT(longRecordName.Length() < GMP_MAX_RECORD_NAME_SIZE);
     MOZ_ASSERT(longRecordName.Length() > 260); // Windows MAX_PATH
 
-    CreateDecryptor(NS_LITERAL_STRING("fuz.com"),
-                    NS_LITERAL_STRING("baz.com"),
-                    false);
-
     nsCString response("stored ");
     response.Append(longRecordName);
     response.AppendLiteral(" ");
     response.Append(data);
     Expect(response, NS_NewRunnableMethod(this, &GMPStorageTest::SetFinished));
 
     nsCString update("store ");
     update.Append(longRecordName);
     update.AppendLiteral(" ");
     update.Append(data);
-    Update(update);
+    CreateDecryptor(NS_LITERAL_STRING("fuz.com"),
+                    NS_LITERAL_STRING("baz.com"),
+                    false,
+                    update);
   }
 
   void Expect(const nsCString& aMessage, nsIRunnable* aContinuation) {
     mExpected.AppendElement(ExpectedMessage(aMessage, aContinuation));
   }
 
   void AwaitFinished() {
     while (!mFinished) {
@@ -1142,34 +1370,41 @@ private:
 
   GMPDecryptorProxy* mDecryptor;
   Monitor mMonitor;
   Atomic<bool> mFinished;
   nsCString mNodeId;
 };
 
 void
-GMPTestRunner::DoTest(void (GMPTestRunner::*aTestMethod)())
+GMPTestRunner::DoTest(void (GMPTestRunner::*aTestMethod)(GMPTestMonitor&))
 {
-  nsRefPtr<GeckoMediaPluginService> service =
-    GeckoMediaPluginService::GetGeckoMediaPluginService();
-  nsCOMPtr<nsIThread> thread;
+  nsCOMPtr<nsIThread> thread(GetGMPThread());
 
-  service->GetThread(getter_AddRefs(thread));
-  thread->Dispatch(NS_NewRunnableMethod(this, aTestMethod), NS_DISPATCH_SYNC);
+  GMPTestMonitor monitor;
+  thread->Dispatch(NS_NewRunnableMethodWithArg<GMPTestMonitor&>(this,
+                                                                aTestMethod,
+                                                                monitor),
+                   NS_DISPATCH_NORMAL);
+  monitor.AwaitFinished();
 }
 
 TEST(GeckoMediaPlugins, GMPTestCodec) {
   nsRefPtr<GMPTestRunner> runner = new GMPTestRunner();
-  runner->DoTest(&GMPTestRunner::RunTestGMPTestCodec);
+  runner->DoTest(&GMPTestRunner::RunTestGMPTestCodec1);
+  runner->DoTest(&GMPTestRunner::RunTestGMPTestCodec2);
+  runner->DoTest(&GMPTestRunner::RunTestGMPTestCodec3);
 }
 
 TEST(GeckoMediaPlugins, GMPCrossOrigin) {
   nsRefPtr<GMPTestRunner> runner = new GMPTestRunner();
-  runner->DoTest(&GMPTestRunner::RunTestGMPCrossOrigin);
+  runner->DoTest(&GMPTestRunner::RunTestGMPCrossOrigin1);
+  runner->DoTest(&GMPTestRunner::RunTestGMPCrossOrigin2);
+  runner->DoTest(&GMPTestRunner::RunTestGMPCrossOrigin3);
+  runner->DoTest(&GMPTestRunner::RunTestGMPCrossOrigin4);
 }
 
 TEST(GeckoMediaPlugins, GMPStorageGetNodeId) {
   nsRefPtr<GMPStorageTest> runner = new GMPStorageTest();
   runner->DoTest(&GMPStorageTest::TestGetNodeId);
 }
 
 TEST(GeckoMediaPlugins, GMPStorageBasic) {
--- a/media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.cpp
+++ b/media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.cpp
@@ -146,71 +146,98 @@ WebrtcGmpVideoEncoder::InitEncode(const 
   MOZ_ASSERT(mMPS);
 
   if (!mGMPThread) {
     if (NS_WARN_IF(NS_FAILED(mMPS->GetThread(getter_AddRefs(mGMPThread))))) {
       return WEBRTC_VIDEO_CODEC_ERROR;
     }
   }
 
-  int32_t ret;
-  mGMPThread->Dispatch(WrapRunnableRet(this,
-                                       &WebrtcGmpVideoEncoder::InitEncode_g,
-                                       aCodecSettings,
-                                       aNumberOfCores,
-                                       aMaxPayloadSize,
-                                       &ret),
-                       NS_DISPATCH_SYNC);
+  nsCOMPtr<nsIThread> currentThread(do_GetCurrentThread());
+  MOZ_ASSERT(currentThread != mGMPThread);
 
-  return ret;
+  nsRefPtr<InitDoneRunnable> initDone(new InitDoneRunnable());
+  mGMPThread->Dispatch(WrapRunnable(this,
+                                    &WebrtcGmpVideoEncoder::InitEncode_g,
+                                    aCodecSettings,
+                                    aNumberOfCores,
+                                    aMaxPayloadSize,
+                                    initDone),
+                       NS_DISPATCH_NORMAL);
+
+
+  while (!initDone->IsDone()) {
+    NS_ProcessNextEvent(currentThread, true);
+  }
+
+  return initDone->Result();
 }
 
-int32_t
+void
 WebrtcGmpVideoEncoder::InitEncode_g(const webrtc::VideoCodec* aCodecSettings,
                                     int32_t aNumberOfCores,
-                                    uint32_t aMaxPayloadSize)
+                                    uint32_t aMaxPayloadSize,
+                                    InitDoneRunnable* aInitDone)
 {
   nsTArray<nsCString> tags;
   tags.AppendElement(NS_LITERAL_CSTRING("h264"));
-  if (NS_WARN_IF(NS_FAILED(mMPS->GetGMPVideoEncoder(&tags,
-                                                    NS_LITERAL_CSTRING(""),
-                                                    &mHost,
-                                                    &mGMP)))) {
+  UniquePtr<GetGMPVideoEncoderCallback> callback(
+    new InitDoneCallback(this, aInitDone, aCodecSettings, aMaxPayloadSize));
+  nsresult rv = mMPS->GetGMPVideoEncoder(&tags,
+                                         NS_LITERAL_CSTRING(""),
+                                         Move(callback));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
     mMPS = nullptr;
     mGMP = nullptr;
     mGMPThread = nullptr;
     mHost = nullptr;
-    return WEBRTC_VIDEO_CODEC_ERROR;
+    aInitDone->Dispatch(WEBRTC_VIDEO_CODEC_ERROR);
+    return;
   }
+}
 
+int32_t
+WebrtcGmpVideoEncoder::GmpInitDone(GMPVideoEncoderProxy* aGMP,
+                                   GMPVideoHost* aHost,
+                                   const webrtc::VideoCodec* aCodecSettings,
+                                   uint32_t aMaxPayloadSize)
+{
+  mGMP = aGMP;
+  mHost = aHost;
   if (!mGMP || !mHost) {
     return WEBRTC_VIDEO_CODEC_ERROR;
   }
 
   // Bug XXXXXX: transfer settings from codecSettings to codec.
   memset(&mCodecParams, 0, sizeof(mCodecParams));
 
   mCodecParams.mGMPApiVersion = 33;
-  mCodecParams.mWidth = aCodecSettings->width;
-  mCodecParams.mHeight = aCodecSettings->height;
   mCodecParams.mStartBitrate = aCodecSettings->startBitrate;
   mCodecParams.mMinBitrate = aCodecSettings->minBitrate;
   mCodecParams.mMaxBitrate = aCodecSettings->maxBitrate;
   mCodecParams.mMaxFramerate = aCodecSettings->maxFramerate;
   mMaxPayloadSize = aMaxPayloadSize;
   if (aCodecSettings->codecSpecific.H264.packetizationMode == 1) {
     mMaxPayloadSize = 0; // No limit.
   }
 
   if (aCodecSettings->mode == webrtc::kScreensharing) {
     mCodecParams.mMode = kGMPScreensharing;
   } else {
     mCodecParams.mMode = kGMPRealtimeVideo;
   }
 
+  return InitEncoderForSize(aCodecSettings->width, aCodecSettings->height);
+}
+
+int32_t
+WebrtcGmpVideoEncoder::InitEncoderForSize(unsigned short aWidth, unsigned short aHeight)
+{
+  mCodecParams.mWidth = aWidth;
+  mCodecParams.mHeight = aHeight;
   // Pass dummy codecSpecific data for now...
   nsTArray<uint8_t> codecSpecific;
 
   GMPErr err = mGMP->InitEncode(mCodecParams, codecSpecific, this, 1, mMaxPayloadSize);
   if (err != GMPNoErr) {
     return WEBRTC_VIDEO_CODEC_ERROR;
   }
 
@@ -218,72 +245,90 @@ WebrtcGmpVideoEncoder::InitEncode_g(cons
 }
 
 
 int32_t
 WebrtcGmpVideoEncoder::Encode(const webrtc::I420VideoFrame& aInputImage,
                               const webrtc::CodecSpecificInfo* aCodecSpecificInfo,
                               const std::vector<webrtc::VideoFrameType>* aFrameTypes)
 {
+  MOZ_ASSERT(mHost);
+  if (!mGMP) {
+    // destroyed via Terminate()
+    return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
+  MOZ_ASSERT(aInputImage.width() >= 0 && aInputImage.height() >= 0);
+  if (static_cast<uint32_t>(aInputImage.width()) != mCodecParams.mWidth ||
+      static_cast<uint32_t>(aInputImage.height()) != mCodecParams.mHeight) {
+    LOGD(("GMP Encode: resolution change from %ux%u to %dx%d",
+          mCodecParams.mWidth, mCodecParams.mHeight, aInputImage.width(), aInputImage.height()));
+
+    nsRefPtr<InitDoneRunnable> initDone(new InitDoneRunnable());
+    nsCOMPtr<nsIRunnable> task(
+      WrapRunnable(this,
+                   &WebrtcGmpVideoEncoder::RegetEncoderForResolutionChange,
+                   &aInputImage,
+                   initDone));
+    mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
+
+    nsCOMPtr<nsIThread> currentThread(do_GetCurrentThread());
+    while (!initDone->IsDone()) {
+      NS_ProcessNextEvent(currentThread, true);
+    }
+
+    if (initDone->Result() != WEBRTC_VIDEO_CODEC_OK) {
+      return initDone->Result();
+    }
+  }
+
   int32_t ret;
-  MOZ_ASSERT(mGMPThread);
   mozilla::SyncRunnable::DispatchToThread(mGMPThread,
                 WrapRunnableRet(this,
                                 &WebrtcGmpVideoEncoder::Encode_g,
                                 &aInputImage,
                                 aCodecSpecificInfo,
                                 aFrameTypes,
                                 &ret));
 
   return ret;
 }
 
+void
+WebrtcGmpVideoEncoder::RegetEncoderForResolutionChange(const webrtc::I420VideoFrame* aInputImage,
+                                                       InitDoneRunnable* aInitDone)
+{
+  mGMP->Close();
+
+  UniquePtr<GetGMPVideoEncoderCallback> callback(
+    new InitDoneForResolutionChangeCallback(this, aInitDone,
+                                            aInputImage->width(),
+                                            aInputImage->height()));
+
+  // OpenH264 codec (at least) can't handle dynamic input resolution changes
+  // re-init the plugin when the resolution changes
+  // XXX allow codec to indicate it doesn't need re-init!
+  nsTArray<nsCString> tags;
+  tags.AppendElement(NS_LITERAL_CSTRING("h264"));
+  if (NS_WARN_IF(NS_FAILED(mMPS->GetGMPVideoEncoder(&tags,
+                                                    NS_LITERAL_CSTRING(""),
+                                                    Move(callback))))) {
+    mGMP = nullptr;
+    mHost = nullptr;
+    aInitDone->Dispatch(WEBRTC_VIDEO_CODEC_ERROR);
+  }
+}
+
 int32_t
 WebrtcGmpVideoEncoder::Encode_g(const webrtc::I420VideoFrame* aInputImage,
                                 const webrtc::CodecSpecificInfo* aCodecSpecificInfo,
                                 const std::vector<webrtc::VideoFrameType>* aFrameTypes)
 {
   MOZ_ASSERT(mHost);
-  if (!mGMP) {
-    // destroyed via Terminate()
-    return WEBRTC_VIDEO_CODEC_ERROR;
-  }
-
-  MOZ_ASSERT(aInputImage->width() >= 0 && aInputImage->height() >= 0);
-  if (static_cast<uint32_t>(aInputImage->width()) != mCodecParams.mWidth ||
-      static_cast<uint32_t>(aInputImage->height()) != mCodecParams.mHeight) {
-    LOGD(("GMP Encode: resolution change from %ux%u to %dx%d",
-          mCodecParams.mWidth, mCodecParams.mHeight, aInputImage->width(), aInputImage->height()));
-
-    mGMP->Close();
-
-    // OpenH264 codec (at least) can't handle dynamic input resolution changes
-    // re-init the plugin when the resolution changes
-    // XXX allow codec to indicate it doesn't need re-init!
-    nsTArray<nsCString> tags;
-    tags.AppendElement(NS_LITERAL_CSTRING("h264"));
-    if (NS_WARN_IF(NS_FAILED(mMPS->GetGMPVideoEncoder(&tags,
-                                                      NS_LITERAL_CSTRING(""),
-                                                      &mHost,
-                                                      &mGMP)))) {
-      mGMP = nullptr;
-      mHost = nullptr;
-      return WEBRTC_VIDEO_CODEC_ERROR;
-    }
-
-    mCodecParams.mWidth = aInputImage->width();
-    mCodecParams.mHeight = aInputImage->height();
-    // Pass dummy codecSpecific data for now...
-    nsTArray<uint8_t> codecSpecific;
-
-    GMPErr err = mGMP->InitEncode(mCodecParams, codecSpecific, this, 1, mMaxPayloadSize);
-    if (err != GMPNoErr) {
-      return WEBRTC_VIDEO_CODEC_ERROR;
-    }
-  }
+  MOZ_ASSERT(mGMP);
 
   GMPVideoFrame* ftmp = nullptr;
   GMPErr err = mHost->CreateFrame(kGMPI420VideoFrame, &ftmp);
   if (err != GMPNoErr) {
     return WEBRTC_VIDEO_CODEC_ERROR;
   }
   GMPUnique<GMPVideoi420Frame>::Ptr frame(static_cast<GMPVideoi420Frame*>(ftmp));
 
@@ -561,43 +606,58 @@ WebrtcGmpVideoDecoder::InitDecode(const 
   MOZ_ASSERT(mMPS);
 
   if (!mGMPThread) {
     if (NS_WARN_IF(NS_FAILED(mMPS->GetThread(getter_AddRefs(mGMPThread))))) {
       return WEBRTC_VIDEO_CODEC_ERROR;
     }
   }
 
-  int32_t ret;
-  mGMPThread->Dispatch(WrapRunnableRet(this,
-                                       &WebrtcGmpVideoDecoder::InitDecode_g,
-                                       aCodecSettings,
-                                       aNumberOfCores,
-                                       &ret),
-                       NS_DISPATCH_SYNC);
+  nsRefPtr<InitDoneRunnable> initDone(new InitDoneRunnable());
+  mGMPThread->Dispatch(WrapRunnable(this,
+                                    &WebrtcGmpVideoDecoder::InitDecode_g,
+                                    aCodecSettings,
+                                    aNumberOfCores,
+                                    initDone.get()),
+                       NS_DISPATCH_NORMAL);
 
-  return ret;
+  nsCOMPtr<nsIThread> currentThread(do_GetCurrentThread());
+  while (!initDone->IsDone()) {
+    NS_ProcessNextEvent(currentThread, true);
+  }
+
+  return initDone->Result();
 }
 
-int32_t
+void
 WebrtcGmpVideoDecoder::InitDecode_g(const webrtc::VideoCodec* aCodecSettings,
-                                    int32_t aNumberOfCores)
+                                    int32_t aNumberOfCores,
+                                    InitDoneRunnable* aInitDone)
 {
   nsTArray<nsCString> tags;
   tags.AppendElement(NS_LITERAL_CSTRING("h264"));
+  UniquePtr<GetGMPVideoDecoderCallback> callback(
+    new InitDoneCallback(this, aInitDone));
   if (NS_WARN_IF(NS_FAILED(mMPS->GetGMPVideoDecoder(&tags,
                                                     NS_LITERAL_CSTRING(""),
-                                                    &mHost,
-                                                    &mGMP)))) {
+                                                    Move(callback))))) {
     mMPS = nullptr;
     mGMP = nullptr;
     mGMPThread = nullptr;
     mHost = nullptr;
-    return WEBRTC_VIDEO_CODEC_ERROR;
+    aInitDone->Dispatch(WEBRTC_VIDEO_CODEC_ERROR);
   }
+}
+
+int32_t
+WebrtcGmpVideoDecoder::GmpInitDone(GMPVideoDecoderProxy* aGMP,
+                                   GMPVideoHost* aHost)
+{
+  mGMP = aGMP;
+  mHost = aHost;
   mMPS = nullptr;
 
   if (!mGMP || !mHost) {
     return WEBRTC_VIDEO_CODEC_ERROR;
   }
 
   // Bug XXXXXX: transfer settings from codecSettings to codec.
   GMPVideoCodec codec;
--- a/media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.h
+++ b/media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.h
@@ -16,16 +16,17 @@
 
 #ifndef WEBRTCGMPVIDEOCODEC_H_
 #define WEBRTCGMPVIDEOCODEC_H_
 
 #include <iostream>
 #include <queue>
 
 #include "nsThreadUtils.h"
+#include "mozilla/Monitor.h"
 #include "mozilla/Mutex.h"
 
 #include "mozIGeckoMediaPluginService.h"
 #include "MediaConduitInterface.h"
 #include "AudioConduit.h"
 #include "VideoConduit.h"
 #include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h"
 
@@ -72,23 +73,141 @@ public:
   // GMPVideoEncoderCallback virtual functions.
   virtual void Encoded(GMPVideoEncodedFrame* aEncodedFrame,
                        const nsTArray<uint8_t>& aCodecSpecificInfo) override;
 
   virtual void Error(GMPErr aError) override {
   }
 
 private:
-  virtual int32_t InitEncode_g(const webrtc::VideoCodec* aCodecSettings,
-                               int32_t aNumberOfCores,
-                               uint32_t aMaxPayloadSize);
+  class InitDoneRunnable : public nsRunnable
+  {
+  public:
+    InitDoneRunnable()
+      : mInitDone(false),
+        mResult(WEBRTC_VIDEO_CODEC_OK),
+        mThread(do_GetCurrentThread())
+    {
+    }
+
+    NS_IMETHOD Run()
+    {
+      MOZ_ASSERT(mThread == nsCOMPtr<nsIThread>(do_GetCurrentThread()));
+      mInitDone = true;
+      return NS_OK;
+    }
+
+    void Dispatch(int32_t aResult)
+    {
+      mResult = aResult;
+      mThread->Dispatch(this, NS_DISPATCH_NORMAL);
+    }
+
+    bool IsDone()
+    {
+      MOZ_ASSERT(nsCOMPtr<nsIThread>(do_GetCurrentThread()) == mThread);
+      return mInitDone;
+    }
+
+    int32_t Result()
+    {
+      return mResult;
+    }
+
+  private:
+    bool mInitDone;
+    int32_t mResult;
+    nsCOMPtr<nsIThread> mThread;
+  };
+
+  void InitEncode_g(const webrtc::VideoCodec* aCodecSettings,
+                    int32_t aNumberOfCores,
+                    uint32_t aMaxPayloadSize,
+                    InitDoneRunnable* aInitDone);
+  int32_t GmpInitDone(GMPVideoEncoderProxy* aGMP, GMPVideoHost* aHost,
+                      const webrtc::VideoCodec* aCodecSettings,
+                      uint32_t aMaxPayloadSize);
+  int32_t InitEncoderForSize(unsigned short aWidth, unsigned short aHeight);
+
+  class InitDoneCallback : public GetGMPVideoEncoderCallback
+  {
+  public:
+    InitDoneCallback(WebrtcGmpVideoEncoder* aEncoder,
+                     InitDoneRunnable* aInitDone,
+                     const webrtc::VideoCodec* aCodecSettings,
+                     uint32_t aMaxPayloadSize)
+      : mEncoder(aEncoder),
+        mInitDone(aInitDone),
+        mCodecSettings(aCodecSettings),
+        mMaxPayloadSize(aMaxPayloadSize)
+    {
+    }
 
-  virtual int32_t Encode_g(const webrtc::I420VideoFrame* aInputImage,
-                           const webrtc::CodecSpecificInfo* aCodecSpecificInfo,
-                           const std::vector<webrtc::VideoFrameType>* aFrameTypes);
+    virtual void Done(GMPVideoEncoderProxy* aGMP, GMPVideoHost* aHost) override
+    {
+      mEncoder->mGMP = aGMP;
+      mEncoder->mHost = aHost;
+      int32_t result;
+      if (aGMP || aHost) {
+        result = mEncoder->GmpInitDone(aGMP, aHost, mCodecSettings,
+                                       mMaxPayloadSize);
+      } else {
+        result = WEBRTC_VIDEO_CODEC_ERROR;
+      }
+
+      mInitDone->Dispatch(result);
+    }
+
+  private:
+    WebrtcGmpVideoEncoder* mEncoder;
+    nsRefPtr<InitDoneRunnable> mInitDone;
+    const webrtc::VideoCodec* mCodecSettings;
+    uint32_t mMaxPayloadSize;
+  };
+
+  int32_t Encode_g(const webrtc::I420VideoFrame* aInputImage,
+                   const webrtc::CodecSpecificInfo* aCodecSpecificInfo,
+                   const std::vector<webrtc::VideoFrameType>* aFrameTypes);
+  void RegetEncoderForResolutionChange(const webrtc::I420VideoFrame* aInputImage,
+                                       InitDoneRunnable* aInitDone);
+
+  class InitDoneForResolutionChangeCallback : public GetGMPVideoEncoderCallback
+  {
+  public:
+    InitDoneForResolutionChangeCallback(WebrtcGmpVideoEncoder* aEncoder,
+                                        InitDoneRunnable* aInitDone,
+                                        uint32_t aWidth,
+                                        uint32_t aHeight)
+      : mEncoder(aEncoder),
+        mInitDone(aInitDone),
+        mWidth(aWidth),
+        mHeight(aHeight)
+    {
+    }
+
+    virtual void Done(GMPVideoEncoderProxy* aGMP, GMPVideoHost* aHost) override
+    {
+      mEncoder->mGMP = aGMP;
+      mEncoder->mHost = aHost;
+      int32_t result;
+      if (aGMP && aHost) {
+        result = mEncoder->InitEncoderForSize(mWidth, mHeight);
+      } else {
+        result = WEBRTC_VIDEO_CODEC_ERROR;
+      }
+
+      mInitDone->Dispatch(result);
+    }
+
+  private:
+    WebrtcGmpVideoEncoder* mEncoder;
+    nsRefPtr<InitDoneRunnable> mInitDone;
+    uint32_t mWidth;
+    uint32_t mHeight;
+  };
 
   virtual int32_t SetRates_g(uint32_t aNewBitRate,
                              uint32_t aFrameRate);
 
   nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
   nsCOMPtr<nsIThread> mGMPThread;
   GMPVideoEncoderProxy* mGMP;
   GMPVideoHost* mHost;
@@ -146,18 +265,82 @@ public:
   virtual void ResetComplete() override {
   }
 
   virtual void Error(GMPErr aError) override {
      mDecoderStatus = aError;
   }
 
 private:
-  virtual int32_t InitDecode_g(const webrtc::VideoCodec* aCodecSettings,
-                               int32_t aNumberOfCores);
+  class InitDoneRunnable : public nsRunnable
+  {
+  public:
+    InitDoneRunnable()
+      : mInitDone(false),
+        mResult(WEBRTC_VIDEO_CODEC_OK),
+        mThread(do_GetCurrentThread())
+    {
+    }
+
+    NS_IMETHOD Run()
+    {
+      MOZ_ASSERT(mThread == nsCOMPtr<nsIThread>(do_GetCurrentThread()));
+      mInitDone = true;
+      return NS_OK;
+    }
+
+    void Dispatch(int32_t aResult)
+    {
+      mResult = aResult;
+      mThread->Dispatch(this, NS_DISPATCH_NORMAL);
+    }
+
+    bool IsDone()
+    {
+      MOZ_ASSERT(nsCOMPtr<nsIThread>(do_GetCurrentThread()) == mThread);
+      return mInitDone;
+    }
+
+    int32_t Result()
+    {
+      return mResult;
+    }
+
+  private:
+    bool mInitDone;
+    int32_t mResult;
+    nsCOMPtr<nsIThread> mThread;
+  };
+
+  void InitDecode_g(const webrtc::VideoCodec* aCodecSettings,
+                    int32_t aNumberOfCores,
+                    InitDoneRunnable* aInitDone);
+  int32_t GmpInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost);
+
+  class InitDoneCallback : public GetGMPVideoDecoderCallback
+  {
+  public:
+    explicit InitDoneCallback(WebrtcGmpVideoDecoder* aDecoder,
+                              InitDoneRunnable* aInitDone)
+      : mDecoder(aDecoder),
+        mInitDone(aInitDone)
+    {
+    }
+
+    virtual void Done(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost)
+    {
+      int32_t result = mDecoder->GmpInitDone(aGMP, aHost);
+
+      mInitDone->Dispatch(result);
+    }
+
+  private:
+    WebrtcGmpVideoDecoder* mDecoder;
+    nsRefPtr<InitDoneRunnable> mInitDone;
+};
 
   virtual int32_t Decode_g(const webrtc::EncodedImage& aInputImage,
                            bool aMissingFrames,
                            const webrtc::RTPFragmentationHeader* aFragmentation,
                            const webrtc::CodecSpecificInfo* aCodecSpecificInfo,
                            int64_t aRenderTimeMs);
 
   nsCOMPtr<mozIGeckoMediaPluginService> mMPS;