Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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,
#include "mozilla/dom/MediaKeySession.h"
#include "mozilla/MediaDrmCDMProxy.h"
#include "MediaDrmCDMCallbackProxy.h"
namespace mozilla {
MediaDrmSessionType ToMediaDrmSessionType(
dom::MediaKeySessionType aSessionType) {
switch (aSessionType) {
case dom::MediaKeySessionType::Temporary:
return kKeyStreaming;
case dom::MediaKeySessionType::Persistent_license:
return kKeyOffline;
default:
return kKeyStreaming;
};
}
MediaDrmCDMProxy::MediaDrmCDMProxy(dom::MediaKeys* aKeys,
const nsAString& aKeySystem,
bool aDistinctiveIdentifierRequired,
bool aPersistentStateRequired)
: CDMProxy(aKeys, aKeySystem, aDistinctiveIdentifierRequired,
aPersistentStateRequired),
mCDM(nullptr),
mShutdownCalled(false) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_COUNT_CTOR(MediaDrmCDMProxy);
}
MediaDrmCDMProxy::~MediaDrmCDMProxy() { MOZ_COUNT_DTOR(MediaDrmCDMProxy); }
void MediaDrmCDMProxy::Init(PromiseId aPromiseId, const nsAString& aOrigin,
const nsAString& aTopLevelOrigin,
const nsAString& aName) {
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
EME_LOG("MediaDrmCDMProxy::Init (%s, %s) %s",
NS_ConvertUTF16toUTF8(aOrigin).get(),
NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
NS_ConvertUTF16toUTF8(aName).get());
// Create a thread to work with cdm.
if (!mOwnerThread) {
nsresult rv =
NS_NewNamedThread("MDCDMThread", getter_AddRefs(mOwnerThread));
if (NS_FAILED(rv)) {
RejectPromiseWithStateError(
aPromiseId, nsLiteralCString(
"Couldn't create CDM thread MediaDrmCDMProxy::Init"));
return;
}
}
mCDM = mozilla::MakeUnique<MediaDrmProxySupport>(mKeySystem);
nsCOMPtr<nsIRunnable> task(
NewRunnableMethod<uint32_t>("MediaDrmCDMProxy::md_Init", this,
&MediaDrmCDMProxy::md_Init, aPromiseId));
mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
}
void MediaDrmCDMProxy::CreateSession(uint32_t aCreateSessionToken,
MediaKeySessionType aSessionType,
PromiseId aPromiseId,
const nsAString& aInitDataType,
nsTArray<uint8_t>& aInitData) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mOwnerThread);
UniquePtr<CreateSessionData> data(new CreateSessionData());
data->mSessionType = aSessionType;
data->mCreateSessionToken = aCreateSessionToken;
data->mPromiseId = aPromiseId;
data->mInitDataType = NS_ConvertUTF16toUTF8(aInitDataType);
data->mInitData = std::move(aInitData);
nsCOMPtr<nsIRunnable> task(NewRunnableMethod<UniquePtr<CreateSessionData>&&>(
"MediaDrmCDMProxy::md_CreateSession", this,
&MediaDrmCDMProxy::md_CreateSession, std::move(data)));
mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
}
void MediaDrmCDMProxy::LoadSession(PromiseId aPromiseId,
dom::MediaKeySessionType aSessionType,
const nsAString& aSessionId) {
// TODO: Implement LoadSession.
RejectPromiseWithStateError(
aPromiseId, "Currently Fennec does not support LoadSession"_ns);
}
void MediaDrmCDMProxy::SetServerCertificate(PromiseId aPromiseId,
nsTArray<uint8_t>& aCert) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mOwnerThread);
mOwnerThread->Dispatch(NewRunnableMethod<PromiseId, const nsTArray<uint8_t>>(
"MediaDrmCDMProxy::md_SetServerCertificate", this,
&MediaDrmCDMProxy::md_SetServerCertificate,
aPromiseId, std::move(aCert)),
NS_DISPATCH_NORMAL);
}
void MediaDrmCDMProxy::UpdateSession(const nsAString& aSessionId,
PromiseId aPromiseId,
nsTArray<uint8_t>& aResponse) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mOwnerThread);
NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
UniquePtr<UpdateSessionData> data(new UpdateSessionData());
data->mPromiseId = aPromiseId;
data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
data->mResponse = std::move(aResponse);
nsCOMPtr<nsIRunnable> task(NewRunnableMethod<UniquePtr<UpdateSessionData>&&>(
"MediaDrmCDMProxy::md_UpdateSession", this,
&MediaDrmCDMProxy::md_UpdateSession, std::move(data)));
mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
}
void MediaDrmCDMProxy::CloseSession(const nsAString& aSessionId,
PromiseId aPromiseId) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mOwnerThread);
NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
UniquePtr<SessionOpData> data(new SessionOpData());
data->mPromiseId = aPromiseId;
data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
nsCOMPtr<nsIRunnable> task(NewRunnableMethod<UniquePtr<SessionOpData>&&>(
"MediaDrmCDMProxy::md_CloseSession", this,
&MediaDrmCDMProxy::md_CloseSession, std::move(data)));
mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
}
void MediaDrmCDMProxy::RemoveSession(const nsAString& aSessionId,
PromiseId aPromiseId) {
// TODO: Implement RemoveSession.
RejectPromiseWithStateError(
aPromiseId, "Currently Fennec does not support RemoveSession"_ns);
}
void MediaDrmCDMProxy::QueryOutputProtectionStatus() {
// TODO(bryce): determine if this is needed for Android and implement as
// needed. See also `NotifyOutputProtectionStatus`.
}
void MediaDrmCDMProxy::NotifyOutputProtectionStatus(
OutputProtectionCheckStatus aCheckStatus,
OutputProtectionCaptureStatus aCaptureStatus) {
// TODO(bryce): determine if this is needed for Android and implement as
// needed. See also `QueryOutputProtectionStatus`.
}
void MediaDrmCDMProxy::Shutdown() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mOwnerThread);
nsCOMPtr<nsIRunnable> task(NewRunnableMethod(
"MediaDrmCDMProxy::md_Shutdown", this, &MediaDrmCDMProxy::md_Shutdown));
mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
mOwnerThread->Shutdown();
mOwnerThread = nullptr;
mKeys.Clear();
}
void MediaDrmCDMProxy::Terminated() {
// TODO: Implement Terminated.
// Should find a way to handle the case when remote side MediaDrm crashed.
}
void MediaDrmCDMProxy::OnSetSessionId(uint32_t aCreateSessionToken,
const nsAString& aSessionId) {
MOZ_ASSERT(NS_IsMainThread());
if (mKeys.IsNull()) {
return;
}
RefPtr<dom::MediaKeySession> session(
mKeys->GetPendingSession(aCreateSessionToken));
if (session) {
session->SetSessionId(aSessionId);
}
}
void MediaDrmCDMProxy::OnResolveLoadSessionPromise(uint32_t aPromiseId,
bool aSuccess) {
MOZ_ASSERT(NS_IsMainThread());
if (mKeys.IsNull()) {
return;
}
mKeys->OnSessionLoaded(aPromiseId, aSuccess);
}
void MediaDrmCDMProxy::OnSessionMessage(const nsAString& aSessionId,
dom::MediaKeyMessageType aMessageType,
const nsTArray<uint8_t>& aMessage) {
MOZ_ASSERT(NS_IsMainThread());
if (mKeys.IsNull()) {
return;
}
RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
if (session) {
session->DispatchKeyMessage(aMessageType, aMessage);
}
}
void MediaDrmCDMProxy::OnExpirationChange(const nsAString& aSessionId,
UnixTime aExpiryTime) {
MOZ_ASSERT(NS_IsMainThread());
if (mKeys.IsNull()) {
return;
}
RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
if (session) {
session->SetExpiration(static_cast<double>(aExpiryTime));
}
}
void MediaDrmCDMProxy::OnSessionClosed(const nsAString& aSessionId) {
MOZ_ASSERT(NS_IsMainThread());
if (mKeys.IsNull()) {
return;
}
RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
if (session) {
session->OnClosed();
}
}
void MediaDrmCDMProxy::OnSessionError(const nsAString& aSessionId,
nsresult aException, uint32_t aSystemCode,
const nsAString& aMsg) {
MOZ_ASSERT(NS_IsMainThread());
if (mKeys.IsNull()) {
return;
}
RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
if (session) {
session->DispatchKeyError(aSystemCode);
}
}
void MediaDrmCDMProxy::OnRejectPromise(uint32_t aPromiseId,
ErrorResult&& aException,
const nsCString& aMsg) {
MOZ_ASSERT(NS_IsMainThread());
RejectPromise(aPromiseId, std::move(aException), aMsg);
}
RefPtr<DecryptPromise> MediaDrmCDMProxy::Decrypt(MediaRawData* aSample) {
MOZ_ASSERT_UNREACHABLE("Fennec could not handle decrypting individually");
return nullptr;
}
void MediaDrmCDMProxy::OnDecrypted(uint32_t aId, DecryptStatus aResult,
const nsTArray<uint8_t>& aDecryptedData) {
MOZ_ASSERT_UNREACHABLE("Fennec could not handle decrypted event");
}
void MediaDrmCDMProxy::RejectPromise(PromiseId aId, ErrorResult&& aException,
const nsCString& aReason) {
if (NS_IsMainThread()) {
if (!mKeys.IsNull()) {
mKeys->RejectPromise(aId, std::move(aException), aReason);
}
} else {
nsCOMPtr<nsIRunnable> task(
new RejectPromiseTask(this, aId, std::move(aException), aReason));
mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
}
}
void MediaDrmCDMProxy::RejectPromiseWithStateError(PromiseId aId,
const nsCString& aReason) {
ErrorResult rv;
rv.ThrowInvalidStateError(aReason);
RejectPromise(aId, std::move(rv), aReason);
}
void MediaDrmCDMProxy::ResolvePromise(PromiseId aId) {
if (NS_IsMainThread()) {
if (!mKeys.IsNull()) {
mKeys->ResolvePromise(aId);
} else {
NS_WARNING("MediaDrmCDMProxy unable to resolve promise!");
}
} else {
nsCOMPtr<nsIRunnable> task;
task =
NewRunnableMethod<PromiseId>("MediaDrmCDMProxy::ResolvePromise", this,
&MediaDrmCDMProxy::ResolvePromise, aId);
mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
}
}
template <typename T>
void MediaDrmCDMProxy::ResolvePromiseWithResult(PromiseId aId,
const T& aResult) {
if (NS_IsMainThread()) {
if (!mKeys.IsNull()) {
mKeys->ResolvePromiseWithResult(aId, aResult);
} else {
NS_WARNING("MediaDrmCDMProxy unable to resolve promise!");
}
return;
}
nsCOMPtr<nsIRunnable> task;
task = NewRunnableMethod<PromiseId, T>(
"MediaDrmCDMProxy::ResolvePromiseWithResult", this,
&MediaDrmCDMProxy::ResolvePromiseWithResult<T>, aId, aResult);
mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
}
void MediaDrmCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId) {
MOZ_ASSERT(NS_IsMainThread());
if (mKeys.IsNull()) {
return;
}
RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
if (session) {
session->DispatchKeyStatusesChange();
}
}
void MediaDrmCDMProxy::GetStatusForPolicy(
PromiseId aPromiseId, const dom::HDCPVersion& aMinHdcpVersion) {
// TODO: Implement GetStatusForPolicy.
constexpr auto err =
"Currently Fennec does not support GetStatusForPolicy"_ns;
ErrorResult rv;
rv.ThrowNotSupportedError(err);
RejectPromise(aPromiseId, std::move(rv), err);
}
#ifdef DEBUG
bool MediaDrmCDMProxy::IsOnOwnerThread() {
return NS_GetCurrentThread() == mOwnerThread;
}
#endif
const nsString& MediaDrmCDMProxy::GetMediaDrmStubId() const {
MOZ_ASSERT(mCDM);
return mCDM->GetMediaDrmStubId();
}
void MediaDrmCDMProxy::OnCDMCreated(uint32_t aPromiseId) {
MOZ_ASSERT(NS_IsMainThread());
if (mKeys.IsNull()) {
return;
}
if (mCDM) {
mKeys->OnCDMCreated(aPromiseId, 0);
return;
}
// No CDM? Just reject the promise.
constexpr auto err = "Null CDM in OnCDMCreated()"_ns;
ErrorResult rv;
rv.ThrowInvalidStateError(err);
mKeys->RejectPromise(aPromiseId, std::move(rv), err);
}
void MediaDrmCDMProxy::md_Init(uint32_t aPromiseId) {
MOZ_ASSERT(IsOnOwnerThread());
MOZ_ASSERT(mCDM);
UniquePtr<MediaDrmCDMCallbackProxy> callback(
new MediaDrmCDMCallbackProxy(this));
mCDM->Init(std::move(callback));
nsCOMPtr<nsIRunnable> task(
NewRunnableMethod<uint32_t>("MediaDrmCDMProxy::OnCDMCreated", this,
&MediaDrmCDMProxy::OnCDMCreated, aPromiseId));
mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
}
void MediaDrmCDMProxy::md_CreateSession(UniquePtr<CreateSessionData>&& aData) {
MOZ_ASSERT(IsOnOwnerThread());
if (!mCDM) {
RejectPromiseWithStateError(aData->mPromiseId,
"Null CDM in md_CreateSession"_ns);
return;
}
mCDM->CreateSession(aData->mCreateSessionToken, aData->mPromiseId,
aData->mInitDataType, aData->mInitData,
ToMediaDrmSessionType(aData->mSessionType));
}
void MediaDrmCDMProxy::md_SetServerCertificate(PromiseId aPromiseId,
const nsTArray<uint8_t>& aCert) {
MOZ_ASSERT(IsOnOwnerThread());
if (!mCDM) {
RejectPromiseWithStateError(aPromiseId,
"Null CDM in md_SetServerCertificate"_ns);
return;
}
if (mCDM->SetServerCertificate(aCert)) {
ResolvePromiseWithResult(aPromiseId, true);
} else {
RejectPromiseWithStateError(
aPromiseId, "MediaDrmCDMProxy unable to set server certificate"_ns);
}
}
void MediaDrmCDMProxy::md_UpdateSession(UniquePtr<UpdateSessionData>&& aData) {
MOZ_ASSERT(IsOnOwnerThread());
if (!mCDM) {
RejectPromiseWithStateError(aData->mPromiseId,
"Null CDM in md_UpdateSession"_ns);
return;
}
mCDM->UpdateSession(aData->mPromiseId, aData->mSessionId, aData->mResponse);
}
void MediaDrmCDMProxy::md_CloseSession(UniquePtr<SessionOpData>&& aData) {
MOZ_ASSERT(IsOnOwnerThread());
if (!mCDM) {
RejectPromiseWithStateError(aData->mPromiseId,
"Null CDM in md_CloseSession"_ns);
return;
}
mCDM->CloseSession(aData->mPromiseId, aData->mSessionId);
}
void MediaDrmCDMProxy::md_Shutdown() {
MOZ_ASSERT(IsOnOwnerThread());
MOZ_ASSERT(mCDM);
if (mShutdownCalled) {
return;
}
mShutdownCalled = true;
mCDM->Shutdown();
mCDM = nullptr;
}
} // namespace mozilla