Bug 1315850 - Shutdown ChromiumCDMParent. r=gerald draft
authorChris Pearce <cpearce@mozilla.com>
Thu, 09 Mar 2017 17:34:18 +1300
changeset 504177 6f0d72f76e313b55f7c905d5878c63b8d7292b1b
parent 504176 5a2f77ffe84f9b99b4668520c838b29a428578d3
child 504178 186f35455264aaa144fd7b1887b8ca2476ac03b2
push id50748
push userbmo:cpearce@mozilla.com
push dateFri, 24 Mar 2017 01:10:17 +0000
reviewersgerald
bugs1315850
milestone55.0a1
Bug 1315850 - Shutdown ChromiumCDMParent. r=gerald MozReview-Commit-ID: E82ETFS90eH
dom/media/gmp/ChromiumCDMParent.cpp
dom/media/gmp/ChromiumCDMParent.h
dom/media/gmp/ChromiumCDMProxy.cpp
--- a/dom/media/gmp/ChromiumCDMParent.cpp
+++ b/dom/media/gmp/ChromiumCDMParent.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ChromiumCDMParent.h"
 #include "mozilla/gmp/GMPTypes.h"
 #include "GMPContentChild.h"
+#include "GMPContentParent.h"
 #include "mozilla/Unused.h"
 #include "ChromiumCDMProxy.h"
 #include "mozilla/dom/MediaKeyMessageEventBinding.h"
 #include "content_decryption_module.h"
 #include "GMPLog.h"
 
 namespace mozilla {
 namespace gmp {
@@ -28,84 +29,117 @@ ChromiumCDMParent::ChromiumCDMParent(GMP
 }
 
 bool
 ChromiumCDMParent::Init(ChromiumCDMProxy* aProxy,
                         bool aAllowDistinctiveIdentifier,
                         bool aAllowPersistentState)
 {
   GMP_LOG("ChromiumCDMParent::Init(this=%p)", this);
+  if (!aProxy) {
+    return false;
+  }
   mProxy = aProxy;
   return SendInit(aAllowDistinctiveIdentifier, aAllowPersistentState);
 }
 
 void
 ChromiumCDMParent::CreateSession(uint32_t aCreateSessionToken,
                                  uint32_t aSessionType,
                                  uint32_t aInitDataType,
                                  uint32_t aPromiseId,
                                  const nsTArray<uint8_t>& aInitData)
 {
   GMP_LOG("ChromiumCDMParent::CreateSession(this=%p)", this);
+  if (mIsShutdown) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("CDM is shutdown."));
+    return;
+  }
   if (!SendCreateSessionAndGenerateRequest(
         aPromiseId, aSessionType, aInitDataType, aInitData)) {
     RejectPromise(
       aPromiseId,
       NS_ERROR_DOM_INVALID_STATE_ERR,
       NS_LITERAL_CSTRING("Failed to send generateRequest to CDM process."));
     return;
   }
   mPromiseToCreateSessionToken.Put(aPromiseId, aCreateSessionToken);
 }
 
 void
 ChromiumCDMParent::SetServerCertificate(uint32_t aPromiseId,
                                         const nsTArray<uint8_t>& aCert)
 {
   GMP_LOG("ChromiumCDMParent::SetServerCertificate(this=%p)", this);
+  if (mIsShutdown) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("CDM is shutdown."));
+    return;
+  }
   if (!SendSetServerCertificate(aPromiseId, aCert)) {
     RejectPromise(
       aPromiseId,
       NS_ERROR_DOM_INVALID_STATE_ERR,
       NS_LITERAL_CSTRING("Failed to send setServerCertificate to CDM process"));
   }
 }
 
 void
 ChromiumCDMParent::UpdateSession(const nsCString& aSessionId,
                                  uint32_t aPromiseId,
                                  const nsTArray<uint8_t>& aResponse)
 {
   GMP_LOG("ChromiumCDMParent::UpdateSession(this=%p)", this);
+  if (mIsShutdown) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("CDM is shutdown."));
+    return;
+  }
   if (!SendUpdateSession(aPromiseId, aSessionId, aResponse)) {
     RejectPromise(
       aPromiseId,
       NS_ERROR_DOM_INVALID_STATE_ERR,
       NS_LITERAL_CSTRING("Failed to send updateSession to CDM process"));
   }
 }
 
 void
 ChromiumCDMParent::CloseSession(const nsCString& aSessionId,
                                 uint32_t aPromiseId)
 {
   GMP_LOG("ChromiumCDMParent::CloseSession(this=%p)", this);
+  if (mIsShutdown) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("CDM is shutdown."));
+    return;
+  }
   if (!SendCloseSession(aPromiseId, aSessionId)) {
     RejectPromise(
       aPromiseId,
       NS_ERROR_DOM_INVALID_STATE_ERR,
       NS_LITERAL_CSTRING("Failed to send closeSession to CDM process"));
   }
 }
 
 void
 ChromiumCDMParent::RemoveSession(const nsCString& aSessionId,
                                  uint32_t aPromiseId)
 {
   GMP_LOG("ChromiumCDMParent::RemoveSession(this=%p)", this);
+  if (mIsShutdown) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("CDM is shutdown."));
+    return;
+  }
   if (!SendRemoveSession(aPromiseId, aSessionId)) {
     RejectPromise(
       aPromiseId,
       NS_ERROR_DOM_INVALID_STATE_ERR,
       NS_LITERAL_CSTRING("Failed to send removeSession to CDM process"));
   }
 }
 
@@ -130,16 +164,20 @@ InitCDMInputBuffer(gmp::CDMInputBuffer& 
                                 crypto.mEncryptedSizes,
                                 crypto.mValid);
   return true;
 }
 
 RefPtr<DecryptPromise>
 ChromiumCDMParent::Decrypt(MediaRawData* aSample)
 {
+  if (mIsShutdown) {
+    return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
+                                           __func__);
+  }
   CDMInputBuffer buffer;
   if (!InitCDMInputBuffer(buffer, aSample)) {
     return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
                                            __func__);
   }
   RefPtr<DecryptJob> job = new DecryptJob(aSample);
   if (!SendDecrypt(job->mId, buffer)) {
     GMP_LOG(
@@ -151,30 +189,35 @@ ChromiumCDMParent::Decrypt(MediaRawData*
   RefPtr<DecryptPromise> promise = job->Ensure();
   mDecrypts.AppendElement(job);
   return promise;
 }
 
 ipc::IPCResult
 ChromiumCDMParent::Recv__delete__()
 {
+  MOZ_ASSERT(mIsShutdown);
   GMP_LOG("ChromiumCDMParent::Recv__delete__(this=%p)", this);
+  if (mContentParent) {
+    mContentParent->ChromiumCDMDestroyed(this);
+    mContentParent = nullptr;
+  }
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvOnResolveNewSessionPromise(const uint32_t& aPromiseId,
                                                   const nsCString& aSessionId)
 {
   GMP_LOG("ChromiumCDMParent::RecvOnResolveNewSessionPromise(this=%p, pid=%u, "
           "sid=%s)",
           this,
           aPromiseId,
           aSessionId.get());
-  if (!mProxy) {
+  if (!mProxy || mIsShutdown) {
     return IPC_OK();
   }
 
   Maybe<uint32_t> token = mPromiseToCreateSessionToken.GetAndRemove(aPromiseId);
   if (token.isNothing()) {
     RejectPromise(aPromiseId,
                   NS_ERROR_DOM_INVALID_STATE_ERR,
                   NS_LITERAL_CSTRING("Lost session token for new session."));
@@ -194,17 +237,19 @@ ChromiumCDMParent::RecvOnResolveNewSessi
 }
 
 void
 ChromiumCDMParent::ResolvePromise(uint32_t aPromiseId)
 {
   GMP_LOG(
     "ChromiumCDMParent::ResolvePromise(this=%p, pid=%u)", this, aPromiseId);
 
-  if (!mProxy) {
+  // Note: The MediaKeys rejects all pending DOM promises when it
+  // initiates shutdown.
+  if (!mProxy || mIsShutdown) {
     return;
   }
   NS_DispatchToMainThread(NewRunnableMethod<uint32_t>(
     mProxy, &ChromiumCDMProxy::ResolvePromise, aPromiseId));
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvOnResolvePromise(const uint32_t& aPromiseId)
@@ -242,17 +287,19 @@ ToNsresult(uint32_t aError)
 
 void
 ChromiumCDMParent::RejectPromise(uint32_t aPromiseId,
                                  nsresult aError,
                                  const nsCString& aErrorMessage)
 {
   GMP_LOG(
     "ChromiumCDMParent::RejectPromise(this=%p, pid=%u)", this, aPromiseId);
-  if (!mProxy) {
+  // Note: The MediaKeys rejects all pending DOM promises when it
+  // initiates shutdown.
+  if (!mProxy || mIsShutdown) {
     return;
   }
   NS_DispatchToMainThread(NewRunnableMethod<uint32_t, nsresult, nsCString>(
     mProxy,
     &ChromiumCDMProxy::RejectPromise,
     aPromiseId,
     aError,
     aErrorMessage));
@@ -286,17 +333,17 @@ ToDOMMessageType(uint32_t aMessageType)
 ipc::IPCResult
 ChromiumCDMParent::RecvOnSessionMessage(const nsCString& aSessionId,
                                         const uint32_t& aMessageType,
                                         nsTArray<uint8_t>&& aMessage)
 {
   GMP_LOG("ChromiumCDMParent::RecvOnSessionMessage(this=%p, sid=%s)",
           this,
           aSessionId.get());
-  if (!mProxy) {
+  if (!mProxy || mIsShutdown) {
     return IPC_OK();
   }
   RefPtr<CDMProxy> proxy = mProxy;
   nsString sid = NS_ConvertUTF8toUTF16(aSessionId);
   dom::MediaKeyMessageType messageType = ToDOMMessageType(aMessageType);
   nsTArray<uint8_t> msg(Move(aMessage));
   NS_DispatchToMainThread(
     NS_NewRunnableFunction([proxy, sid, messageType, msg]() mutable {
@@ -329,17 +376,17 @@ ToDOMMediaKeyStatus(uint32_t aStatus)
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvOnSessionKeysChange(
   const nsCString& aSessionId,
   nsTArray<CDMKeyInformation>&& aKeysInfo)
 {
   GMP_LOG("ChromiumCDMParent::RecvOnSessionKeysChange(this=%p)", this);
-  if (!mProxy) {
+  if (!mProxy || mIsShutdown) {
     return IPC_OK();
   }
   bool keyStatusesChange = false;
   {
     CDMCaps::AutoLock caps(mProxy->Capabilites());
     for (size_t i = 0; i < aKeysInfo.Length(); i++) {
       keyStatusesChange |=
         caps.SetKeyStatus(aKeysInfo[i].mKeyId(),
@@ -359,49 +406,49 @@ ChromiumCDMParent::RecvOnSessionKeysChan
 
 ipc::IPCResult
 ChromiumCDMParent::RecvOnExpirationChange(const nsCString& aSessionId,
                                           const double& aSecondsSinceEpoch)
 {
   GMP_LOG("ChromiumCDMParent::RecvOnExpirationChange(this=%p) time=%lf",
           this,
           aSecondsSinceEpoch);
-  if (!mProxy) {
+  if (!mProxy || mIsShutdown) {
     return IPC_OK();
   }
   NS_DispatchToMainThread(NewRunnableMethod<nsString, UnixTime>(
     mProxy,
     &ChromiumCDMProxy::OnExpirationChange,
     NS_ConvertUTF8toUTF16(aSessionId),
     GMPTimestamp(aSecondsSinceEpoch * 1000)));
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvOnSessionClosed(const nsCString& aSessionId)
 {
   GMP_LOG("ChromiumCDMParent::RecvOnSessionClosed(this=%p)", this);
-  if (!mProxy) {
+  if (!mProxy || mIsShutdown) {
     return IPC_OK();
   }
   NS_DispatchToMainThread(
     NewRunnableMethod<nsString>(mProxy,
                                 &ChromiumCDMProxy::OnSessionClosed,
                                 NS_ConvertUTF8toUTF16(aSessionId)));
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvOnLegacySessionError(const nsCString& aSessionId,
                                             const uint32_t& aError,
                                             const uint32_t& aSystemCode,
                                             const nsCString& aMessage)
 {
   GMP_LOG("ChromiumCDMParent::RecvOnLegacySessionError(this=%p)", this);
-  if (!mProxy) {
+  if (!mProxy || mIsShutdown) {
     return IPC_OK();
   }
   NS_DispatchToMainThread(
     NewRunnableMethod<nsString, nsresult, uint32_t, nsString>(
       mProxy,
       &ChromiumCDMProxy::OnSessionError,
       NS_ConvertUTF8toUTF16(aSessionId),
       ToNsresult(aError),
@@ -427,30 +474,34 @@ ipc::IPCResult
 ChromiumCDMParent::RecvDecrypted(const uint32_t& aId,
                                  const uint32_t& aStatus,
                                  nsTArray<uint8_t>&& aData)
 {
   GMP_LOG("ChromiumCDMParent::RecvDecrypted(this=%p, id=%u, status=%u)",
           this,
           aId,
           aStatus);
+  if (mIsShutdown) {
+    MOZ_ASSERT(mDecrypts.IsEmpty());
+    return IPC_OK();
+  }
   for (size_t i = 0; i < mDecrypts.Length(); i++) {
     if (mDecrypts[i]->mId == aId) {
       mDecrypts[i]->PostResult(ToDecryptStatus(aStatus), aData);
       mDecrypts.RemoveElementAt(i);
       break;
     }
   }
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvDecoded(const CDMVideoFrame& aFrame)
 {
-  if (mDecodePromise.IsEmpty()) {
+  if (mIsShutdown || mDecodePromise.IsEmpty()) {
     return IPC_OK();
   }
   VideoData::YCbCrBuffer b;
   nsTArray<uint8_t> data;
   data = aFrame.mData();
 
   if (data.IsEmpty()) {
     mDecodePromise.ResolveIfExists(nsTArray<RefPtr<MediaData>>(), __func__);
@@ -500,77 +551,121 @@ ChromiumCDMParent::RecvDecoded(const CDM
   }
 
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvDecodeFailed(const uint32_t& aStatus)
 {
+  if (mIsShutdown) {
+    MOZ_ASSERT(mDecodePromise.IsEmpty());
+    return IPC_OK();
+  }
   mDecodePromise.RejectIfExists(
     MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                 RESULT_DETAIL("ChromiumCDMParent::RecvDecodeFailed")),
     __func__);
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvShutdown()
 {
   GMP_LOG("ChromiumCDMParent::RecvShutdown(this=%p)", this);
-  // TODO: SendDestroy(), call Terminated.
+  Shutdown();
   return IPC_OK();
 }
 
 void
 ChromiumCDMParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   GMP_LOG("ChromiumCDMParent::ActorDestroy(this=%p, reason=%d)", this, aWhy);
+  MOZ_ASSERT(!mActorDestroyed);
+  mActorDestroyed = true;
+  if (!mIsShutdown) {
+    // Plugin crash.
+    MOZ_ASSERT(aWhy == AbnormalShutdown);
+    Shutdown();
+  }
+  MOZ_ASSERT(mIsShutdown);
+  RefPtr<ChromiumCDMParent> kungFuDeathGrip(this);
+  if (mContentParent) {
+    mContentParent->ChromiumCDMDestroyed(this);
+    mContentParent = nullptr;
+  }
+  bool abnormalShutdown = (aWhy == AbnormalShutdown);
+  if (abnormalShutdown && mProxy) {
+    RefPtr<Runnable> task =
+      NewRunnableMethod(mProxy, &ChromiumCDMProxy::Terminated);
+    NS_DispatchToMainThread(task);
+  }
+  MaybeDisconnect(abnormalShutdown);
 }
 
 RefPtr<MediaDataDecoder::InitPromise>
 ChromiumCDMParent::InitializeVideoDecoder(
   const gmp::CDMVideoDecoderConfig& aConfig,
   const VideoInfo& aInfo,
   RefPtr<layers::ImageContainer> aImageContainer)
 {
+  if (mIsShutdown) {
+    return MediaDataDecoder::InitPromise::CreateAndReject(
+      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                  RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+      __func__);
+  }
+
   if (!SendInitializeVideoDecoder(aConfig)) {
     return MediaDataDecoder::InitPromise::CreateAndReject(
       MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                   RESULT_DETAIL("Failed to send init video decoder to CDM")),
       __func__);
   }
 
+  mVideoDecoderInitialized = true;
   mImageContainer = aImageContainer;
   mVideoInfo = aInfo;
 
   return mInitVideoDecoderPromise.Ensure(__func__);
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvOnDecoderInitDone(const uint32_t& aStatus)
 {
   GMP_LOG("ChromiumCDMParent::RecvOnDecoderInitDone(this=%p, status=%u)",
           this,
           aStatus);
+  if (mIsShutdown) {
+    MOZ_ASSERT(mInitVideoDecoderPromise.IsEmpty());
+    return IPC_OK();
+  }
   if (aStatus == static_cast<uint32_t>(cdm::kSuccess)) {
     mInitVideoDecoderPromise.ResolveIfExists(TrackInfo::kVideoTrack, __func__);
   } else {
+    mVideoDecoderInitialized = false;
     mInitVideoDecoderPromise.RejectIfExists(
       MediaResult(
         NS_ERROR_DOM_MEDIA_FATAL_ERR,
         RESULT_DETAIL("CDM init decode failed with %" PRIu32, aStatus)),
       __func__);
   }
   return IPC_OK();
 }
 
 RefPtr<MediaDataDecoder::DecodePromise>
 ChromiumCDMParent::DecryptAndDecodeFrame(MediaRawData* aSample)
 {
+  if (mIsShutdown) {
+    return MediaDataDecoder::DecodePromise::CreateAndReject(
+      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                  RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+      __func__);
+  }
+
   CDMInputBuffer buffer;
 
   if (!InitCDMInputBuffer(buffer, aSample)) {
     return MediaDataDecoder::DecodePromise::CreateAndReject(
       MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Failed to init CDM buffer."),
       __func__);
   }
 
@@ -587,57 +682,123 @@ ChromiumCDMParent::DecryptAndDecodeFrame
   }
 
   return mDecodePromise.Ensure(__func__);
 }
 
 RefPtr<MediaDataDecoder::FlushPromise>
 ChromiumCDMParent::FlushVideoDecoder()
 {
+  if (mIsShutdown) {
+    return MediaDataDecoder::FlushPromise::CreateAndReject(
+      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                  RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+      __func__);
+  }
+
   mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   if (!SendResetVideoDecoder()) {
     return MediaDataDecoder::FlushPromise::CreateAndReject(
       MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Failed to send flush to CDM."),
       __func__);
   }
   return mFlushDecoderPromise.Ensure(__func__);
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvResetVideoDecoderComplete()
 {
-  mFlushDecoderPromise.Resolve(true, __func__);
+  if (mIsShutdown) {
+    MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
+    return IPC_OK();
+  }
+  mFlushDecoderPromise.ResolveIfExists(true, __func__);
   return IPC_OK();
 }
 
 RefPtr<MediaDataDecoder::DecodePromise>
 ChromiumCDMParent::Drain()
 {
   MOZ_ASSERT(mDecodePromise.IsEmpty(), "Must wait for decoding to complete");
+  if (mIsShutdown) {
+    return MediaDataDecoder::DecodePromise::CreateAndReject(
+      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                  RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+      __func__);
+  }
 
   RefPtr<MediaDataDecoder::DecodePromise> p = mDecodePromise.Ensure(__func__);
   if (!SendDrain()) {
     mDecodePromise.Resolve(MediaDataDecoder::DecodedData(), __func__);
   }
   return p;
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvDrainComplete()
 {
+  if (mIsShutdown) {
+    MOZ_ASSERT(mDecodePromise.IsEmpty());
+    return IPC_OK();
+  }
   mDecodePromise.ResolveIfExists(MediaDataDecoder::DecodedData(), __func__);
   return IPC_OK();
 }
 RefPtr<ShutdownPromise>
 ChromiumCDMParent::ShutdownVideoDecoder()
 {
+  if (mIsShutdown || !mVideoDecoderInitialized) {
+    return ShutdownPromise::CreateAndResolve(true, __func__);
+  }
   mInitVideoDecoderPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED,
                                           __func__);
-  MOZ_ASSERT(mDecodePromise.IsEmpty());
+  mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
   if (!SendDeinitializeVideoDecoder()) {
     return ShutdownPromise::CreateAndResolve(true, __func__);
   }
+  mVideoDecoderInitialized = false;
   return ShutdownPromise::CreateAndResolve(true, __func__);
 }
 
+void
+ChromiumCDMParent::Shutdown()
+{
+  GMP_LOG("ChromiumCDMParent::Shutdown(this=%p)", this);
+
+  if (mIsShutdown) {
+    return;
+  }
+  mIsShutdown = true;
+
+  for (RefPtr<DecryptJob>& decrypt : mDecrypts) {
+    decrypt->PostResult(AbortedErr);
+  }
+  mDecrypts.Clear();
+
+  if (mVideoDecoderInitialized && !mActorDestroyed) {
+    Unused << SendDeinitializeVideoDecoder();
+    mVideoDecoderInitialized = false;
+  }
+
+  // Note: MediaKeys rejects all outstanding promises when it initiates shutdown.
+  mPromiseToCreateSessionToken.Clear();
+
+  mInitVideoDecoderPromise.RejectIfExists(
+    MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+    __func__);
+  mDecodePromise.RejectIfExists(
+    MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+    __func__);
+  mFlushDecoderPromise.RejectIfExists(
+    MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+    __func__);
+
+  if (!mActorDestroyed) {
+    Unused << SendDestroy();
+  }
+}
+
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/ChromiumCDMParent.h
+++ b/dom/media/gmp/ChromiumCDMParent.h
@@ -70,16 +70,18 @@ public:
     MediaRawData* aSample);
 
   RefPtr<MediaDataDecoder::FlushPromise> FlushVideoDecoder();
 
   RefPtr<MediaDataDecoder::DecodePromise> Drain();
 
   RefPtr<ShutdownPromise> ShutdownVideoDecoder();
 
+  void Shutdown();
+
 protected:
   ~ChromiumCDMParent() {}
 
   ipc::IPCResult Recv__delete__() override;
   ipc::IPCResult RecvOnResolveNewSessionPromise(
     const uint32_t& aPromiseId,
     const nsCString& aSessionId) override;
   ipc::IPCResult RecvOnResolvePromise(const uint32_t& aPromiseId) override;
@@ -130,14 +132,18 @@ protected:
   MozPromiseHolder<MediaDataDecoder::InitPromise> mInitVideoDecoderPromise;
   MozPromiseHolder<MediaDataDecoder::DecodePromise> mDecodePromise;
 
   RefPtr<layers::ImageContainer> mImageContainer;
   VideoInfo mVideoInfo;
   uint64_t mLastStreamOffset = 0;
 
   MozPromiseHolder<MediaDataDecoder::FlushPromise> mFlushDecoderPromise;
+
+  bool mIsShutdown = false;
+  bool mVideoDecoderInitialized = false;
+  bool mActorDestroyed = false;
 };
 
 } // namespace gmp
 } // namespace mozilla
 
 #endif // ChromiumCDMParent_h_
--- a/dom/media/gmp/ChromiumCDMProxy.cpp
+++ b/dom/media/gmp/ChromiumCDMProxy.cpp
@@ -197,16 +197,23 @@ ChromiumCDMProxy::CreateSession(uint32_t
           (int)aSessionType,
           aPromiseId,
           aInitData.Length());
 
   uint32_t sessionType = ToCDMSessionType(aSessionType);
   uint32_t initDataType = ToCDMInitDataType(aInitDataType);
 
   RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+  if (!cdm) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("Null CDM in CreateSession"));
+    return;
+  }
+
   mGMPThread->Dispatch(
     NewRunnableMethod<uint32_t,
                       uint32_t,
                       uint32_t,
                       uint32_t,
                       nsTArray<uint8_t>>(cdm,
                                          &gmp::ChromiumCDMParent::CreateSession,
                                          aCreateSessionToken,
@@ -231,16 +238,23 @@ ChromiumCDMProxy::SetServerCertificate(P
                                        nsTArray<uint8_t>& aCert)
 {
   MOZ_ASSERT(NS_IsMainThread());
   EME_LOG("ChromiumCDMProxy::SetServerCertificate(pid=%u) certLen=%zu",
           aPromiseId,
           aCert.Length());
 
   RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+  if (!cdm) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("Null CDM in SetServerCertificate"));
+    return;
+  }
+
   mGMPThread->Dispatch(NewRunnableMethod<uint32_t, nsTArray<uint8_t>>(
     cdm,
     &gmp::ChromiumCDMParent::SetServerCertificate,
     aPromiseId,
     Move(aCert)));
 }
 
 void
@@ -250,16 +264,22 @@ ChromiumCDMProxy::UpdateSession(const ns
 {
   MOZ_ASSERT(NS_IsMainThread());
   EME_LOG("ChromiumCDMProxy::UpdateSession(sid='%s', pid=%u) responseLen=%zu",
           NS_ConvertUTF16toUTF8(aSessionId).get(),
           aPromiseId,
           aResponse.Length());
 
   RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+  if (!cdm) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("Null CDM in UpdateSession"));
+    return;
+  }
   mGMPThread->Dispatch(
     NewRunnableMethod<nsCString, uint32_t, nsTArray<uint8_t>>(
       cdm,
       &gmp::ChromiumCDMParent::UpdateSession,
       NS_ConvertUTF16toUTF8(aSessionId),
       aPromiseId,
       Move(aResponse)));
 }
@@ -269,16 +289,22 @@ ChromiumCDMProxy::CloseSession(const nsA
                                PromiseId aPromiseId)
 {
   MOZ_ASSERT(NS_IsMainThread());
   EME_LOG("ChromiumCDMProxy::CloseSession(sid='%s', pid=%u)",
           NS_ConvertUTF16toUTF8(aSessionId).get(),
           aPromiseId);
 
   RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+  if (!cdm) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("Null CDM in CloseSession"));
+    return;
+  }
   mGMPThread->Dispatch(NewRunnableMethod<nsCString, uint32_t>(
     cdm,
     &gmp::ChromiumCDMParent::CloseSession,
     NS_ConvertUTF16toUTF8(aSessionId),
     aPromiseId));
 }
 
 void
@@ -286,27 +312,45 @@ ChromiumCDMProxy::RemoveSession(const ns
                                 PromiseId aPromiseId)
 {
   MOZ_ASSERT(NS_IsMainThread());
   EME_LOG("ChromiumCDMProxy::RemoveSession(sid='%s', pid=%u)",
           NS_ConvertUTF16toUTF8(aSessionId).get(),
           aPromiseId);
 
   RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+  if (!cdm) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("Null CDM in RemoveSession"));
+    return;
+  }
   mGMPThread->Dispatch(NewRunnableMethod<nsCString, uint32_t>(
     cdm,
     &gmp::ChromiumCDMParent::RemoveSession,
     NS_ConvertUTF16toUTF8(aSessionId),
     aPromiseId));
 }
 
 void
 ChromiumCDMProxy::Shutdown()
 {
+  MOZ_ASSERT(NS_IsMainThread());
   EME_LOG("ChromiumCDMProxy::Shutdown()");
+  mKeys.Clear();
+  RefPtr<gmp::ChromiumCDMParent> cdm;
+  {
+    MutexAutoLock lock(mCDMMutex);
+    cdm.swap(mCDM);
+  }
+  if (cdm) {
+    nsCOMPtr<nsIRunnable> task =
+      NewRunnableMethod(mCDM, &gmp::ChromiumCDMParent::Shutdown);
+    mGMPThread->Dispatch(task.forget());
+  }
 }
 
 void
 ChromiumCDMProxy::RejectPromise(PromiseId aId,
                                 nsresult aCode,
                                 const nsCString& aReason)
 {
   if (!NS_IsMainThread()) {
@@ -486,16 +530,20 @@ ChromiumCDMProxy::Capabilites()
 {
   return mCapabilites;
 }
 
 RefPtr<DecryptPromise>
 ChromiumCDMProxy::Decrypt(MediaRawData* aSample)
 {
   RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+  if (!cdm) {
+    return DecryptPromise::CreateAndReject(DecryptResult(AbortedErr, aSample),
+                                           __func__);
+  }
   RefPtr<MediaRawData> sample = aSample;
   return InvokeAsync(
     mGMPThread, __func__, [cdm, sample]() { return cdm->Decrypt(sample); });
 }
 
 void
 ChromiumCDMProxy::GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
                                         nsTArray<nsCString>& aSessionIds)