diff options
Diffstat (limited to 'dom/media/eme/mediafoundation/WMFCDMProxy.cpp')
-rw-r--r-- | dom/media/eme/mediafoundation/WMFCDMProxy.cpp | 414 |
1 files changed, 414 insertions, 0 deletions
diff --git a/dom/media/eme/mediafoundation/WMFCDMProxy.cpp b/dom/media/eme/mediafoundation/WMFCDMProxy.cpp new file mode 100644 index 0000000000..21207ecc22 --- /dev/null +++ b/dom/media/eme/mediafoundation/WMFCDMProxy.cpp @@ -0,0 +1,414 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "WMFCDMProxy.h" + +#include "mozilla/dom/MediaKeysBinding.h" +#include "mozilla/dom/MediaKeySession.h" +#include "mozilla/dom/MediaKeySystemAccessBinding.h" +#include "mozilla/EMEUtils.h" +#include "mozilla/WMFCDMProxyCallback.h" +#include "mozilla/WindowsVersion.h" +#include "WMFCDMImpl.h" +#include "WMFCDMProxyCallback.h" + +namespace mozilla { + +#define LOG(msg, ...) \ + EME_LOG("WMFCDMProxy[%p]@%s: " msg, this, __func__, ##__VA_ARGS__) + +#define RETURN_IF_SHUTDOWN() \ + do { \ + MOZ_ASSERT(NS_IsMainThread()); \ + if (mIsShutdown) { \ + return; \ + } \ + } while (false) + +#define PERFORM_ON_CDM(operation, promiseId, ...) \ + do { \ + mCDM->operation(promiseId, __VA_ARGS__) \ + ->Then( \ + mMainThread, __func__, \ + [self = RefPtr{this}, this, promiseId]() { \ + RETURN_IF_SHUTDOWN(); \ + if (mKeys.IsNull()) { \ + EME_LOG("WMFCDMProxy(this=%p, pid=%" PRIu32 \ + ") : abort the " #operation " due to empty key", \ + this, promiseId); \ + return; \ + } \ + ResolvePromise(promiseId); \ + }, \ + [self = RefPtr{this}, this, promiseId]() { \ + RETURN_IF_SHUTDOWN(); \ + RejectPromiseWithStateError( \ + promiseId, nsLiteralCString("WMFCDMProxy::" #operation ": " \ + "failed to " #operation)); \ + }); \ + } while (false) + +WMFCDMProxy::WMFCDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem, + const dom::MediaKeySystemConfiguration& aConfig) + : CDMProxy( + aKeys, aKeySystem, + aConfig.mDistinctiveIdentifier == dom::MediaKeysRequirement::Required, + aConfig.mPersistentState == dom::MediaKeysRequirement::Required), + mConfig(aConfig) { + MOZ_ASSERT(NS_IsMainThread()); +} + +WMFCDMProxy::~WMFCDMProxy() {} + +void WMFCDMProxy::Init(PromiseId aPromiseId, const nsAString& aOrigin, + const nsAString& aTopLevelOrigin, + const nsAString& aName) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aOrigin.IsEmpty()); + + MOZ_ASSERT(!mOwnerThread); + if (NS_FAILED( + NS_NewNamedThread("WMFCDMThread", getter_AddRefs(mOwnerThread)))) { + RejectPromiseWithStateError( + aPromiseId, + nsLiteralCString("WMFCDMProxy::Init: couldn't create CDM thread")); + return; + } + + mCDM = MakeRefPtr<WMFCDMImpl>(mKeySystem); + mProxyCallback = new WMFCDMProxyCallback(this); + // SWDRM has a PMP process leakage problem on Windows 10 due to the internal + // issue in the media foundation. Microsoft only lands the solution on Windows + // 11 and doesn't have plan to port it back to Windows 10. Therefore, for + // PlayReady, we need to force to covert SL2000 to SL3000 for our underlying + // CDM to avoid that issue. In addition, as we only support L1 for Widevine, + // this issue won't happen on it. + Maybe<nsString> forcedRobustness = + IsPlayReadyKeySystemAndSupported(mKeySystem) && !IsWin11OrLater() + ? Some(nsString(u"3000")) + : Nothing(); + WMFCDMImpl::InitParams params{ + nsString(aOrigin), + mConfig.mInitDataTypes, + mPersistentStateRequired, + mDistinctiveIdentifierRequired, + mProxyCallback, + GenerateMFCDMMediaCapabilities(mConfig.mAudioCapabilities), + GenerateMFCDMMediaCapabilities(mConfig.mVideoCapabilities, + forcedRobustness)}; + mCDM->Init(params)->Then( + mMainThread, __func__, + [self = RefPtr{this}, this, aPromiseId](const bool) { + MOZ_ASSERT(mCDM->Id() > 0); + mKeys->OnCDMCreated(aPromiseId, mCDM->Id()); + }, + [self = RefPtr{this}, this, aPromiseId](const nsresult rv) { + RejectPromiseWithStateError( + aPromiseId, + nsLiteralCString("WMFCDMProxy::Init: WMFCDM init error")); + }); +} + +CopyableTArray<MFCDMMediaCapability> +WMFCDMProxy::GenerateMFCDMMediaCapabilities( + const dom::Sequence<dom::MediaKeySystemMediaCapability>& aCapabilities, + const Maybe<nsString>& forcedRobustness) { + CopyableTArray<MFCDMMediaCapability> outCapabilites; + for (const auto& capabilities : aCapabilities) { + if (!forcedRobustness) { + EME_LOG("WMFCDMProxy::Init %p, robustness=%s", this, + NS_ConvertUTF16toUTF8(capabilities.mRobustness).get()); + outCapabilites.AppendElement(MFCDMMediaCapability{ + capabilities.mContentType, capabilities.mRobustness}); + } else { + EME_LOG("WMFCDMProxy::Init %p, force to robustness=%s", this, + NS_ConvertUTF16toUTF8(*forcedRobustness).get()); + outCapabilites.AppendElement( + MFCDMMediaCapability{capabilities.mContentType, *forcedRobustness}); + } + } + return outCapabilites; +} + +void WMFCDMProxy::ResolvePromise(PromiseId aId) { + auto resolve = [self = RefPtr{this}, this, aId]() { + RETURN_IF_SHUTDOWN(); + EME_LOG("WMFCDMProxy::ResolvePromise(this=%p, pid=%" PRIu32 ")", this, aId); + if (!mKeys.IsNull()) { + mKeys->ResolvePromise(aId); + } else { + NS_WARNING("WMFCDMProxy unable to resolve promise!"); + } + }; + + if (NS_IsMainThread()) { + resolve(); + return; + } + mMainThread->Dispatch( + NS_NewRunnableFunction("WMFCDMProxy::ResolvePromise", resolve)); +} + +void WMFCDMProxy::ResolvePromiseWithKeyStatus( + const PromiseId& aId, const dom::MediaKeyStatus& aStatus) { + auto resolve = [self = RefPtr{this}, this, aId, aStatus]() { + RETURN_IF_SHUTDOWN(); + EME_LOG("WMFCDMProxy::ResolvePromiseWithKeyStatus(this=%p, pid=%" PRIu32 + ", status=%s)", + this, aId, ToMediaKeyStatusStr(aStatus)); + if (!mKeys.IsNull()) { + mKeys->ResolvePromiseWithKeyStatus(aId, aStatus); + } else { + NS_WARNING("WMFCDMProxy unable to resolve promise!"); + } + }; + + if (NS_IsMainThread()) { + resolve(); + return; + } + mMainThread->Dispatch(NS_NewRunnableFunction( + "WMFCDMProxy::ResolvePromiseWithKeyStatus", resolve)); +} + +void WMFCDMProxy::RejectPromise(PromiseId aId, ErrorResult&& aException, + const nsCString& aReason) { + if (!NS_IsMainThread()) { + // Use CopyableErrorResult to store our exception in the runnable, + // because ErrorResult is not OK to move across threads. + mMainThread->Dispatch( + NewRunnableMethod<PromiseId, StoreCopyPassByRRef<CopyableErrorResult>, + nsCString>("WMFCDMProxy::RejectPromise", this, + &WMFCDMProxy::RejectPromiseOnMainThread, + aId, std::move(aException), aReason), + NS_DISPATCH_NORMAL); + return; + } + EME_LOG("WMFCDMProxy::RejectPromise(this=%p, pid=%" PRIu32 + ", code=0x%x, " + "reason='%s')", + this, aId, aException.ErrorCodeAsInt(), aReason.get()); + if (!mKeys.IsNull()) { + mKeys->RejectPromise(aId, std::move(aException), aReason); + } else { + // We don't have a MediaKeys object to pass the exception to, so silence + // the exception to avoid it asserting due to being unused. + aException.SuppressException(); + } +} + +void WMFCDMProxy::RejectPromiseOnMainThread(PromiseId aId, + CopyableErrorResult&& aException, + const nsCString& aReason) { + RETURN_IF_SHUTDOWN(); + // Moving into or out of a non-copyable ErrorResult will assert that both + // ErorResults are from our current thread. Avoid the assertion by moving + // into a current-thread CopyableErrorResult first. Note that this is safe, + // because CopyableErrorResult never holds state that can't move across + // threads. + CopyableErrorResult rv(std::move(aException)); + RejectPromise(aId, std::move(rv), aReason); +} + +void WMFCDMProxy::RejectPromiseWithStateError(PromiseId aId, + const nsCString& aReason) { + ErrorResult rv; + rv.ThrowInvalidStateError(aReason); + RejectPromise(aId, std::move(rv), aReason); +} + +void WMFCDMProxy::CreateSession(uint32_t aCreateSessionToken, + MediaKeySessionType aSessionType, + PromiseId aPromiseId, + const nsAString& aInitDataType, + nsTArray<uint8_t>& aInitData) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + const auto sessionType = ConvertToKeySystemConfigSessionType(aSessionType); + EME_LOG("WMFCDMProxy::CreateSession(this=%p, pid=%" PRIu32 + "), sessionType=%s", + this, aPromiseId, SessionTypeToStr(sessionType)); + mCDM->CreateSession(aPromiseId, sessionType, aInitDataType, aInitData) + ->Then( + mMainThread, __func__, + [self = RefPtr{this}, this, aCreateSessionToken, + aPromiseId](nsString sessionID) { + RETURN_IF_SHUTDOWN(); + if (mKeys.IsNull()) { + EME_LOG("WMFCDMProxy(this=%p, pid=%" PRIu32 + ") : abort the create session due to " + "empty key", + this, aPromiseId); + return; + } + if (RefPtr<dom::MediaKeySession> session = + mKeys->GetPendingSession(aCreateSessionToken)) { + session->SetSessionId(std::move(sessionID)); + } + ResolvePromise(aPromiseId); + }, + [self = RefPtr{this}, this, aPromiseId]() { + RETURN_IF_SHUTDOWN(); + RejectPromiseWithStateError( + aPromiseId, + nsLiteralCString( + "WMFCDMProxy::CreateSession: cannot create session")); + }); +} + +void WMFCDMProxy::LoadSession(PromiseId aPromiseId, + dom::MediaKeySessionType aSessionType, + const nsAString& aSessionId) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + const auto sessionType = ConvertToKeySystemConfigSessionType(aSessionType); + EME_LOG("WMFCDMProxy::LoadSession(this=%p, pid=%" PRIu32 + "), sessionType=%s, sessionId=%s", + this, aPromiseId, SessionTypeToStr(sessionType), + NS_ConvertUTF16toUTF8(aSessionId).get()); + PERFORM_ON_CDM(LoadSession, aPromiseId, sessionType, aSessionId); +} + +void WMFCDMProxy::UpdateSession(const nsAString& aSessionId, + PromiseId aPromiseId, + nsTArray<uint8_t>& aResponse) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + EME_LOG("WMFCDMProxy::UpdateSession(this=%p, pid=%" PRIu32 + "), sessionId=%s, responseLen=%zu", + this, aPromiseId, NS_ConvertUTF16toUTF8(aSessionId).get(), + aResponse.Length()); + PERFORM_ON_CDM(UpdateSession, aPromiseId, aSessionId, aResponse); +} + +void WMFCDMProxy::CloseSession(const nsAString& aSessionId, + PromiseId aPromiseId) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + EME_LOG("WMFCDMProxy::CloseSession(this=%p, pid=%" PRIu32 "), sessionId=%s", + this, aPromiseId, NS_ConvertUTF16toUTF8(aSessionId).get()); + PERFORM_ON_CDM(CloseSession, aPromiseId, aSessionId); +} + +void WMFCDMProxy::RemoveSession(const nsAString& aSessionId, + PromiseId aPromiseId) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + EME_LOG("WMFCDMProxy::RemoveSession(this=%p, pid=%" PRIu32 "), sessionId=%s", + this, aPromiseId, NS_ConvertUTF16toUTF8(aSessionId).get()); + PERFORM_ON_CDM(RemoveSession, aPromiseId, aSessionId); +} + +void WMFCDMProxy::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mIsShutdown); + if (mProxyCallback) { + mProxyCallback->Shutdown(); + mProxyCallback = nullptr; + } + mIsShutdown = true; +} + +void WMFCDMProxy::OnSessionMessage(const nsAString& aSessionId, + dom::MediaKeyMessageType aMessageType, + const nsTArray<uint8_t>& aMessage) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + if (mKeys.IsNull()) { + return; + } + if (RefPtr<dom::MediaKeySession> session = mKeys->GetSession(aSessionId)) { + LOG("Notify key message for session Id=%s", + NS_ConvertUTF16toUTF8(aSessionId).get()); + session->DispatchKeyMessage(aMessageType, aMessage); + } +} + +void WMFCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + if (mKeys.IsNull()) { + return; + } + if (RefPtr<dom::MediaKeySession> session = mKeys->GetSession(aSessionId)) { + LOG("Notify key statuses for session Id=%s", + NS_ConvertUTF16toUTF8(aSessionId).get()); + session->DispatchKeyStatusesChange(); + } +} + +void WMFCDMProxy::OnExpirationChange(const nsAString& aSessionId, + UnixTime aExpiryTime) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + if (mKeys.IsNull()) { + return; + } + if (RefPtr<dom::MediaKeySession> session = mKeys->GetSession(aSessionId)) { + LOG("Notify expiration for session Id=%s", + NS_ConvertUTF16toUTF8(aSessionId).get()); + session->SetExpiration(static_cast<double>(aExpiryTime)); + } +} + +void WMFCDMProxy::SetServerCertificate(PromiseId aPromiseId, + nsTArray<uint8_t>& aCert) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + EME_LOG("WMFCDMProxy::SetServerCertificate(this=%p, pid=%" PRIu32 ")", this, + aPromiseId); + mCDM->SetServerCertificate(aPromiseId, aCert) + ->Then( + mMainThread, __func__, + [self = RefPtr{this}, this, aPromiseId]() { + RETURN_IF_SHUTDOWN(); + ResolvePromise(aPromiseId); + }, + [self = RefPtr{this}, this, aPromiseId]() { + RETURN_IF_SHUTDOWN(); + RejectPromiseWithStateError( + aPromiseId, + nsLiteralCString("WMFCDMProxy::SetServerCertificate failed!")); + }); +} + +void WMFCDMProxy::GetStatusForPolicy(PromiseId aPromiseId, + const dom::HDCPVersion& aMinHdcpVersion) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + EME_LOG("WMFCDMProxy::GetStatusForPolicy(this=%p, pid=%" PRIu32 + ", minHDCP=%s)", + this, aPromiseId, + dom::HDCPVersionValues::GetString(aMinHdcpVersion).data()); + mCDM->GetStatusForPolicy(aPromiseId, aMinHdcpVersion) + ->Then( + mMainThread, __func__, + [self = RefPtr{this}, this, aPromiseId]() { + RETURN_IF_SHUTDOWN(); + ResolvePromiseWithKeyStatus(aPromiseId, + dom::MediaKeyStatus::Usable); + }, + [self = RefPtr{this}, this, aPromiseId]() { + RETURN_IF_SHUTDOWN(); + ResolvePromiseWithKeyStatus(aPromiseId, + dom::MediaKeyStatus::Output_restricted); + }); +} + +bool WMFCDMProxy::IsHardwareDecryptionSupported() const { + return mozilla::IsHardwareDecryptionSupported(mConfig); +} + +uint64_t WMFCDMProxy::GetCDMProxyId() const { + MOZ_DIAGNOSTIC_ASSERT(mCDM); + return mCDM->Id(); +} + +#undef LOG +#undef RETURN_IF_SHUTDOWN +#undef PERFORM_ON_CDM + +} // namespace mozilla |