mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			1403 lines
		
	
	
	
		
			50 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1403 lines
		
	
	
	
		
			50 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- 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 "ChromiumCDMCallback.h"
 | 
						|
#include "ChromiumCDMCallbackProxy.h"
 | 
						|
#include "ChromiumCDMProxy.h"
 | 
						|
#include "content_decryption_module.h"
 | 
						|
#include "GMPContentChild.h"
 | 
						|
#include "GMPContentParent.h"
 | 
						|
#include "GMPLog.h"
 | 
						|
#include "GMPService.h"
 | 
						|
#include "GMPUtils.h"
 | 
						|
#include "VideoUtils.h"
 | 
						|
#include "mozilla/dom/MediaKeyMessageEventBinding.h"
 | 
						|
#include "mozilla/gmp/GMPTypes.h"
 | 
						|
#include "mozilla/ScopeExit.h"
 | 
						|
#include "mozilla/StaticPrefs_media.h"
 | 
						|
#include "mozilla/Unused.h"
 | 
						|
#include "AnnexB.h"
 | 
						|
#include "H264.h"
 | 
						|
 | 
						|
#define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead
 | 
						|
 | 
						|
namespace mozilla::gmp {
 | 
						|
 | 
						|
using namespace eme;
 | 
						|
 | 
						|
ChromiumCDMParent::ChromiumCDMParent(GMPContentParent* aContentParent,
 | 
						|
                                     uint32_t aPluginId)
 | 
						|
    : mPluginId(aPluginId),
 | 
						|
      mContentParent(aContentParent),
 | 
						|
      mVideoShmemLimit(StaticPrefs::media_eme_chromium_api_video_shmems())
 | 
						|
#ifdef DEBUG
 | 
						|
      ,
 | 
						|
      mGMPThread(aContentParent->GMPEventTarget())
 | 
						|
#endif
 | 
						|
{
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG(
 | 
						|
      "ChromiumCDMParent::ChromiumCDMParent(this=%p, contentParent=%p, "
 | 
						|
      "id=%" PRIu32 ")",
 | 
						|
      this, aContentParent, aPluginId);
 | 
						|
}
 | 
						|
 | 
						|
RefPtr<ChromiumCDMParent::InitPromise> ChromiumCDMParent::Init(
 | 
						|
    ChromiumCDMCallback* aCDMCallback, bool aAllowDistinctiveIdentifier,
 | 
						|
    bool aAllowPersistentState, nsIEventTarget* aMainThread) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG(
 | 
						|
      "ChromiumCDMParent::Init(this=%p) shutdown=%s abormalShutdown=%s "
 | 
						|
      "actorDestroyed=%s",
 | 
						|
      this, mIsShutdown ? "true" : "false",
 | 
						|
      mAbnormalShutdown ? "true" : "false", mActorDestroyed ? "true" : "false");
 | 
						|
  if (!aCDMCallback || !aMainThread) {
 | 
						|
    GMP_LOG_DEBUG(
 | 
						|
        "ChromiumCDMParent::Init(this=%p) failed "
 | 
						|
        "nullCallback=%s nullMainThread=%s",
 | 
						|
        this, !aCDMCallback ? "true" : "false",
 | 
						|
        !aMainThread ? "true" : "false");
 | 
						|
 | 
						|
    return ChromiumCDMParent::InitPromise::CreateAndReject(
 | 
						|
        MediaResult(NS_ERROR_FAILURE,
 | 
						|
                    nsPrintfCString("ChromiumCDMParent::Init() failed "
 | 
						|
                                    "nullCallback=%s nullMainThread=%s",
 | 
						|
                                    !aCDMCallback ? "true" : "false",
 | 
						|
                                    !aMainThread ? "true" : "false")),
 | 
						|
        __func__);
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<ChromiumCDMParent::InitPromise> promise =
 | 
						|
      mInitPromise.Ensure(__func__);
 | 
						|
  RefPtr<ChromiumCDMParent> self = this;
 | 
						|
  SendInit(aAllowDistinctiveIdentifier, aAllowPersistentState)
 | 
						|
      ->Then(
 | 
						|
          GetCurrentSerialEventTarget(), __func__,
 | 
						|
          [self, aCDMCallback](bool aSuccess) {
 | 
						|
            if (!aSuccess) {
 | 
						|
              GMP_LOG_DEBUG(
 | 
						|
                  "ChromiumCDMParent::Init() failed with callback from "
 | 
						|
                  "child indicating CDM failed init");
 | 
						|
              self->mInitPromise.RejectIfExists(
 | 
						|
                  MediaResult(NS_ERROR_FAILURE,
 | 
						|
                              "ChromiumCDMParent::Init() failed with callback "
 | 
						|
                              "from child indicating CDM failed init"),
 | 
						|
                  __func__);
 | 
						|
              return;
 | 
						|
            }
 | 
						|
            GMP_LOG_DEBUG(
 | 
						|
                "ChromiumCDMParent::Init() succeeded with callback from child");
 | 
						|
            self->mCDMCallback = aCDMCallback;
 | 
						|
            self->mInitPromise.ResolveIfExists(true /* unused */, __func__);
 | 
						|
          },
 | 
						|
          [self](ResponseRejectReason&& aReason) {
 | 
						|
            RefPtr<gmp::GeckoMediaPluginService> service =
 | 
						|
                gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
 | 
						|
            bool xpcomWillShutdown =
 | 
						|
                service && service->XPCOMWillShutdownReceived();
 | 
						|
            GMP_LOG_DEBUG(
 | 
						|
                "ChromiumCDMParent::Init(this=%p) failed "
 | 
						|
                "shutdown=%s cdmCrash=%s actorDestroyed=%s "
 | 
						|
                "browserShutdown=%s promiseRejectReason=%d",
 | 
						|
                self.get(), self->mIsShutdown ? "true" : "false",
 | 
						|
                self->mAbnormalShutdown ? "true" : "false",
 | 
						|
                self->mActorDestroyed ? "true" : "false",
 | 
						|
                xpcomWillShutdown ? "true" : "false",
 | 
						|
                static_cast<int>(aReason));
 | 
						|
            self->mInitPromise.RejectIfExists(
 | 
						|
                MediaResult(
 | 
						|
                    NS_ERROR_FAILURE,
 | 
						|
                    nsPrintfCString("ChromiumCDMParent::Init() failed "
 | 
						|
                                    "shutdown=%s cdmCrash=%s actorDestroyed=%s "
 | 
						|
                                    "browserShutdown=%s promiseRejectReason=%d",
 | 
						|
                                    self->mIsShutdown ? "true" : "false",
 | 
						|
                                    self->mAbnormalShutdown ? "true" : "false",
 | 
						|
                                    self->mActorDestroyed ? "true" : "false",
 | 
						|
                                    xpcomWillShutdown ? "true" : "false",
 | 
						|
                                    static_cast<int>(aReason))),
 | 
						|
                __func__);
 | 
						|
          });
 | 
						|
  return promise;
 | 
						|
}
 | 
						|
 | 
						|
void ChromiumCDMParent::CreateSession(uint32_t aCreateSessionToken,
 | 
						|
                                      uint32_t aSessionType,
 | 
						|
                                      uint32_t aInitDataType,
 | 
						|
                                      uint32_t aPromiseId,
 | 
						|
                                      const nsTArray<uint8_t>& aInitData) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::CreateSession(this=%p)", this);
 | 
						|
  if (mIsShutdown) {
 | 
						|
    RejectPromiseShutdown(aPromiseId);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  if (!SendCreateSessionAndGenerateRequest(aPromiseId, aSessionType,
 | 
						|
                                           aInitDataType, aInitData)) {
 | 
						|
    RejectPromiseWithStateError(
 | 
						|
        aPromiseId, "Failed to send generateRequest to CDM process."_ns);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  mPromiseToCreateSessionToken.InsertOrUpdate(aPromiseId, aCreateSessionToken);
 | 
						|
}
 | 
						|
 | 
						|
void ChromiumCDMParent::LoadSession(uint32_t aPromiseId, uint32_t aSessionType,
 | 
						|
                                    nsString aSessionId) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::LoadSession(this=%p, pid=%" PRIu32
 | 
						|
                ", type=%" PRIu32 ", sid=%s)",
 | 
						|
                this, aPromiseId, aSessionType,
 | 
						|
                NS_ConvertUTF16toUTF8(aSessionId).get());
 | 
						|
  if (mIsShutdown) {
 | 
						|
    RejectPromiseShutdown(aPromiseId);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  if (!SendLoadSession(aPromiseId, aSessionType,
 | 
						|
                       NS_ConvertUTF16toUTF8(aSessionId))) {
 | 
						|
    RejectPromiseWithStateError(
 | 
						|
        aPromiseId, "Failed to send loadSession to CDM process."_ns);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void ChromiumCDMParent::SetServerCertificate(uint32_t aPromiseId,
 | 
						|
                                             const nsTArray<uint8_t>& aCert) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::SetServerCertificate(this=%p)", this);
 | 
						|
  if (mIsShutdown) {
 | 
						|
    RejectPromiseShutdown(aPromiseId);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  if (!SendSetServerCertificate(aPromiseId, aCert)) {
 | 
						|
    RejectPromiseWithStateError(
 | 
						|
        aPromiseId, "Failed to send setServerCertificate to CDM process"_ns);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void ChromiumCDMParent::UpdateSession(const nsCString& aSessionId,
 | 
						|
                                      uint32_t aPromiseId,
 | 
						|
                                      const nsTArray<uint8_t>& aResponse) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::UpdateSession(this=%p)", this);
 | 
						|
  if (mIsShutdown) {
 | 
						|
    RejectPromiseShutdown(aPromiseId);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  if (!SendUpdateSession(aPromiseId, aSessionId, aResponse)) {
 | 
						|
    RejectPromiseWithStateError(
 | 
						|
        aPromiseId, "Failed to send updateSession to CDM process"_ns);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void ChromiumCDMParent::CloseSession(const nsCString& aSessionId,
 | 
						|
                                     uint32_t aPromiseId) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::CloseSession(this=%p)", this);
 | 
						|
  if (mIsShutdown) {
 | 
						|
    RejectPromiseShutdown(aPromiseId);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  if (!SendCloseSession(aPromiseId, aSessionId)) {
 | 
						|
    RejectPromiseWithStateError(
 | 
						|
        aPromiseId, "Failed to send closeSession to CDM process"_ns);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void ChromiumCDMParent::RemoveSession(const nsCString& aSessionId,
 | 
						|
                                      uint32_t aPromiseId) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::RemoveSession(this=%p)", this);
 | 
						|
  if (mIsShutdown) {
 | 
						|
    RejectPromiseShutdown(aPromiseId);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  if (!SendRemoveSession(aPromiseId, aSessionId)) {
 | 
						|
    RejectPromiseWithStateError(
 | 
						|
        aPromiseId, "Failed to send removeSession to CDM process"_ns);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void ChromiumCDMParent::NotifyOutputProtectionStatus(bool aSuccess,
 | 
						|
                                                     uint32_t aLinkMask,
 | 
						|
                                                     uint32_t aProtectionMask) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::NotifyOutputProtectionStatus(this=%p)",
 | 
						|
                this);
 | 
						|
  if (mIsShutdown) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  const bool haveCachedValue = mOutputProtectionLinkMask.isSome();
 | 
						|
  if (mAwaitingOutputProtectionInformation && !aSuccess) {
 | 
						|
    MOZ_DIAGNOSTIC_ASSERT(
 | 
						|
        !haveCachedValue,
 | 
						|
        "Should not have a cached value if we're still awaiting infomation");
 | 
						|
    // We're awaiting info and don't yet have a cached value, and the check
 | 
						|
    // failed, don't cache the result, just forward the failure.
 | 
						|
    CompleteQueryOutputProtectionStatus(false, aLinkMask, aProtectionMask);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  if (!mAwaitingOutputProtectionInformation && haveCachedValue && !aSuccess) {
 | 
						|
    // We're not awaiting info, already have a cached value, and the check
 | 
						|
    // failed. Ignore this, we'll update our info from any future successful
 | 
						|
    // checks.
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  MOZ_ASSERT(aSuccess, "Failed checks should be handled by this point");
 | 
						|
  // Update our protection information.
 | 
						|
  mOutputProtectionLinkMask = Some(aLinkMask);
 | 
						|
 | 
						|
  if (mAwaitingOutputProtectionInformation) {
 | 
						|
    // If we have an outstanding query, service that query with this
 | 
						|
    // information.
 | 
						|
    mAwaitingOutputProtectionInformation = false;
 | 
						|
    MOZ_ASSERT(!haveCachedValue,
 | 
						|
               "If we were waiting on information, we shouldn't have yet "
 | 
						|
               "cached a value");
 | 
						|
    CompleteQueryOutputProtectionStatus(true, mOutputProtectionLinkMask.value(),
 | 
						|
                                        aProtectionMask);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void ChromiumCDMParent::CompleteQueryOutputProtectionStatus(
 | 
						|
    bool aSuccess, uint32_t aLinkMask, uint32_t aProtectionMask) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG(
 | 
						|
      "ChromiumCDMParent::CompleteQueryOutputProtectionStatus(this=%p) "
 | 
						|
      "mIsShutdown=%s aSuccess=%s aLinkMask=%" PRIu32,
 | 
						|
      this, mIsShutdown ? "true" : "false", aSuccess ? "true" : "false",
 | 
						|
      aLinkMask);
 | 
						|
  if (mIsShutdown) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  Unused << SendCompleteQueryOutputProtectionStatus(aSuccess, aLinkMask,
 | 
						|
                                                    aProtectionMask);
 | 
						|
}
 | 
						|
 | 
						|
static cdm::HdcpVersion ToCDMHdcpVersion(
 | 
						|
    const dom::HDCPVersion& aMinHdcpVersion) {
 | 
						|
  switch (aMinHdcpVersion) {
 | 
						|
    case dom::HDCPVersion::_1_0:
 | 
						|
      return cdm::HdcpVersion::kHdcpVersion1_0;
 | 
						|
    case dom::HDCPVersion::_1_1:
 | 
						|
      return cdm::HdcpVersion::kHdcpVersion1_1;
 | 
						|
    case dom::HDCPVersion::_1_2:
 | 
						|
      return cdm::HdcpVersion::kHdcpVersion1_2;
 | 
						|
    case dom::HDCPVersion::_1_3:
 | 
						|
      return cdm::HdcpVersion::kHdcpVersion1_3;
 | 
						|
    case dom::HDCPVersion::_1_4:
 | 
						|
      return cdm::HdcpVersion::kHdcpVersion1_4;
 | 
						|
    case dom::HDCPVersion::_2_0:
 | 
						|
      return cdm::HdcpVersion::kHdcpVersion2_0;
 | 
						|
    case dom::HDCPVersion::_2_1:
 | 
						|
      return cdm::HdcpVersion::kHdcpVersion2_1;
 | 
						|
    case dom::HDCPVersion::_2_2:
 | 
						|
      return cdm::HdcpVersion::kHdcpVersion2_2;
 | 
						|
    case dom::HDCPVersion::_2_3:
 | 
						|
      return cdm::HdcpVersion::kHdcpVersion2_3;
 | 
						|
    // When adding another version remember to update GMPMessageUtils so that we
 | 
						|
    // can serialize it correctly and have correct bounds on the enum!
 | 
						|
    default:
 | 
						|
      MOZ_ASSERT_UNREACHABLE("Unexpected HDCP version!");
 | 
						|
      return cdm::HdcpVersion::kHdcpVersionNone;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void ChromiumCDMParent::GetStatusForPolicy(
 | 
						|
    uint32_t aPromiseId, const dom::HDCPVersion& aMinHdcpVersion) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::GetStatusForPolicy(this=%p)", this);
 | 
						|
  if (mIsShutdown) {
 | 
						|
    RejectPromiseShutdown(aPromiseId);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  if (!SendGetStatusForPolicy(aPromiseId, ToCDMHdcpVersion(aMinHdcpVersion))) {
 | 
						|
    RejectPromiseWithStateError(
 | 
						|
        aPromiseId, "Failed to send getStatusForPolicy to CDM process"_ns);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bool ChromiumCDMParent::InitCDMInputBuffer(gmp::CDMInputBuffer& aBuffer,
 | 
						|
                                           MediaRawData* aSample) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  const CryptoSample& crypto = aSample->mCrypto;
 | 
						|
  if (crypto.mEncryptedSizes.Length() != crypto.mPlainSizes.Length()) {
 | 
						|
    GMP_LOG_DEBUG("InitCDMInputBuffer clear/cipher subsamples don't match");
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  Shmem shmem;
 | 
						|
  if (!AllocShmem(aSample->Size(), &shmem)) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  memcpy(shmem.get<uint8_t>(), aSample->Data(), aSample->Size());
 | 
						|
  cdm::EncryptionScheme encryptionScheme = cdm::EncryptionScheme::kUnencrypted;
 | 
						|
  switch (crypto.mCryptoScheme) {
 | 
						|
    case CryptoScheme::None:
 | 
						|
      break;  // Default to none
 | 
						|
    case CryptoScheme::Cenc:
 | 
						|
      encryptionScheme = cdm::EncryptionScheme::kCenc;
 | 
						|
      break;
 | 
						|
    case CryptoScheme::Cbcs:
 | 
						|
    case CryptoScheme::Cbcs_1_9:
 | 
						|
      encryptionScheme = cdm::EncryptionScheme::kCbcs;
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      GMP_LOG_DEBUG(
 | 
						|
          "InitCDMInputBuffer got unexpected encryption scheme with "
 | 
						|
          "value of %" PRIu8 ". Treating as no encryption.",
 | 
						|
          static_cast<uint8_t>(crypto.mCryptoScheme));
 | 
						|
      MOZ_ASSERT_UNREACHABLE("Should not have unrecognized encryption type");
 | 
						|
      break;
 | 
						|
  }
 | 
						|
 | 
						|
  const nsTArray<uint8_t>& iv = encryptionScheme != cdm::EncryptionScheme::kCbcs
 | 
						|
                                    ? crypto.mIV
 | 
						|
                                    : crypto.mConstantIV;
 | 
						|
  aBuffer = gmp::CDMInputBuffer(
 | 
						|
      shmem, crypto.mKeyId, iv, aSample->mTime.ToMicroseconds(),
 | 
						|
      aSample->mDuration.ToMicroseconds(), crypto.mPlainSizes,
 | 
						|
      crypto.mEncryptedSizes, crypto.mCryptByteBlock, crypto.mSkipByteBlock,
 | 
						|
      encryptionScheme);
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool ChromiumCDMParent::SendBufferToCDM(uint32_t aSizeInBytes) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::SendBufferToCDM() size=%" PRIu32,
 | 
						|
                aSizeInBytes);
 | 
						|
  Shmem shmem;
 | 
						|
  if (!AllocShmem(aSizeInBytes, &shmem)) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  if (!SendGiveBuffer(std::move(shmem))) {
 | 
						|
    DeallocShmem(shmem);
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
RefPtr<DecryptPromise> ChromiumCDMParent::Decrypt(MediaRawData* aSample) {
 | 
						|
  if (mIsShutdown) {
 | 
						|
    MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
    return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
 | 
						|
                                           __func__);
 | 
						|
  }
 | 
						|
  CDMInputBuffer buffer;
 | 
						|
  if (!InitCDMInputBuffer(buffer, aSample)) {
 | 
						|
    return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
 | 
						|
                                           __func__);
 | 
						|
  }
 | 
						|
  // Send a buffer to the CDM to store the output. The CDM will either fill
 | 
						|
  // it with the decrypted sample and return it, or deallocate it on failure.
 | 
						|
  if (!SendBufferToCDM(aSample->Size())) {
 | 
						|
    DeallocShmem(buffer.mData());
 | 
						|
    return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
 | 
						|
                                           __func__);
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<DecryptJob> job = new DecryptJob(aSample);
 | 
						|
  if (!SendDecrypt(job->mId, buffer)) {
 | 
						|
    GMP_LOG_DEBUG(
 | 
						|
        "ChromiumCDMParent::Decrypt(this=%p) failed to send decrypt message",
 | 
						|
        this);
 | 
						|
    DeallocShmem(buffer.mData());
 | 
						|
    return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
 | 
						|
                                           __func__);
 | 
						|
  }
 | 
						|
  RefPtr<DecryptPromise> promise = job->Ensure();
 | 
						|
  mDecrypts.AppendElement(job);
 | 
						|
  return promise;
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::Recv__delete__() {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  MOZ_ASSERT(mIsShutdown);
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::Recv__delete__(this=%p)", this);
 | 
						|
  if (mContentParent) {
 | 
						|
    mContentParent->ChromiumCDMDestroyed(this);
 | 
						|
    mContentParent = nullptr;
 | 
						|
  }
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvOnResolvePromiseWithKeyStatus(
 | 
						|
    const uint32_t& aPromiseId, const uint32_t& aKeyStatus) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG(
 | 
						|
      "ChromiumCDMParent::RecvOnResolvePromiseWithKeyStatus(this=%p, "
 | 
						|
      "pid=%" PRIu32 ", keystatus=%" PRIu32 ")",
 | 
						|
      this, aPromiseId, aKeyStatus);
 | 
						|
  if (!mCDMCallback || mIsShutdown) {
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  mCDMCallback->ResolvePromiseWithKeyStatus(aPromiseId, aKeyStatus);
 | 
						|
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvOnResolveNewSessionPromise(
 | 
						|
    const uint32_t& aPromiseId, const nsCString& aSessionId) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG(
 | 
						|
      "ChromiumCDMParent::RecvOnResolveNewSessionPromise(this=%p, pid=%" PRIu32
 | 
						|
      ", sid=%s)",
 | 
						|
      this, aPromiseId, aSessionId.get());
 | 
						|
  if (!mCDMCallback || mIsShutdown) {
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  Maybe<uint32_t> token = mPromiseToCreateSessionToken.Extract(aPromiseId);
 | 
						|
  if (token.isNothing()) {
 | 
						|
    RejectPromiseWithStateError(aPromiseId,
 | 
						|
                                "Lost session token for new session."_ns);
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  mCDMCallback->SetSessionId(token.value(), aSessionId);
 | 
						|
 | 
						|
  ResolvePromise(aPromiseId);
 | 
						|
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvResolveLoadSessionPromise(
 | 
						|
    const uint32_t& aPromiseId, const bool& aSuccessful) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG(
 | 
						|
      "ChromiumCDMParent::RecvResolveLoadSessionPromise(this=%p, pid=%" PRIu32
 | 
						|
      ", successful=%d)",
 | 
						|
      this, aPromiseId, aSuccessful);
 | 
						|
  if (!mCDMCallback || mIsShutdown) {
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  mCDMCallback->ResolveLoadSessionPromise(aPromiseId, aSuccessful);
 | 
						|
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
void ChromiumCDMParent::ResolvePromise(uint32_t aPromiseId) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::ResolvePromise(this=%p, pid=%" PRIu32 ")",
 | 
						|
                this, aPromiseId);
 | 
						|
 | 
						|
  // Note: The MediaKeys rejects all pending DOM promises when it
 | 
						|
  // initiates shutdown.
 | 
						|
  if (!mCDMCallback || mIsShutdown) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  mCDMCallback->ResolvePromise(aPromiseId);
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvOnResolvePromise(
 | 
						|
    const uint32_t& aPromiseId) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  ResolvePromise(aPromiseId);
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
void ChromiumCDMParent::RejectPromise(uint32_t aPromiseId,
 | 
						|
                                      ErrorResult&& aException,
 | 
						|
                                      const nsCString& aErrorMessage) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::RejectPromise(this=%p, pid=%" PRIu32 ")",
 | 
						|
                this, aPromiseId);
 | 
						|
  // Note: The MediaKeys rejects all pending DOM promises when it
 | 
						|
  // initiates shutdown.
 | 
						|
  if (!mCDMCallback || mIsShutdown) {
 | 
						|
    // Suppress the exception as it will not be explicitly handled due to the
 | 
						|
    // early return.
 | 
						|
    aException.SuppressException();
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  mCDMCallback->RejectPromise(aPromiseId, std::move(aException), aErrorMessage);
 | 
						|
}
 | 
						|
 | 
						|
void ChromiumCDMParent::RejectPromiseShutdown(uint32_t aPromiseId) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  RejectPromiseWithStateError(aPromiseId, "CDM is shutdown"_ns);
 | 
						|
}
 | 
						|
 | 
						|
void ChromiumCDMParent::RejectPromiseWithStateError(
 | 
						|
    uint32_t aPromiseId, const nsCString& aErrorMessage) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  ErrorResult rv;
 | 
						|
  rv.ThrowInvalidStateError(aErrorMessage);
 | 
						|
  RejectPromise(aPromiseId, std::move(rv), aErrorMessage);
 | 
						|
}
 | 
						|
 | 
						|
static ErrorResult ToErrorResult(uint32_t aException,
 | 
						|
                                 const nsCString& aErrorMessage) {
 | 
						|
  // XXXbz could we have a CopyableErrorResult sent to us with a better error
 | 
						|
  // message?
 | 
						|
  ErrorResult rv;
 | 
						|
  switch (static_cast<cdm::Exception>(aException)) {
 | 
						|
    case cdm::Exception::kExceptionNotSupportedError:
 | 
						|
      rv.ThrowNotSupportedError(aErrorMessage);
 | 
						|
      break;
 | 
						|
    case cdm::Exception::kExceptionInvalidStateError:
 | 
						|
      rv.ThrowInvalidStateError(aErrorMessage);
 | 
						|
      break;
 | 
						|
    case cdm::Exception::kExceptionTypeError:
 | 
						|
      rv.ThrowTypeError(aErrorMessage);
 | 
						|
      break;
 | 
						|
    case cdm::Exception::kExceptionQuotaExceededError:
 | 
						|
      rv.ThrowQuotaExceededError(aErrorMessage);
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      MOZ_ASSERT_UNREACHABLE("Invalid cdm::Exception enum value.");
 | 
						|
      // Note: Unique placeholder.
 | 
						|
      rv.ThrowTimeoutError(aErrorMessage);
 | 
						|
  };
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvOnRejectPromise(
 | 
						|
    const uint32_t& aPromiseId, const uint32_t& aException,
 | 
						|
    const uint32_t& aSystemCode, const nsCString& aErrorMessage) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  RejectPromise(aPromiseId, ToErrorResult(aException, aErrorMessage),
 | 
						|
                aErrorMessage);
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvOnSessionMessage(
 | 
						|
    const nsCString& aSessionId, const uint32_t& aMessageType,
 | 
						|
    nsTArray<uint8_t>&& aMessage) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionMessage(this=%p, sid=%s)",
 | 
						|
                this, aSessionId.get());
 | 
						|
  if (!mCDMCallback || mIsShutdown) {
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  mCDMCallback->SessionMessage(aSessionId, aMessageType, std::move(aMessage));
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvOnSessionKeysChange(
 | 
						|
    const nsCString& aSessionId, nsTArray<CDMKeyInformation>&& aKeysInfo) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionKeysChange(this=%p)", this);
 | 
						|
  if (!mCDMCallback || mIsShutdown) {
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  mCDMCallback->SessionKeysChange(aSessionId, std::move(aKeysInfo));
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvOnExpirationChange(
 | 
						|
    const nsCString& aSessionId, const double& aSecondsSinceEpoch) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnExpirationChange(this=%p) time=%lf",
 | 
						|
                this, aSecondsSinceEpoch);
 | 
						|
  if (!mCDMCallback || mIsShutdown) {
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  mCDMCallback->ExpirationChange(aSessionId, aSecondsSinceEpoch);
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvOnSessionClosed(
 | 
						|
    const nsCString& aSessionId) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionClosed(this=%p)", this);
 | 
						|
  if (!mCDMCallback || mIsShutdown) {
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  mCDMCallback->SessionClosed(aSessionId);
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvOnQueryOutputProtectionStatus() {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG(
 | 
						|
      "ChromiumCDMParent::RecvOnQueryOutputProtectionStatus(this=%p) "
 | 
						|
      "mIsShutdown=%s mCDMCallback=%s mAwaitingOutputProtectionInformation=%s",
 | 
						|
      this, mIsShutdown ? "true" : "false", mCDMCallback ? "true" : "false",
 | 
						|
      mAwaitingOutputProtectionInformation ? "true" : "false");
 | 
						|
  if (mIsShutdown) {
 | 
						|
    // We're shutdown, don't try to service the query.
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
  if (!mCDMCallback) {
 | 
						|
    // We don't have a callback. We're not yet outputting anything so can report
 | 
						|
    // we're safe.
 | 
						|
    CompleteQueryOutputProtectionStatus(true, uint32_t{}, uint32_t{});
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  if (mOutputProtectionLinkMask.isSome()) {
 | 
						|
    MOZ_DIAGNOSTIC_ASSERT(
 | 
						|
        !mAwaitingOutputProtectionInformation,
 | 
						|
        "If we have a cached value we should not be awaiting information");
 | 
						|
    // We have a cached value, use that.
 | 
						|
    CompleteQueryOutputProtectionStatus(true, mOutputProtectionLinkMask.value(),
 | 
						|
                                        uint32_t{});
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  // We need to call up the stack to get the info. The CDM proxy will finish
 | 
						|
  // the request via `NotifyOutputProtectionStatus`.
 | 
						|
  mAwaitingOutputProtectionInformation = true;
 | 
						|
  mCDMCallback->QueryOutputProtectionStatus();
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
DecryptStatus ToDecryptStatus(uint32_t aStatus) {
 | 
						|
  switch (static_cast<cdm::Status>(aStatus)) {
 | 
						|
    case cdm::kSuccess:
 | 
						|
      return DecryptStatus::Ok;
 | 
						|
    case cdm::kNoKey:
 | 
						|
      return DecryptStatus::NoKeyErr;
 | 
						|
    default:
 | 
						|
      return DecryptStatus::GenericErr;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvDecryptFailed(const uint32_t& aId,
 | 
						|
                                                    const uint32_t& aStatus) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecryptFailed(this=%p, id=%" PRIu32
 | 
						|
                ", status=%" PRIu32 ")",
 | 
						|
                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));
 | 
						|
      mDecrypts.RemoveElementAt(i);
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvDecrypted(const uint32_t& aId,
 | 
						|
                                                const uint32_t& aStatus,
 | 
						|
                                                ipc::Shmem&& aShmem) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecrypted(this=%p, id=%" PRIu32
 | 
						|
                ", status=%" PRIu32 ")",
 | 
						|
                this, aId, aStatus);
 | 
						|
 | 
						|
  // We must deallocate the shmem once we've copied the result out of it
 | 
						|
  // in PostResult below.
 | 
						|
  auto autoDeallocateShmem = MakeScopeExit([&, this] { DeallocShmem(aShmem); });
 | 
						|
 | 
						|
  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),
 | 
						|
          Span<const uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>()));
 | 
						|
      mDecrypts.RemoveElementAt(i);
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvIncreaseShmemPoolSize() {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("%s(this=%p) limit=%" PRIu32 " active=%" PRIu32, __func__, this,
 | 
						|
                mVideoShmemLimit, mVideoShmemsActive);
 | 
						|
 | 
						|
  // Put an upper limit on the number of shmems we tolerate the CDM asking
 | 
						|
  // for, to prevent a memory blow-out. In practice, we expect the CDM to
 | 
						|
  // need less than 5, but some encodings require more.
 | 
						|
  // We'd expect CDMs to not have video frames larger than 720p-1080p
 | 
						|
  // (due to DRM robustness requirements), which is about 1.5MB-3MB per
 | 
						|
  // frame.
 | 
						|
  if (mVideoShmemLimit > 50) {
 | 
						|
    mDecodePromise.RejectIfExists(
 | 
						|
        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
 | 
						|
                    RESULT_DETAIL("Failled to ensure CDM has enough shmems.")),
 | 
						|
        __func__);
 | 
						|
    Shutdown();
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
  mVideoShmemLimit++;
 | 
						|
 | 
						|
  EnsureSufficientShmems(mVideoFrameBufferSize);
 | 
						|
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
bool ChromiumCDMParent::PurgeShmems() {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG(
 | 
						|
      "ChromiumCDMParent::PurgeShmems(this=%p) frame_size=%zu"
 | 
						|
      " limit=%" PRIu32 " active=%" PRIu32,
 | 
						|
      this, mVideoFrameBufferSize, mVideoShmemLimit, mVideoShmemsActive);
 | 
						|
 | 
						|
  if (mVideoShmemsActive == 0) {
 | 
						|
    // We haven't allocated any shmems, nothing to do here.
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
  if (!SendPurgeShmems()) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  mVideoShmemsActive = 0;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool ChromiumCDMParent::EnsureSufficientShmems(size_t aVideoFrameSize) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG(
 | 
						|
      "ChromiumCDMParent::EnsureSufficientShmems(this=%p) "
 | 
						|
      "size=%zu expected_size=%zu limit=%" PRIu32 " active=%" PRIu32,
 | 
						|
      this, aVideoFrameSize, mVideoFrameBufferSize, mVideoShmemLimit,
 | 
						|
      mVideoShmemsActive);
 | 
						|
 | 
						|
  // The Chromium CDM API requires us to implement a synchronous
 | 
						|
  // interface to supply buffers to the CDM for it to write decrypted samples
 | 
						|
  // into. We want our buffers to be backed by shmems, in order to reduce
 | 
						|
  // the overhead of transferring decoded frames. However due to sandboxing
 | 
						|
  // restrictions, the CDM process cannot allocate shmems itself.
 | 
						|
  // We don't want to be doing synchronous IPC to request shmems from the
 | 
						|
  // content process, nor do we want to have to do intr IPC or make async
 | 
						|
  // IPC conform to the sync allocation interface. So instead we have the
 | 
						|
  // content process pre-allocate a set of shmems and give them to the CDM
 | 
						|
  // process in advance of them being needed.
 | 
						|
  //
 | 
						|
  // When the CDM needs to allocate a buffer for storing a decoded video
 | 
						|
  // frame, the CDM host gives it one of these shmems' buffers. When this
 | 
						|
  // is sent back to the content process, we upload it to a GPU surface,
 | 
						|
  // and send the shmem back to the CDM process so it can reuse it.
 | 
						|
  //
 | 
						|
  // Normally the CDM won't allocate more than one buffer at once, but
 | 
						|
  // we've seen cases where it allocates multiple buffers, returns one and
 | 
						|
  // holds onto the rest. So we need to ensure we have several extra
 | 
						|
  // shmems pre-allocated for the CDM. This threshold is set by the pref
 | 
						|
  // media.eme.chromium-api.video-shmems.
 | 
						|
  //
 | 
						|
  // We also have a failure recovery mechanism; if the CDM asks for more
 | 
						|
  // buffers than we have shmem's available, ChromiumCDMChild gives the
 | 
						|
  // CDM a non-shared memory buffer, and returns the frame to the parent
 | 
						|
  // in an nsTArray<uint8_t> instead of a shmem. The child then sends a
 | 
						|
  // message to the parent asking it to increase the number of shmems in
 | 
						|
  // the pool. Via this mechanism we should recover from incorrectly
 | 
						|
  // predicting how many shmems to pre-allocate.
 | 
						|
  //
 | 
						|
  // At decoder start up, we guess how big the shmems need to be based on
 | 
						|
  // the video frame dimensions. If we guess wrong, the CDM will follow
 | 
						|
  // the non-shmem path, and we'll re-create the shmems of the correct size.
 | 
						|
  // This meanns we can recover from guessing the shmem size wrong.
 | 
						|
  // We must re-take this path after every decoder de-init/re-init, as the
 | 
						|
  // frame sizes should change every time we switch video stream.
 | 
						|
 | 
						|
  if (mVideoFrameBufferSize < aVideoFrameSize) {
 | 
						|
    if (!PurgeShmems()) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    mVideoFrameBufferSize = aVideoFrameSize;
 | 
						|
  }
 | 
						|
 | 
						|
  while (mVideoShmemsActive < mVideoShmemLimit) {
 | 
						|
    if (!SendBufferToCDM(mVideoFrameBufferSize)) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    mVideoShmemsActive++;
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvDecodedData(const CDMVideoFrame& aFrame,
 | 
						|
                                                  nsTArray<uint8_t>&& aData) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodedData(this=%p) time=%" PRId64,
 | 
						|
                this, aFrame.mTimestamp());
 | 
						|
 | 
						|
  if (mIsShutdown || mDecodePromise.IsEmpty()) {
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  if (!EnsureSufficientShmems(aData.Length())) {
 | 
						|
    mDecodePromise.RejectIfExists(
 | 
						|
        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
 | 
						|
                    RESULT_DETAIL("Failled to ensure CDM has enough shmems.")),
 | 
						|
        __func__);
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<VideoData> v = CreateVideoFrame(aFrame, aData);
 | 
						|
  if (!v) {
 | 
						|
    mDecodePromise.RejectIfExists(
 | 
						|
        MediaResult(NS_ERROR_OUT_OF_MEMORY,
 | 
						|
                    RESULT_DETAIL("Can't create VideoData")),
 | 
						|
        __func__);
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  ReorderAndReturnOutput(std::move(v));
 | 
						|
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvDecodedShmem(const CDMVideoFrame& aFrame,
 | 
						|
                                                   ipc::Shmem&& aShmem) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodedShmem(this=%p) time=%" PRId64
 | 
						|
                " duration=%" PRId64,
 | 
						|
                this, aFrame.mTimestamp(), aFrame.mDuration());
 | 
						|
 | 
						|
  // On failure we need to deallocate the shmem we're to return to the
 | 
						|
  // CDM. On success we return it to the CDM to be reused.
 | 
						|
  auto autoDeallocateShmem =
 | 
						|
      MakeScopeExit([&, this] { this->DeallocShmem(aShmem); });
 | 
						|
 | 
						|
  if (mIsShutdown || mDecodePromise.IsEmpty()) {
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<VideoData> v = CreateVideoFrame(
 | 
						|
      aFrame, Span<uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>()));
 | 
						|
  if (!v) {
 | 
						|
    mDecodePromise.RejectIfExists(
 | 
						|
        MediaResult(NS_ERROR_OUT_OF_MEMORY,
 | 
						|
                    RESULT_DETAIL("Can't create VideoData")),
 | 
						|
        __func__);
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  // Return the shmem to the CDM so the shmem can be reused to send us
 | 
						|
  // another frame.
 | 
						|
  if (!SendGiveBuffer(std::move(aShmem))) {
 | 
						|
    mDecodePromise.RejectIfExists(
 | 
						|
        MediaResult(NS_ERROR_OUT_OF_MEMORY,
 | 
						|
                    RESULT_DETAIL("Can't return shmem to CDM process")),
 | 
						|
        __func__);
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  // Don't need to deallocate the shmem since the CDM process is responsible
 | 
						|
  // for it again.
 | 
						|
  autoDeallocateShmem.release();
 | 
						|
 | 
						|
  ReorderAndReturnOutput(std::move(v));
 | 
						|
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
void ChromiumCDMParent::ReorderAndReturnOutput(RefPtr<VideoData>&& aFrame) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  if (mMaxRefFrames == 0) {
 | 
						|
    mDecodePromise.ResolveIfExists(
 | 
						|
        MediaDataDecoder::DecodedData({std::move(aFrame)}), __func__);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  mReorderQueue.Push(std::move(aFrame));
 | 
						|
  MediaDataDecoder::DecodedData results;
 | 
						|
  while (mReorderQueue.Length() > mMaxRefFrames) {
 | 
						|
    results.AppendElement(mReorderQueue.Pop());
 | 
						|
  }
 | 
						|
  mDecodePromise.Resolve(std::move(results), __func__);
 | 
						|
}
 | 
						|
 | 
						|
already_AddRefed<VideoData> ChromiumCDMParent::CreateVideoFrame(
 | 
						|
    const CDMVideoFrame& aFrame, Span<uint8_t> aData) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  MOZ_ASSERT(aData.Length() > 0);
 | 
						|
  GMP_LOG_DEBUG(
 | 
						|
      "ChromiumCDMParent::CreateVideoFrame(this=%p aFrame.mFormat=%" PRIu32 ")",
 | 
						|
      this, aFrame.mFormat());
 | 
						|
 | 
						|
  if (aFrame.mFormat() == cdm::VideoFormat::kUnknownVideoFormat) {
 | 
						|
    GMP_LOG_DEBUG(
 | 
						|
        "ChromiumCDMParent::CreateVideoFrame(this=%p) Got kUnknownVideoFormat, "
 | 
						|
        "bailing.",
 | 
						|
        this);
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  if (aFrame.mFormat() == cdm::VideoFormat::kYUV420P9 ||
 | 
						|
      aFrame.mFormat() == cdm::VideoFormat::kYUV422P9 ||
 | 
						|
      aFrame.mFormat() == cdm::VideoFormat::kYUV444P9) {
 | 
						|
    // If we ever hit this we can reconsider support, but 9 bit formats
 | 
						|
    // should be so rare as to be non-existent.
 | 
						|
    GMP_LOG_DEBUG(
 | 
						|
        "ChromiumCDMParent::CreateVideoFrame(this=%p) Got a 9 bit depth pixel "
 | 
						|
        "format. We don't support these, bailing.",
 | 
						|
        this);
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  VideoData::YCbCrBuffer b;
 | 
						|
 | 
						|
  // Determine the dimensions of our chroma planes, color depth and chroma
 | 
						|
  // subsampling.
 | 
						|
  uint32_t chromaWidth = (aFrame.mImageWidth() + 1) / 2;
 | 
						|
  uint32_t chromaHeight = (aFrame.mImageHeight() + 1) / 2;
 | 
						|
  gfx::ColorDepth colorDepth = gfx::ColorDepth::COLOR_8;
 | 
						|
  gfx::ChromaSubsampling chromaSubsampling =
 | 
						|
      gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT;
 | 
						|
  switch (aFrame.mFormat()) {
 | 
						|
    case cdm::VideoFormat::kYv12:
 | 
						|
    case cdm::VideoFormat::kI420:
 | 
						|
      break;
 | 
						|
    case cdm::VideoFormat::kYUV420P10:
 | 
						|
      colorDepth = gfx::ColorDepth::COLOR_10;
 | 
						|
      break;
 | 
						|
    case cdm::VideoFormat::kYUV422P10:
 | 
						|
      chromaHeight = aFrame.mImageHeight();
 | 
						|
      colorDepth = gfx::ColorDepth::COLOR_10;
 | 
						|
      chromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH;
 | 
						|
      break;
 | 
						|
    case cdm::VideoFormat::kYUV444P10:
 | 
						|
      chromaWidth = aFrame.mImageWidth();
 | 
						|
      chromaHeight = aFrame.mImageHeight();
 | 
						|
      colorDepth = gfx::ColorDepth::COLOR_10;
 | 
						|
      chromaSubsampling = gfx::ChromaSubsampling::FULL;
 | 
						|
      break;
 | 
						|
    case cdm::VideoFormat::kYUV420P12:
 | 
						|
      colorDepth = gfx::ColorDepth::COLOR_12;
 | 
						|
      break;
 | 
						|
    case cdm::VideoFormat::kYUV422P12:
 | 
						|
      chromaHeight = aFrame.mImageHeight();
 | 
						|
      colorDepth = gfx::ColorDepth::COLOR_12;
 | 
						|
      chromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH;
 | 
						|
      break;
 | 
						|
    case cdm::VideoFormat::kYUV444P12:
 | 
						|
      chromaWidth = aFrame.mImageWidth();
 | 
						|
      chromaHeight = aFrame.mImageHeight();
 | 
						|
      colorDepth = gfx::ColorDepth::COLOR_12;
 | 
						|
      chromaSubsampling = gfx::ChromaSubsampling::FULL;
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      MOZ_ASSERT_UNREACHABLE("Should handle all formats");
 | 
						|
      return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  // Since we store each plane separately we can just roll the offset
 | 
						|
  // into our pointer to that plane and store that.
 | 
						|
  b.mPlanes[0].mData = aData.Elements() + aFrame.mYPlane().mPlaneOffset();
 | 
						|
  b.mPlanes[0].mWidth = aFrame.mImageWidth();
 | 
						|
  b.mPlanes[0].mHeight = aFrame.mImageHeight();
 | 
						|
  b.mPlanes[0].mStride = aFrame.mYPlane().mStride();
 | 
						|
  b.mPlanes[0].mSkip = 0;
 | 
						|
 | 
						|
  b.mPlanes[1].mData = aData.Elements() + aFrame.mUPlane().mPlaneOffset();
 | 
						|
  b.mPlanes[1].mWidth = chromaWidth;
 | 
						|
  b.mPlanes[1].mHeight = chromaHeight;
 | 
						|
  b.mPlanes[1].mStride = aFrame.mUPlane().mStride();
 | 
						|
  b.mPlanes[1].mSkip = 0;
 | 
						|
 | 
						|
  b.mPlanes[2].mData = aData.Elements() + aFrame.mVPlane().mPlaneOffset();
 | 
						|
  b.mPlanes[2].mWidth = chromaWidth;
 | 
						|
  b.mPlanes[2].mHeight = chromaHeight;
 | 
						|
  b.mPlanes[2].mStride = aFrame.mVPlane().mStride();
 | 
						|
  b.mPlanes[2].mSkip = 0;
 | 
						|
 | 
						|
  b.mColorDepth = colorDepth;
 | 
						|
  b.mChromaSubsampling = chromaSubsampling;
 | 
						|
 | 
						|
  // Ensure that each plane fits within the buffer bounds.
 | 
						|
  auto bpp = BytesPerPixel(SurfaceFormatForColorDepth(colorDepth));
 | 
						|
  for (const auto& plane : b.mPlanes) {
 | 
						|
    auto rowSize = CheckedInt<uint32_t>(plane.mWidth) * bpp;
 | 
						|
    if (NS_WARN_IF(!rowSize.isValid() || rowSize.value() > plane.mStride)) {
 | 
						|
      GMP_LOG_DEBUG(
 | 
						|
          "ChromiumCDMParent::CreateVideoFrame(this=%p) Plane width %u stride "
 | 
						|
          "%u bpp %d mismatch, bailing.",
 | 
						|
          this, plane.mWidth, plane.mStride, bpp);
 | 
						|
      return nullptr;
 | 
						|
    }
 | 
						|
    auto size = CheckedInt<uint32_t>(plane.mStride) * plane.mHeight;
 | 
						|
    if (NS_WARN_IF(!size.isValid())) {
 | 
						|
      GMP_LOG_DEBUG(
 | 
						|
          "ChromiumCDMParent::CreateVideoFrame(this=%p) Plane height %u stride "
 | 
						|
          "%u integer overflow, bailing.",
 | 
						|
          this, plane.mHeight, plane.mStride);
 | 
						|
      return nullptr;
 | 
						|
    }
 | 
						|
    auto offset = plane.mData - aData.Elements();
 | 
						|
    auto required = size + offset;
 | 
						|
    if (NS_WARN_IF(!required.isValid() || required.value() > aData.Length())) {
 | 
						|
      GMP_LOG_DEBUG(
 | 
						|
          "ChromiumCDMParent::CreateVideoFrame(this=%p) Plane height %u stride "
 | 
						|
          "%u offset %u buffer length %zu overflow, bailing.",
 | 
						|
          this, plane.mHeight, plane.mStride, static_cast<uint32_t>(offset),
 | 
						|
          aData.Length());
 | 
						|
      return nullptr;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // We unfortunately can't know which colorspace the video is using at this
 | 
						|
  // stage.
 | 
						|
  b.mYUVColorSpace =
 | 
						|
      DefaultColorSpace({aFrame.mImageWidth(), aFrame.mImageHeight()});
 | 
						|
 | 
						|
  gfx::IntRect pictureRegion(0, 0, aFrame.mImageWidth(), aFrame.mImageHeight());
 | 
						|
 | 
						|
  mozilla::Result<already_AddRefed<VideoData>, MediaResult> r =
 | 
						|
      VideoData::CreateAndCopyData(
 | 
						|
          mVideoInfo, mImageContainer, mLastStreamOffset,
 | 
						|
          media::TimeUnit::FromMicroseconds(
 | 
						|
              CheckedInt64(aFrame.mTimestamp()).value()),
 | 
						|
          media::TimeUnit::FromMicroseconds(
 | 
						|
              CheckedInt64(aFrame.mDuration()).value()),
 | 
						|
          b, false, media::TimeUnit::FromMicroseconds(-1), pictureRegion,
 | 
						|
          mKnowsCompositor);
 | 
						|
  RefPtr<VideoData> v = r.unwrapOr(nullptr);
 | 
						|
 | 
						|
  if (!v || !v->mImage) {
 | 
						|
    NS_WARNING("Failed to decode video frame.");
 | 
						|
    return v.forget();
 | 
						|
  }
 | 
						|
 | 
						|
  // This is a DRM image.
 | 
						|
  v->mImage->SetIsDRM(true);
 | 
						|
 | 
						|
  return v.forget();
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvDecodeFailed(const uint32_t& aStatus) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodeFailed(this=%p status=%" PRIu32
 | 
						|
                ")",
 | 
						|
                this, aStatus);
 | 
						|
  if (mIsShutdown) {
 | 
						|
    MOZ_ASSERT(mDecodePromise.IsEmpty());
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  if (aStatus == cdm::kNeedMoreData) {
 | 
						|
    mDecodePromise.ResolveIfExists(nsTArray<RefPtr<MediaData>>(), __func__);
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  mDecodePromise.RejectIfExists(
 | 
						|
      MediaResult(
 | 
						|
          NS_ERROR_DOM_MEDIA_FATAL_ERR,
 | 
						|
          RESULT_DETAIL(
 | 
						|
              "ChromiumCDMParent::RecvDecodeFailed with status %s (%" PRIu32
 | 
						|
              ")",
 | 
						|
              CdmStatusToString(aStatus), aStatus)),
 | 
						|
      __func__);
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvShutdown() {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::RecvShutdown(this=%p)", this);
 | 
						|
  Shutdown();
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
void ChromiumCDMParent::ActorDestroy(ActorDestroyReason aWhy) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::ActorDestroy(this=%p, reason=%d)", this,
 | 
						|
                aWhy);
 | 
						|
  MOZ_ASSERT(!mActorDestroyed);
 | 
						|
  mActorDestroyed = true;
 | 
						|
  // Shutdown() will clear mCDMCallback, so let's keep a reference for later
 | 
						|
  // use.
 | 
						|
  auto callback = mCDMCallback;
 | 
						|
  if (!mIsShutdown) {
 | 
						|
    // Plugin crash.
 | 
						|
    MOZ_ASSERT(aWhy == AbnormalShutdown);
 | 
						|
    Shutdown();
 | 
						|
  }
 | 
						|
  MOZ_ASSERT(mIsShutdown);
 | 
						|
  RefPtr<ChromiumCDMParent> kungFuDeathGrip(this);
 | 
						|
  if (mContentParent) {
 | 
						|
    mContentParent->ChromiumCDMDestroyed(this);
 | 
						|
    mContentParent = nullptr;
 | 
						|
  }
 | 
						|
  mAbnormalShutdown = (aWhy == AbnormalShutdown);
 | 
						|
  if (mAbnormalShutdown && callback) {
 | 
						|
    callback->Terminated();
 | 
						|
  }
 | 
						|
  MaybeDisconnect(mAbnormalShutdown);
 | 
						|
}
 | 
						|
 | 
						|
RefPtr<MediaDataDecoder::InitPromise> ChromiumCDMParent::InitializeVideoDecoder(
 | 
						|
    const gmp::CDMVideoDecoderConfig& aConfig, const VideoInfo& aInfo,
 | 
						|
    RefPtr<layers::ImageContainer> aImageContainer,
 | 
						|
    RefPtr<layers::KnowsCompositor> aKnowsCompositor) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  if (mIsShutdown) {
 | 
						|
    return MediaDataDecoder::InitPromise::CreateAndReject(
 | 
						|
        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
 | 
						|
                    RESULT_DETAIL("ChromiumCDMParent is shutdown")),
 | 
						|
        __func__);
 | 
						|
  }
 | 
						|
 | 
						|
  // The Widevine CDM version 1.4.8.970 and above contain a video decoder that
 | 
						|
  // does not optimally allocate video frames; it requests buffers much larger
 | 
						|
  // than required. The exact formula the CDM uses to calculate their frame
 | 
						|
  // sizes isn't obvious, but they normally request around or slightly more
 | 
						|
  // than 1.5X the optimal amount. So pad the size of buffers we allocate so
 | 
						|
  // that we're likely to have buffers big enough to accomodate the CDM's weird
 | 
						|
  // frame size calculation.
 | 
						|
  const size_t bufferSize =
 | 
						|
      1.7 * I420FrameBufferSizePadded(aInfo.mImage.width, aInfo.mImage.height);
 | 
						|
  if (bufferSize <= 0) {
 | 
						|
    return MediaDataDecoder::InitPromise::CreateAndReject(
 | 
						|
        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
 | 
						|
                    RESULT_DETAIL("Video frame buffer size is invalid.")),
 | 
						|
        __func__);
 | 
						|
  }
 | 
						|
 | 
						|
  if (!EnsureSufficientShmems(bufferSize)) {
 | 
						|
    return MediaDataDecoder::InitPromise::CreateAndReject(
 | 
						|
        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
 | 
						|
                    RESULT_DETAIL("Failed to init shmems for video decoder")),
 | 
						|
        __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__);
 | 
						|
  }
 | 
						|
 | 
						|
  mMaxRefFrames = (aConfig.mCodec() == cdm::VideoCodec::kCodecH264)
 | 
						|
                      ? H264::HasSPS(aInfo.mExtraData)
 | 
						|
                            ? H264::ComputeMaxRefFrames(aInfo.mExtraData)
 | 
						|
                            : 16
 | 
						|
                      : 0;
 | 
						|
 | 
						|
  mVideoDecoderInitialized = true;
 | 
						|
  mImageContainer = aImageContainer;
 | 
						|
  mKnowsCompositor = aKnowsCompositor;
 | 
						|
  mVideoInfo = aInfo;
 | 
						|
  mVideoFrameBufferSize = bufferSize;
 | 
						|
 | 
						|
  return mInitVideoDecoderPromise.Ensure(__func__);
 | 
						|
}
 | 
						|
 | 
						|
ipc::IPCResult ChromiumCDMParent::RecvOnDecoderInitDone(
 | 
						|
    const uint32_t& aStatus) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG(
 | 
						|
      "ChromiumCDMParent::RecvOnDecoderInitDone(this=%p, status=%" PRIu32 ")",
 | 
						|
      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 status %s (%" PRIu32 ")",
 | 
						|
                          CdmStatusToString(aStatus), aStatus)),
 | 
						|
        __func__);
 | 
						|
  }
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
RefPtr<MediaDataDecoder::DecodePromise>
 | 
						|
ChromiumCDMParent::DecryptAndDecodeFrame(MediaRawData* aSample) {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  if (mIsShutdown) {
 | 
						|
    return MediaDataDecoder::DecodePromise::CreateAndReject(
 | 
						|
        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
 | 
						|
                    RESULT_DETAIL("ChromiumCDMParent is shutdown")),
 | 
						|
        __func__);
 | 
						|
  }
 | 
						|
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::DecryptAndDecodeFrame t=%" PRId64,
 | 
						|
                aSample->mTime.ToMicroseconds());
 | 
						|
 | 
						|
  CDMInputBuffer buffer;
 | 
						|
 | 
						|
  if (!InitCDMInputBuffer(buffer, aSample)) {
 | 
						|
    return MediaDataDecoder::DecodePromise::CreateAndReject(
 | 
						|
        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Failed to init CDM buffer."),
 | 
						|
        __func__);
 | 
						|
  }
 | 
						|
 | 
						|
  mLastStreamOffset = aSample->mOffset;
 | 
						|
 | 
						|
  if (!SendDecryptAndDecodeFrame(buffer)) {
 | 
						|
    GMP_LOG_DEBUG(
 | 
						|
        "ChromiumCDMParent::Decrypt(this=%p) failed to send decrypt message.",
 | 
						|
        this);
 | 
						|
    DeallocShmem(buffer.mData());
 | 
						|
    return MediaDataDecoder::DecodePromise::CreateAndReject(
 | 
						|
        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
 | 
						|
                    "Failed to send decrypt to CDM process."),
 | 
						|
        __func__);
 | 
						|
  }
 | 
						|
 | 
						|
  return mDecodePromise.Ensure(__func__);
 | 
						|
}
 | 
						|
 | 
						|
RefPtr<MediaDataDecoder::FlushPromise> ChromiumCDMParent::FlushVideoDecoder() {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  if (mIsShutdown) {
 | 
						|
    MOZ_ASSERT(mReorderQueue.IsEmpty());
 | 
						|
    return MediaDataDecoder::FlushPromise::CreateAndReject(
 | 
						|
        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
 | 
						|
                    RESULT_DETAIL("ChromiumCDMParent is shutdown")),
 | 
						|
        __func__);
 | 
						|
  }
 | 
						|
 | 
						|
  mReorderQueue.Clear();
 | 
						|
 | 
						|
  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() {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  MOZ_ASSERT(mReorderQueue.IsEmpty());
 | 
						|
  if (mIsShutdown) {
 | 
						|
    MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
  mFlushDecoderPromise.ResolveIfExists(true, __func__);
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
RefPtr<MediaDataDecoder::DecodePromise> ChromiumCDMParent::Drain() {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  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() {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  if (mIsShutdown) {
 | 
						|
    MOZ_ASSERT(mDecodePromise.IsEmpty());
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  MediaDataDecoder::DecodedData samples;
 | 
						|
  while (!mReorderQueue.IsEmpty()) {
 | 
						|
    samples.AppendElement(mReorderQueue.Pop());
 | 
						|
  }
 | 
						|
 | 
						|
  mDecodePromise.ResolveIfExists(std::move(samples), __func__);
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
RefPtr<ShutdownPromise> ChromiumCDMParent::ShutdownVideoDecoder() {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  if (mIsShutdown || !mVideoDecoderInitialized) {
 | 
						|
    return ShutdownPromise::CreateAndResolve(true, __func__);
 | 
						|
  }
 | 
						|
  mInitVideoDecoderPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED,
 | 
						|
                                          __func__);
 | 
						|
  mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
 | 
						|
  MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
 | 
						|
  if (!SendDeinitializeVideoDecoder()) {
 | 
						|
    return ShutdownPromise::CreateAndResolve(true, __func__);
 | 
						|
  }
 | 
						|
  mVideoDecoderInitialized = false;
 | 
						|
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::~ShutdownVideoDecoder(this=%p) ", this);
 | 
						|
 | 
						|
  // The ChromiumCDMChild will purge its shmems, so if the decoder is
 | 
						|
  // reinitialized the shmems need to be re-allocated, and they may need
 | 
						|
  // to be a different size.
 | 
						|
  mVideoShmemsActive = 0;
 | 
						|
  mVideoFrameBufferSize = 0;
 | 
						|
  return ShutdownPromise::CreateAndResolve(true, __func__);
 | 
						|
}
 | 
						|
 | 
						|
void ChromiumCDMParent::Shutdown() {
 | 
						|
  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
 | 
						|
  GMP_LOG_DEBUG("ChromiumCDMParent::Shutdown(this=%p)", this);
 | 
						|
 | 
						|
  if (mIsShutdown) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  mIsShutdown = true;
 | 
						|
 | 
						|
  // If we're shutting down due to the plugin shutting down due to application
 | 
						|
  // shutdown, we should tell the CDM proxy to also shutdown. Otherwise the
 | 
						|
  // proxy will shutdown when the owning MediaKeys is destroyed during cycle
 | 
						|
  // collection, and that will not shut down cleanly as the GMP thread will be
 | 
						|
  // shutdown by then.
 | 
						|
  if (mCDMCallback) {
 | 
						|
    mCDMCallback->Shutdown();
 | 
						|
  }
 | 
						|
 | 
						|
  // We may be called from a task holding the last reference to the CDM
 | 
						|
  // callback, so let's clear our local weak pointer to ensure it will not be
 | 
						|
  // used afterward (including from an already-queued task, e.g.: ActorDestroy).
 | 
						|
  mCDMCallback = nullptr;
 | 
						|
 | 
						|
  mReorderQueue.Clear();
 | 
						|
 | 
						|
  for (RefPtr<DecryptJob>& decrypt : mDecrypts) {
 | 
						|
    decrypt->PostResult(eme::AbortedErr);
 | 
						|
  }
 | 
						|
  mDecrypts.Clear();
 | 
						|
 | 
						|
  if (mVideoDecoderInitialized && !mActorDestroyed) {
 | 
						|
    Unused << SendDeinitializeVideoDecoder();
 | 
						|
    mVideoDecoderInitialized = false;
 | 
						|
  }
 | 
						|
 | 
						|
  // Note: MediaKeys rejects all outstanding promises when it initiates
 | 
						|
  // shutdown.
 | 
						|
  mPromiseToCreateSessionToken.Clear();
 | 
						|
 | 
						|
  mInitPromise.RejectIfExists(
 | 
						|
      MediaResult(NS_ERROR_DOM_ABORT_ERR,
 | 
						|
                  RESULT_DETAIL("ChromiumCDMParent is shutdown")),
 | 
						|
      __func__);
 | 
						|
 | 
						|
  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 mozilla::gmp
 | 
						|
 | 
						|
#undef NS_DispatchToMainThread
 |