diff --git a/dom/media/gmp/ChromiumCDMParent.cpp b/dom/media/gmp/ChromiumCDMParent.cpp index 018b2f636e6e..a1ad05632998 100644 --- a/dom/media/gmp/ChromiumCDMParent.cpp +++ b/dom/media/gmp/ChromiumCDMParent.cpp @@ -8,6 +8,8 @@ #include "GMPContentChild.h" #include "mozilla/Unused.h" #include "ChromiumCDMProxy.h" +#include "mozilla/dom/MediaKeyMessageEventBinding.h" +#include "content_decryption_module.h" namespace mozilla { namespace gmp { @@ -28,6 +30,55 @@ ChromiumCDMParent::Init(ChromiumCDMProxy* aProxy, return SendInit(aAllowDistinctiveIdentifier, aAllowPersistentState); } +void +ChromiumCDMParent::SetServerCertificate(uint32_t aPromiseId, + const nsTArray& aCert) +{ + 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& aResponse) +{ + 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) +{ + 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) +{ + if (!SendRemoveSession(aPromiseId, aSessionId)) { + RejectPromise( + aPromiseId, + NS_ERROR_DOM_INVALID_STATE_ERR, + NS_LITERAL_CSTRING("Failed to send removeSession to CDM process")); + } +} + ipc::IPCResult ChromiumCDMParent::Recv__delete__() { @@ -44,31 +95,149 @@ ChromiumCDMParent::RecvOnResolveNewSessionPromise(const uint32_t& aPromiseId, ipc::IPCResult ChromiumCDMParent::RecvOnResolvePromise(const uint32_t& aPromiseId) { + if (!mProxy) { + return IPC_OK(); + } + NS_DispatchToMainThread(NewRunnableMethod( + mProxy, &ChromiumCDMProxy::ResolvePromise, aPromiseId)); return IPC_OK(); } +static nsresult +ToNsresult(uint32_t aError) +{ + switch (static_cast(aError)) { + case cdm::kNotSupportedError: + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + case cdm::kInvalidStateError: + return NS_ERROR_DOM_INVALID_STATE_ERR; + case cdm::kInvalidAccessError: + // Note: Chrome converts kInvalidAccessError to TypeError, since the + // Chromium CDM API doesn't have a type error enum value. The EME spec + // requires TypeError in some places, so we do the same conversion. + // See bug 1313202. + return NS_ERROR_DOM_TYPE_ERR; + case cdm::kQuotaExceededError: + return NS_ERROR_DOM_QUOTA_EXCEEDED_ERR; + case cdm::kUnknownError: + return NS_ERROR_DOM_UNKNOWN_ERR; // Note: Unique placeholder. + case cdm::kClientError: + return NS_ERROR_DOM_ABORT_ERR; // Note: Unique placeholder. + case cdm::kOutputError: + return NS_ERROR_DOM_SECURITY_ERR; // Note: Unique placeholder. + }; + MOZ_ASSERT_UNREACHABLE("Invalid cdm::Error enum value."); + return NS_ERROR_DOM_TIMEOUT_ERR; // Note: Unique placeholder. +} + +void +ChromiumCDMParent::RejectPromise(uint32_t aPromiseId, + nsresult aError, + const nsCString& aErrorMessage) +{ + if (!mProxy) { + return; + } + NS_DispatchToMainThread(NewRunnableMethod( + mProxy, + &ChromiumCDMProxy::RejectPromise, + aPromiseId, + aError, + aErrorMessage)); +} + ipc::IPCResult ChromiumCDMParent::RecvOnRejectPromise(const uint32_t& aPromiseId, const uint32_t& aError, const uint32_t& aSystemCode, const nsCString& aErrorMessage) { + RejectPromise(aPromiseId, ToNsresult(aError), aErrorMessage); return IPC_OK(); } +static dom::MediaKeyMessageType +ToDOMMessageType(uint32_t aMessageType) +{ + switch (static_cast(aMessageType)) { + case cdm::kLicenseRequest: + return dom::MediaKeyMessageType::License_request; + case cdm::kLicenseRenewal: + return dom::MediaKeyMessageType::License_renewal; + case cdm::kLicenseRelease: + return dom::MediaKeyMessageType::License_release; + } + MOZ_ASSERT_UNREACHABLE("Invalid cdm::MessageType enum value."); + return dom::MediaKeyMessageType::License_request; +} + ipc::IPCResult ChromiumCDMParent::RecvOnSessionMessage(const nsCString& aSessionId, const uint32_t& aMessageType, nsTArray&& aMessage) { + if (!mProxy) { + return IPC_OK(); + } + RefPtr proxy = mProxy; + nsString sid = NS_ConvertUTF8toUTF16(aSessionId); + dom::MediaKeyMessageType messageType = ToDOMMessageType(aMessageType); + nsTArray msg(Move(aMessage)); + NS_DispatchToMainThread( + NS_NewRunnableFunction([proxy, sid, messageType, msg]() mutable { + proxy->OnSessionMessage(sid, messageType, msg); + })); return IPC_OK(); } +static dom::MediaKeyStatus +ToDOMMediaKeyStatus(uint32_t aStatus) +{ + switch (static_cast(aStatus)) { + case cdm::kUsable: + return dom::MediaKeyStatus::Usable; + case cdm::kInternalError: + return dom::MediaKeyStatus::Internal_error; + case cdm::kExpired: + return dom::MediaKeyStatus::Expired; + case cdm::kOutputRestricted: + return dom::MediaKeyStatus::Output_restricted; + case cdm::kOutputDownscaled: + return dom::MediaKeyStatus::Output_downscaled; + case cdm::kStatusPending: + return dom::MediaKeyStatus::Status_pending; + case cdm::kReleased: + return dom::MediaKeyStatus::Released; + } + MOZ_ASSERT_UNREACHABLE("Invalid cdm::KeyStatus enum value."); + return dom::MediaKeyStatus::Internal_error; +} + ipc::IPCResult ChromiumCDMParent::RecvOnSessionKeysChange( const nsCString& aSessionId, nsTArray&& aKeysInfo) { + if (!mProxy) { + 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(), + NS_ConvertUTF8toUTF16(aSessionId), + dom::Optional( + ToDOMMediaKeyStatus(aKeysInfo[i].mStatus()))); + } + } + if (keyStatusesChange) { + NS_DispatchToMainThread( + NewRunnableMethod(mProxy, + &ChromiumCDMProxy::OnKeyStatusesChange, + NS_ConvertUTF8toUTF16(aSessionId))); + } return IPC_OK(); } @@ -76,12 +245,27 @@ ipc::IPCResult ChromiumCDMParent::RecvOnExpirationChange(const nsCString& aSessionId, const double& aSecondsSinceEpoch) { + if (!mProxy) { + return IPC_OK(); + } + NS_DispatchToMainThread(NewRunnableMethod( + mProxy, + &ChromiumCDMProxy::OnExpirationChange, + NS_ConvertUTF8toUTF16(aSessionId), + GMPTimestamp(aSecondsSinceEpoch * 1000))); return IPC_OK(); } ipc::IPCResult ChromiumCDMParent::RecvOnSessionClosed(const nsCString& aSessionId) { + if (!mProxy) { + return IPC_OK(); + } + NS_DispatchToMainThread( + NewRunnableMethod(mProxy, + &ChromiumCDMProxy::OnSessionClosed, + NS_ConvertUTF8toUTF16(aSessionId))); return IPC_OK(); } @@ -91,6 +275,17 @@ ChromiumCDMParent::RecvOnLegacySessionError(const nsCString& aSessionId, const uint32_t& aSystemCode, const nsCString& aMessage) { + if (!mProxy) { + return IPC_OK(); + } + NS_DispatchToMainThread( + NewRunnableMethod( + mProxy, + &ChromiumCDMProxy::OnSessionError, + NS_ConvertUTF8toUTF16(aSessionId), + ToNsresult(aError), + aSystemCode, + NS_ConvertUTF8toUTF16(aMessage))); return IPC_OK(); } diff --git a/dom/media/gmp/ChromiumCDMParent.h b/dom/media/gmp/ChromiumCDMParent.h index d0180a0cfc01..0a42f88fec27 100644 --- a/dom/media/gmp/ChromiumCDMParent.h +++ b/dom/media/gmp/ChromiumCDMParent.h @@ -35,7 +35,17 @@ public: bool aAllowDistinctiveIdentifier, bool aAllowPersistentState); - // TODO: Add functions for clients to send data to CDM, and + void SetServerCertificate(uint32_t aPromiseId, + const nsTArray& aCert); + + void UpdateSession(const nsCString& aSessionId, + uint32_t aPromiseId, + const nsTArray& aResponse); + + void CloseSession(const nsCString& aSessionId, uint32_t aPromiseId); + + void RemoveSession(const nsCString& aSessionId, uint32_t aPromiseId); + // a Close() function. protected: @@ -71,6 +81,10 @@ protected: ipc::IPCResult RecvShutdown() override; void ActorDestroy(ActorDestroyReason aWhy) override; + void RejectPromise(uint32_t aPromiseId, + nsresult aError, + const nsCString& aErrorMessage); + const uint32_t mPluginId; GMPContentParent* mContentParent; // Note: this pointer is a weak reference because otherwise it would cause diff --git a/dom/media/gmp/ChromiumCDMProxy.cpp b/dom/media/gmp/ChromiumCDMProxy.cpp index 6589b101067c..c57b4eb65f4c 100644 --- a/dom/media/gmp/ChromiumCDMProxy.cpp +++ b/dom/media/gmp/ChromiumCDMProxy.cpp @@ -8,6 +8,7 @@ #include "GMPUtils.h" #include "nsPrintfCString.h" #include "GMPService.h" +#include "mozilla/dom/MediaKeySession.h" namespace mozilla { @@ -152,12 +153,24 @@ ChromiumCDMProxy::CreateSession(uint32_t aCreateSessionToken, void ChromiumCDMProxy::LoadSession(PromiseId aPromiseId, const nsAString& aSessionId) { + MOZ_ASSERT(NS_IsMainThread()); + + RejectPromise(aPromiseId, + NS_ERROR_DOM_NOT_SUPPORTED_ERR, + NS_LITERAL_CSTRING("loadSession is not supported")); } void ChromiumCDMProxy::SetServerCertificate(PromiseId aPromiseId, nsTArray& aCert) { + MOZ_ASSERT(NS_IsMainThread()); + + mGMPThread->Dispatch(NewRunnableMethod>( + mCDM, + &gmp::ChromiumCDMParent::SetServerCertificate, + aPromiseId, + Move(aCert))); } void @@ -165,18 +178,36 @@ ChromiumCDMProxy::UpdateSession(const nsAString& aSessionId, PromiseId aPromiseId, nsTArray& aResponse) { + MOZ_ASSERT(NS_IsMainThread()); + + mGMPThread->Dispatch(NewRunnableMethod>( + mCDM, + &gmp::ChromiumCDMParent::UpdateSession, + NS_ConvertUTF16toUTF8(aSessionId), + aPromiseId, + Move(aResponse))); } void ChromiumCDMProxy::CloseSession(const nsAString& aSessionId, PromiseId aPromiseId) { + mGMPThread->Dispatch(NewRunnableMethod( + mCDM, + &gmp::ChromiumCDMParent::CloseSession, + NS_ConvertUTF16toUTF8(aSessionId), + aPromiseId)); } void ChromiumCDMProxy::RemoveSession(const nsAString& aSessionId, PromiseId aPromiseId) { + mGMPThread->Dispatch(NewRunnableMethod( + mCDM, + &gmp::ChromiumCDMParent::RemoveSession, + NS_ConvertUTF16toUTF8(aSessionId), + aPromiseId)); } void @@ -242,22 +273,66 @@ ChromiumCDMProxy::OnSessionMessage(const nsAString& aSessionId, dom::MediaKeyMessageType aMessageType, nsTArray& aMessage) { + MOZ_ASSERT(NS_IsMainThread()); + if (mKeys.IsNull()) { + return; + } + RefPtr session(mKeys->GetSession(aSessionId)); + if (session) { + session->DispatchKeyMessage(aMessageType, aMessage); + } } void ChromiumCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId) { + MOZ_ASSERT(NS_IsMainThread()); + if (mKeys.IsNull()) { + return; + } + RefPtr session(mKeys->GetSession(aSessionId)); + if (session) { + session->DispatchKeyStatusesChange(); + } } void ChromiumCDMProxy::OnExpirationChange(const nsAString& aSessionId, GMPTimestamp aExpiryTime) { + MOZ_ASSERT(NS_IsMainThread()); + if (mKeys.IsNull()) { + return; + } + RefPtr session(mKeys->GetSession(aSessionId)); + if (session) { + // Expiry of 0 is interpreted as "never expire". See bug 1345341. + double t = (aExpiryTime == 0) ? std::numeric_limits::quiet_NaN() + : static_cast(aExpiryTime); + session->SetExpiration(t); + } } void ChromiumCDMProxy::OnSessionClosed(const nsAString& aSessionId) { + MOZ_ASSERT(NS_IsMainThread()); + + bool keyStatusesChange = false; + { + CDMCaps::AutoLock caps(Capabilites()); + keyStatusesChange = caps.RemoveKeysForSession(nsString(aSessionId)); + } + if (keyStatusesChange) { + OnKeyStatusesChange(aSessionId); + } + if (mKeys.IsNull()) { + return; + } + RefPtr session(mKeys->GetSession(aSessionId)); + if (session) { + session->OnClosed(); + } } void @@ -273,6 +348,15 @@ ChromiumCDMProxy::OnSessionError(const nsAString& aSessionId, uint32_t aSystemCode, const nsAString& aMsg) { + MOZ_ASSERT(NS_IsMainThread()); + if (mKeys.IsNull()) { + return; + } + RefPtr session(mKeys->GetSession(aSessionId)); + if (session) { + session->DispatchKeyError(aSystemCode); + } + LogToConsole(aMsg); } void @@ -307,11 +391,16 @@ void ChromiumCDMProxy::GetSessionIdsForKeyId(const nsTArray& aKeyId, nsTArray& aSessionIds) { + CDMCaps::AutoLock caps(Capabilites()); + caps.GetSessionIdsForKeyId(aKeyId, aSessionIds); } void ChromiumCDMProxy::Terminated() { + if (!mKeys.IsNull()) { + mKeys->Terminated(); + } } } // namespace mozilla