Bug 1043147 - Update CDMProxy and EME JS APIs to proxy calls to a GMP/CDM. r=ehsan

This commit is contained in:
Chris Pearce 2014-07-30 18:53:28 +12:00
parent 00b5341e5c
commit 8e5a39d66d
14 changed files with 1292 additions and 97 deletions

View File

@ -0,0 +1,284 @@
/* -*- 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,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/CDMCallbackProxy.h"
#include "mozilla/CDMProxy.h"
#include "nsString.h"
#include "mozilla/dom/MediaKeys.h"
#include "mozilla/dom/MediaKeySession.h"
#include "mozIGeckoMediaPluginService.h"
#include "nsContentCID.h"
#include "nsServiceManagerUtils.h"
#include "MainThreadUtils.h"
#include "EMELog.h"
namespace mozilla {
CDMCallbackProxy::CDMCallbackProxy(CDMProxy* aProxy)
: mProxy(aProxy)
{
}
class NewSessionTask : public nsRunnable {
public:
NewSessionTask(CDMProxy* aProxy,
uint32_t aPromiseId,
const nsCString& aSessionId)
: mProxy(aProxy)
, mPid(aPromiseId)
, mSid(NS_ConvertUTF8toUTF16(aSessionId))
{
}
NS_IMETHOD Run() {
mProxy->OnResolveNewSessionPromise(mPid, mSid);
return NS_OK;
}
nsRefPtr<CDMProxy> mProxy;
dom::PromiseId mPid;
nsString mSid;
};
void
CDMCallbackProxy::ResolveNewSessionPromise(uint32_t aPromiseId,
const nsCString& aSessionId)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
nsRefPtr<nsIRunnable> task(new NewSessionTask(mProxy,
aPromiseId,
aSessionId));
NS_DispatchToMainThread(task);
}
void
CDMCallbackProxy::ResolvePromise(uint32_t aPromiseId)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
// Note: CDMProxy proxies this from non-main threads to main thread.
mProxy->ResolvePromise(aPromiseId);
}
class RejectPromiseTask : public nsRunnable {
public:
RejectPromiseTask(CDMProxy* aProxy,
uint32_t aPromiseId,
nsresult aException,
const nsCString& aMessage)
: mProxy(aProxy)
, mPid(aPromiseId)
, mException(aException)
, mMsg(NS_ConvertUTF8toUTF16(aMessage))
{
}
NS_IMETHOD Run() {
mProxy->OnRejectPromise(mPid, mException, mMsg);
return NS_OK;
}
nsRefPtr<CDMProxy> mProxy;
dom::PromiseId mPid;
nsresult mException;
nsString mMsg;
};
void
CDMCallbackProxy::RejectPromise(uint32_t aPromiseId,
nsresult aException,
const nsCString& aMessage)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
nsRefPtr<nsIRunnable> task;
task = new RejectPromiseTask(mProxy,
aPromiseId,
aException,
aMessage);
NS_DispatchToMainThread(task);
}
class SessionMessageTask : public nsRunnable {
public:
SessionMessageTask(CDMProxy* aProxy,
const nsCString& aSessionId,
const nsTArray<uint8_t>& aMessage,
const nsCString& aDestinationURL)
: mProxy(aProxy)
, mSid(NS_ConvertUTF8toUTF16(aSessionId))
, mURL(NS_ConvertUTF8toUTF16(aDestinationURL))
{
mMsg.AppendElements(aMessage);
}
NS_IMETHOD Run() {
mProxy->OnSessionMessage(mSid, mMsg, mURL);
return NS_OK;
}
nsRefPtr<CDMProxy> mProxy;
dom::PromiseId mPid;
nsString mSid;
nsTArray<uint8_t> mMsg;
nsString mURL;
};
void
CDMCallbackProxy::SessionMessage(const nsCString& aSessionId,
const nsTArray<uint8_t>& aMessage,
const nsCString& aDestinationURL)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
nsRefPtr<nsIRunnable> task;
task = new SessionMessageTask(mProxy,
aSessionId,
aMessage,
aDestinationURL);
NS_DispatchToMainThread(task);
}
class ExpirationChangeTask : public nsRunnable {
public:
ExpirationChangeTask(CDMProxy* aProxy,
const nsCString& aSessionId,
GMPTimestamp aExpiryTime)
: mProxy(aProxy)
, mSid(NS_ConvertUTF8toUTF16(aSessionId))
, mTimestamp(aExpiryTime)
{}
NS_IMETHOD Run() {
mProxy->OnExpirationChange(mSid, mTimestamp);
return NS_OK;
}
nsRefPtr<CDMProxy> mProxy;
nsString mSid;
GMPTimestamp mTimestamp;
};
void
CDMCallbackProxy::ExpirationChange(const nsCString& aSessionId,
GMPTimestamp aExpiryTime)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
nsRefPtr<nsIRunnable> task;
task = new ExpirationChangeTask(mProxy,
aSessionId,
aExpiryTime);
NS_DispatchToMainThread(task);
}
void
CDMCallbackProxy::SessionClosed(const nsCString& aSessionId)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
nsRefPtr<nsIRunnable> task;
task = NS_NewRunnableMethodWithArg<nsString>(mProxy,
&CDMProxy::OnSessionClosed,
NS_ConvertUTF8toUTF16(aSessionId));
NS_DispatchToMainThread(task);
}
class SessionErrorTask : public nsRunnable {
public:
SessionErrorTask(CDMProxy* aProxy,
const nsCString& aSessionId,
nsresult aException,
uint32_t aSystemCode,
const nsCString& aMessage)
: mProxy(aProxy)
, mSid(NS_ConvertUTF8toUTF16(aSessionId))
, mException(aException)
, mSystemCode(aSystemCode)
, mMsg(NS_ConvertUTF8toUTF16(aMessage))
{}
NS_IMETHOD Run() {
mProxy->OnSessionError(mSid, mException, mSystemCode, mMsg);
return NS_OK;
}
nsRefPtr<CDMProxy> mProxy;
dom::PromiseId mPid;
nsString mSid;
nsresult mException;
uint32_t mSystemCode;
nsString mMsg;
};
void
CDMCallbackProxy::SessionError(const nsCString& aSessionId,
nsresult aException,
uint32_t aSystemCode,
const nsCString& aMessage)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
nsRefPtr<nsIRunnable> task;
task = new SessionErrorTask(mProxy,
aSessionId,
aException,
aSystemCode,
aMessage);
NS_DispatchToMainThread(task);
}
void
CDMCallbackProxy::KeyIdUsable(const nsCString& aSessionId,
const nsTArray<uint8_t>& aKeyId)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
CDMCaps::AutoLock caps(mProxy->Capabilites());
caps.SetKeyUsable(aKeyId, NS_ConvertUTF8toUTF16(aSessionId));
}
void
CDMCallbackProxy::KeyIdNotUsable(const nsCString& aSessionId,
const nsTArray<uint8_t>& aKeyId)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
CDMCaps::AutoLock caps(mProxy->Capabilites());
caps.SetKeyUnusable(aKeyId, NS_ConvertUTF8toUTF16(aSessionId));
}
void
CDMCallbackProxy::SetCaps(uint64_t aCaps)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
CDMCaps::AutoLock caps(mProxy->Capabilites());
caps.SetCaps(aCaps);
}
void
CDMCallbackProxy::Decrypted(uint32_t aId,
GMPErr aResult,
const nsTArray<uint8_t>& aDecryptedData)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
mProxy->gmp_Decrypted(aId, aResult, aDecryptedData);
}
void
CDMCallbackProxy::Terminated()
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
mProxy->gmp_Terminated();
}
} // namespace mozilla

View File

@ -0,0 +1,69 @@
/* -*- 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,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef CDMCallbackProxy_h_
#define CDMCallbackProxy_h_
#include "mozilla/CDMProxy.h"
#include "gmp-decryption.h"
#include "GMPDecryptorProxy.h"
namespace mozilla {
// Proxies call backs from the CDM on the GMP thread back to the MediaKeys
// object on the main thread.
class CDMCallbackProxy : public GMPDecryptorProxyCallback {
public:
virtual void ResolveNewSessionPromise(uint32_t aPromiseId,
const nsCString& aSessionId) MOZ_OVERRIDE;
virtual void ResolvePromise(uint32_t aPromiseId) MOZ_OVERRIDE;
virtual void RejectPromise(uint32_t aPromiseId,
nsresult aException,
const nsCString& aSessionId) MOZ_OVERRIDE;
virtual void SessionMessage(const nsCString& aSessionId,
const nsTArray<uint8_t>& aMessage,
const nsCString& aDestinationURL) MOZ_OVERRIDE;
virtual void ExpirationChange(const nsCString& aSessionId,
GMPTimestamp aExpiryTime) MOZ_OVERRIDE;
virtual void SessionClosed(const nsCString& aSessionId) MOZ_OVERRIDE;
virtual void SessionError(const nsCString& aSessionId,
nsresult aException,
uint32_t aSystemCode,
const nsCString& aMessage) MOZ_OVERRIDE;
virtual void KeyIdUsable(const nsCString& aSessionId,
const nsTArray<uint8_t>& aKeyId) MOZ_OVERRIDE;
virtual void KeyIdNotUsable(const nsCString& aSessionId,
const nsTArray<uint8_t>& aKeyId) MOZ_OVERRIDE;
virtual void SetCaps(uint64_t aCaps) MOZ_OVERRIDE;
virtual void Decrypted(uint32_t aId,
GMPErr aResult,
const nsTArray<uint8_t>& aDecryptedData) MOZ_OVERRIDE;
virtual void Terminated() MOZ_OVERRIDE;
~CDMCallbackProxy() {}
private:
friend class CDMProxy;
explicit CDMCallbackProxy(CDMProxy* aProxy);
// Warning: Weak ref.
CDMProxy* mProxy;
};
} // namespace mozilla
#endif // CDMCallbackProxy_h_

View File

@ -0,0 +1,187 @@
/* -*- 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,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "CDMCaps.h"
#include "gmp-decryption.h"
#include "EMELog.h"
#include "nsThreadUtils.h"
namespace mozilla {
CDMCaps::CDMCaps()
: mMonitor("CDMCaps")
, mCaps(0)
{
}
CDMCaps::~CDMCaps()
{
}
void
CDMCaps::Lock()
{
mMonitor.Lock();
}
void
CDMCaps::Unlock()
{
mMonitor.Unlock();
}
bool
CDMCaps::HasCap(uint64_t aCap)
{
mMonitor.AssertCurrentThreadOwns();
return (mCaps & aCap) == aCap;
}
CDMCaps::AutoLock::AutoLock(CDMCaps& aInstance)
: mData(aInstance)
{
mData.Lock();
}
CDMCaps::AutoLock::~AutoLock()
{
mData.Unlock();
}
void
CDMCaps::AutoLock::SetCaps(uint64_t aCaps)
{
EME_LOG("SetCaps()");
mData.mMonitor.AssertCurrentThreadOwns();
mData.mCaps = aCaps;
for (size_t i = 0; i < mData.mWaitForCaps.Length(); i++) {
NS_DispatchToMainThread(mData.mWaitForCaps[i], NS_DISPATCH_NORMAL);
}
mData.mWaitForCaps.Clear();
}
void
CDMCaps::AutoLock::CallOnMainThreadWhenCapsAvailable(nsIRunnable* aContinuation)
{
mData.mMonitor.AssertCurrentThreadOwns();
if (mData.mCaps) {
NS_DispatchToMainThread(aContinuation, NS_DISPATCH_NORMAL);
MOZ_ASSERT(mData.mWaitForCaps.IsEmpty());
} else {
mData.mWaitForCaps.AppendElement(aContinuation);
}
}
bool
CDMCaps::AutoLock::IsKeyUsable(const CencKeyId& aKeyId)
{
mData.mMonitor.AssertCurrentThreadOwns();
const auto& keys = mData.mUsableKeyIds;
for (size_t i = 0; i < keys.Length(); i++) {
if (keys[i].mId == aKeyId) {
return true;
}
}
return false;
}
void
CDMCaps::AutoLock::SetKeyUsable(const CencKeyId& aKeyId,
const nsString& aSessionId)
{
mData.mMonitor.AssertCurrentThreadOwns();
mData.mUsableKeyIds.AppendElement(UsableKey(aKeyId, aSessionId));
auto& waiters = mData.mWaitForKeys;
size_t i = 0;
while (i < waiters.Length()) {
auto& w = waiters[i];
if (w.mKeyId == aKeyId) {
if (waiters[i].mTarget) {
EME_LOG("SetKeyUsable() notified waiter.");
w.mTarget->Dispatch(w.mContinuation, NS_DISPATCH_NORMAL);
} else {
w.mContinuation->Run();
}
waiters.RemoveElementAt(i);
} else {
i++;
}
}
}
void
CDMCaps::AutoLock::SetKeyUnusable(const CencKeyId& aKeyId,
const nsString& aSessionId)
{
mData.mMonitor.AssertCurrentThreadOwns();
auto& keys = mData.mUsableKeyIds;
for (size_t i = 0; i < keys.Length(); i++) {
if (keys[i].mId == aKeyId &&
keys[i].mSessionId == aSessionId) {
keys.RemoveElementAt(i);
break;
}
}
}
void
CDMCaps::AutoLock::CallWhenKeyUsable(const CencKeyId& aKey,
nsIRunnable* aContinuation,
nsIThread* aTarget)
{
mData.mMonitor.AssertCurrentThreadOwns();
MOZ_ASSERT(!IsKeyUsable(aKey));
MOZ_ASSERT(aContinuation);
mData.mWaitForKeys.AppendElement(WaitForKeys(aKey, aContinuation, aTarget));
}
void
CDMCaps::AutoLock::DropKeysForSession(const nsAString& aSessionId)
{
mData.mMonitor.AssertCurrentThreadOwns();
auto& keys = mData.mUsableKeyIds;
size_t i = 0;
while (i < keys.Length()) {
if (keys[i].mSessionId == aSessionId) {
keys.RemoveElementAt(i);
} else {
i++;
}
}
}
bool
CDMCaps::AutoLock::AreCapsKnown()
{
mData.mMonitor.AssertCurrentThreadOwns();
return mData.mCaps != 0;
}
bool
CDMCaps::AutoLock::CanDecryptAndDecodeAudio()
{
return mData.HasCap(GMP_EME_CAP_DECRYPT_AND_DECODE_AUDIO);
}
bool
CDMCaps::AutoLock::CanDecryptAndDecodeVideo()
{
return mData.HasCap(GMP_EME_CAP_DECRYPT_AND_DECODE_VIDEO);
}
bool
CDMCaps::AutoLock::CanDecryptAudio()
{
return mData.HasCap(GMP_EME_CAP_DECRYPT_AUDIO);
}
bool
CDMCaps::AutoLock::CanDecryptVideo()
{
return mData.HasCap(GMP_EME_CAP_DECRYPT_VIDEO);
}
} // namespace mozilla

117
content/media/eme/CDMCaps.h Normal file
View File

@ -0,0 +1,117 @@
/* -*- 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,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef CDMCaps_h_
#define CDMCaps_h_
#include "nsString.h"
#include "nsAutoPtr.h"
#include "mozilla/Monitor.h"
#include "nsIThread.h"
#include "nsTArray.h"
#include "mozilla/Attributes.h"
namespace mozilla {
typedef nsTArray<uint8_t> CencKeyId;
// CDM capabilities; what keys a CDMProxy can use, and whether it can decrypt, or
// decrypt-and-decode on a per stream basis. Must be locked to access state.
class CDMCaps {
public:
CDMCaps();
~CDMCaps();
// Locks the CDMCaps. It must be locked to access its shared state.
// Threadsafe when locked.
class MOZ_STACK_CLASS AutoLock {
public:
explicit AutoLock(CDMCaps& aKeyCaps);
~AutoLock();
// Returns true if the capabilities of the CDM are known, i.e. they have
// been reported by the CDM to Gecko.
bool AreCapsKnown();
bool IsKeyUsable(const CencKeyId& aKeyId);
void SetKeyUsable(const CencKeyId& aKeyId, const nsString& aSessionId);
void SetKeyUnusable(const CencKeyId& aKeyId, const nsString& aSessionId);
void DropKeysForSession(const nsAString& aSessionId);
// Sets the capabilities of the CDM. aCaps is the logical OR of the
// GMP_EME_CAP_* flags from gmp-decryption.h.
void SetCaps(uint64_t aCaps);
bool CanDecryptAndDecodeAudio();
bool CanDecryptAndDecodeVideo();
bool CanDecryptAudio();
bool CanDecryptVideo();
void CallOnMainThreadWhenCapsAvailable(nsIRunnable* aContinuation);
// Calls aContinuation on aTarget thread when key become usable.
// Pass aTarget=nullptr and runnable will be called on the GMP thread
// when key becomes usable.
void CallWhenKeyUsable(const CencKeyId& aKey,
nsIRunnable* aContinuation,
nsIThread* aTarget = nullptr);
private:
// Not taking a strong ref, since this should be allocated on the stack.
CDMCaps& mData;
};
private:
void Lock();
void Unlock();
bool HasCap(uint64_t);
struct WaitForKeys {
WaitForKeys(const CencKeyId& aKeyId,
nsIRunnable* aContinuation,
nsIThread* aTarget)
: mKeyId(aKeyId)
, mContinuation(aContinuation)
, mTarget(aTarget)
{}
CencKeyId mKeyId;
nsRefPtr<nsIRunnable> mContinuation;
nsCOMPtr<nsIThread> mTarget;
};
Monitor mMonitor;
struct UsableKey {
UsableKey(const CencKeyId& aId,
const nsString& aSessionId)
: mId(aId)
, mSessionId(aSessionId)
{}
UsableKey(const UsableKey& aOther)
: mId(aOther.mId)
, mSessionId(aOther.mSessionId)
{}
CencKeyId mId;
nsString mSessionId;
};
nsTArray<UsableKey> mUsableKeyIds;
nsTArray<WaitForKeys> mWaitForKeys;
nsTArray<nsRefPtr<nsIRunnable>> mWaitForCaps;
uint64_t mCaps;
// It is not safe to copy this object.
CDMCaps(const CDMCaps&) MOZ_DELETE;
CDMCaps& operator=(const CDMCaps&) MOZ_DELETE;
};
} // namespace mozilla
#endif

View File

@ -12,16 +12,19 @@
#include "nsContentCID.h"
#include "nsServiceManagerUtils.h"
#include "MainThreadUtils.h"
// TODO: Change the functions in this file to do IPC via the Gecko Media
// Plugins API. In the meantime, the code here merely implements the
// interface we expect will be required when the IPC is working.
#include "mozilla/EMELog.h"
#include "nsIConsoleService.h"
#include "prenv.h"
#include "mozilla/PodOperations.h"
#include "mozilla/CDMCallbackProxy.h"
namespace mozilla {
CDMProxy::CDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem)
: mKeys(aKeys)
, mKeySystem(aKeySystem)
, mCDM(nullptr)
, mDecryptionJobCount(0)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_COUNT_CTOR(CDMProxy);
@ -36,6 +39,15 @@ void
CDMProxy::Init(PromiseId aPromiseId)
{
MOZ_ASSERT(NS_IsMainThread());
nsresult rv = mKeys->GetOrigin(mOrigin);
if (NS_FAILED(rv)) {
RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
EME_LOG("Creating CDMProxy for origin='%s'",
NS_ConvertUTF16toUTF8(GetOrigin()).get());
if (!mGMPThread) {
nsCOMPtr<mozIGeckoMediaPluginService> mps =
do_GetService("@mozilla.org/gecko-media-plugin-service;1");
@ -50,12 +62,53 @@ CDMProxy::Init(PromiseId aPromiseId)
}
}
// TODO: Dispatch task to GMPThread to initialize CDM via IPC.
mKeys->OnCDMCreated(aPromiseId);
nsRefPtr<nsIRunnable> task(NS_NewRunnableMethodWithArg<uint32_t>(this, &CDMProxy::gmp_Init, aPromiseId));
mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
}
static int sFakeSessionIdNum = 0;
#ifdef DEBUG
bool
CDMProxy::IsOnGMPThread()
{
return NS_GetCurrentThread() == mGMPThread;
}
#endif
void
CDMProxy::gmp_Init(uint32_t aPromiseId)
{
MOZ_ASSERT(IsOnGMPThread());
nsCOMPtr<mozIGeckoMediaPluginService> mps =
do_GetService("@mozilla.org/gecko-media-plugin-service;1");
if (!mps) {
RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
nsTArray<nsCString> tags;
tags.AppendElement(NS_ConvertUTF16toUTF8(mKeySystem));
nsresult rv = mps->GetGMPDecryptor(&tags, GetOrigin(), &mCDM);
if (NS_FAILED(rv) || !mCDM) {
RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
} else {
mCallback = new CDMCallbackProxy(this);
mCDM->Init(mCallback);
nsRefPtr<nsIRunnable> task(NS_NewRunnableMethodWithArg<uint32_t>(this, &CDMProxy::OnCDMCreated, aPromiseId));
NS_DispatchToMainThread(task);
}
}
void
CDMProxy::OnCDMCreated(uint32_t aPromiseId)
{
MOZ_ASSERT(NS_IsMainThread());
if (!mKeys.IsNull()) {
mKeys->OnCDMCreated(aPromiseId);
} else {
NS_WARNING("CDMProxy unable to reject promise!");
}
}
void
CDMProxy::CreateSession(dom::SessionType aSessionType,
@ -66,14 +119,38 @@ CDMProxy::CreateSession(dom::SessionType aSessionType,
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mGMPThread);
// TODO: Dispatch task to GMPThread to call CDM CreateSession via IPC.
nsAutoPtr<CreateSessionData> data(new CreateSessionData());
data->mSessionType = aSessionType;
data->mPromiseId = aPromiseId;
data->mInitDataType = NS_ConvertUTF16toUTF8(aInitDataType);
data->mInitData.AppendElements(aInitData.Data(), aInitData.Length());
// Make a fake session id. We'll get this from the CDM normally.
nsAutoString id;
id.AppendASCII("FakeSessionId_");
id.AppendInt(sFakeSessionIdNum++);
nsRefPtr<nsIRunnable> task(
NS_NewRunnableMethodWithArg<nsAutoPtr<CreateSessionData>>(this, &CDMProxy::gmp_CreateSession, data));
mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
}
mKeys->OnSessionActivated(aPromiseId, id);
GMPSessionType
ToGMPSessionType(dom::SessionType aSessionType) {
switch (aSessionType) {
case dom::SessionType::Temporary: return kGMPTemporySession;
case dom::SessionType::Persistent: return kGMPPersistentSession;
default: return kGMPTemporySession;
};
};
void
CDMProxy::gmp_CreateSession(nsAutoPtr<CreateSessionData> aData)
{
MOZ_ASSERT(IsOnGMPThread());
if (!mCDM) {
RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
mCDM->CreateSession(aData->mPromiseId,
aData->mInitDataType,
aData->mInitData,
ToGMPSessionType(aData->mSessionType));
}
void
@ -83,25 +160,51 @@ CDMProxy::LoadSession(PromiseId aPromiseId,
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mGMPThread);
// TODO: Dispatch task to GMPThread to call CDM LoadSession via IPC.
// make MediaKeys::mPendingSessions CC'd
nsAutoPtr<SessionOpData> data(new SessionOpData());
data->mPromiseId = aPromiseId;
data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
nsRefPtr<nsIRunnable> task(
NS_NewRunnableMethodWithArg<nsAutoPtr<SessionOpData>>(this, &CDMProxy::gmp_LoadSession, data));
mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
}
mKeys->OnSessionActivated(aPromiseId, aSessionId);
void
CDMProxy::gmp_LoadSession(nsAutoPtr<SessionOpData> aData)
{
MOZ_ASSERT(IsOnGMPThread());
if (!mCDM) {
RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
mCDM->LoadSession(aData->mPromiseId, aData->mSessionId);
}
void
CDMProxy::SetServerCertificate(PromiseId aPromiseId,
const Uint8Array& aCertData)
const Uint8Array& aCert)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mGMPThread);
// TODO: Dispatch task to GMPThread to call CDM SetServerCertificate via IPC.
ResolvePromise(aPromiseId);
nsAutoPtr<SetServerCertificateData> data;
data->mPromiseId = aPromiseId;
data->mCert.AppendElements(aCert.Data(), aCert.Length());
nsRefPtr<nsIRunnable> task(
NS_NewRunnableMethodWithArg<nsAutoPtr<SetServerCertificateData>>(this, &CDMProxy::gmp_SetServerCertificate, data));
mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
}
static int sUpdateCount = 0;
void
CDMProxy::gmp_SetServerCertificate(nsAutoPtr<SetServerCertificateData> aData)
{
MOZ_ASSERT(IsOnGMPThread());
if (!mCDM) {
RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
mCDM->SetServerCertificate(aData->mPromiseId, aData->mCert);
}
void
CDMProxy::UpdateSession(const nsAString& aSessionId,
@ -112,15 +215,26 @@ CDMProxy::UpdateSession(const nsAString& aSessionId,
MOZ_ASSERT(mGMPThread);
NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
// TODO: Dispatch task to GMPThread to call CDM UpdateSession via IPC.
nsAutoPtr<UpdateSessionData> data(new UpdateSessionData());
data->mPromiseId = aPromiseId;
data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
data->mResponse.AppendElements(aResponse.Data(), aResponse.Length());
nsRefPtr<nsIRunnable> task(
NS_NewRunnableMethodWithArg<nsAutoPtr<UpdateSessionData>>(this, &CDMProxy::gmp_UpdateSession, data));
mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
}
nsRefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
nsAutoCString str(NS_LITERAL_CSTRING("Update_"));
str.AppendInt(sUpdateCount++);
nsTArray<uint8_t> msg;
msg.AppendElements(str.get(), str.Length());
session->DispatchKeyMessage(msg, NS_LITERAL_STRING("http://bogus.url"));
ResolvePromise(aPromiseId);
void
CDMProxy::gmp_UpdateSession(nsAutoPtr<UpdateSessionData> aData)
{
MOZ_ASSERT(IsOnGMPThread());
if (!mCDM) {
RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
mCDM->UpdateSession(aData->mPromiseId,
aData->mSessionId,
aData->mResponse);
}
void
@ -130,17 +244,27 @@ CDMProxy::CloseSession(const nsAString& aSessionId,
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
// TODO: Dispatch task to GMPThread to call CDM CloseSession via IPC.
{
CDMCaps::AutoLock caps(Capabilites());
caps.DropKeysForSession(aSessionId);
}
nsAutoPtr<SessionOpData> data(new SessionOpData());
data->mPromiseId = aPromiseId;
data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
nsRefPtr<nsIRunnable> task(
NS_NewRunnableMethodWithArg<nsAutoPtr<SessionOpData>>(this, &CDMProxy::gmp_CloseSession, data));
mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
}
nsRefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
// Pretend that the CDM actually does close the session...
// "...the [MediaKeySession's] closed attribute promise is resolved
// when the session is closed."
session->OnClosed();
// "The promise is resolved when the request has been processed."
ResolvePromise(aPromiseId);
void
CDMProxy::gmp_CloseSession(nsAutoPtr<SessionOpData> aData)
{
MOZ_ASSERT(IsOnGMPThread());
if (!mCDM) {
RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
mCDM->CloseSession(aData->mPromiseId, aData->mSessionId);
}
void
@ -148,12 +272,29 @@ CDMProxy::RemoveSession(const nsAString& aSessionId,
PromiseId aPromiseId)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
// TODO: Dispatch task to GMPThread to call CDM RemoveSession via IPC.
{
CDMCaps::AutoLock caps(Capabilites());
caps.DropKeysForSession(aSessionId);
}
nsAutoPtr<SessionOpData> data(new SessionOpData());
data->mPromiseId = aPromiseId;
data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
nsRefPtr<nsIRunnable> task(
NS_NewRunnableMethodWithArg<nsAutoPtr<SessionOpData>>(this, &CDMProxy::gmp_RemoveSession, data));
mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
}
// Assume CDM immediately removes session's data, then close the session
// as per the spec.
CloseSession(aSessionId, aPromiseId);
void
CDMProxy::gmp_RemoveSession(nsAutoPtr<SessionOpData> aData)
{
MOZ_ASSERT(IsOnGMPThread());
if (!mCDM) {
RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
mCDM->RemoveSession(aData->mPromiseId, aData->mSessionId);
}
void
@ -196,4 +337,158 @@ CDMProxy::ResolvePromise(PromiseId aId)
}
}
const nsAString&
CDMProxy::GetOrigin() const
{
return mOrigin;
}
void
CDMProxy::OnResolveNewSessionPromise(uint32_t aPromiseId,
const nsAString& aSessionId)
{
MOZ_ASSERT(NS_IsMainThread());
mKeys->OnSessionCreated(aPromiseId, aSessionId);
}
void
CDMProxy::OnSessionMessage(const nsAString& aSessionId,
nsTArray<uint8_t>& aMessage,
const nsAString& aDestinationURL)
{
MOZ_ASSERT(NS_IsMainThread());
nsRefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
if (session) {
session->DispatchKeyMessage(aMessage, aDestinationURL);
}
}
void
CDMProxy::OnExpirationChange(const nsAString& aSessionId,
GMPTimestamp aExpiryTime)
{
MOZ_ASSERT(NS_IsMainThread());
NS_WARNING("CDMProxy::OnExpirationChange() not implemented");
}
void
CDMProxy::OnSessionClosed(const nsAString& aSessionId)
{
MOZ_ASSERT(NS_IsMainThread());
NS_WARNING("CDMProxy::OnSessionClosed() not implemented");
}
static void
LogToConsole(const nsAString& aMsg)
{
nsCOMPtr<nsIConsoleService> console(
do_GetService("@mozilla.org/consoleservice;1"));
if (!console) {
NS_WARNING("Failed to log message to console.");
return;
}
nsAutoString msg(aMsg);
console->LogStringMessage(msg.get());
}
void
CDMProxy::OnSessionError(const nsAString& aSessionId,
nsresult aException,
uint32_t aSystemCode,
const nsAString& aMsg)
{
MOZ_ASSERT(NS_IsMainThread());
nsRefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
if (session) {
session->DispatchKeyError(aSystemCode);
}
LogToConsole(aMsg);
}
void
CDMProxy::OnRejectPromise(uint32_t aPromiseId,
nsresult aDOMException,
const nsAString& aMsg)
{
MOZ_ASSERT(NS_IsMainThread());
RejectPromise(aPromiseId, aDOMException);
LogToConsole(aMsg);
}
const nsString&
CDMProxy::KeySystem() const
{
return mKeySystem;
}
CDMCaps&
CDMProxy::Capabilites() {
return mCapabilites;
}
void
CDMProxy::Decrypt(mp4_demuxer::MP4Sample* aSample,
DecryptionClient* aClient)
{
nsAutoPtr<DecryptJob> job(new DecryptJob(aSample, aClient));
nsRefPtr<nsIRunnable> task(
NS_NewRunnableMethodWithArg<nsAutoPtr<DecryptJob>>(this, &CDMProxy::gmp_Decrypt, job));
mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
}
void
CDMProxy::gmp_Decrypt(nsAutoPtr<DecryptJob> aJob)
{
MOZ_ASSERT(IsOnGMPThread());
MOZ_ASSERT(aJob->mClient);
MOZ_ASSERT(aJob->mSample);
if (!mCDM) {
aJob->mClient->Decrypted(NS_ERROR_FAILURE, nullptr);
return;
}
aJob->mId = ++mDecryptionJobCount;
nsTArray<uint8_t> data;
data.AppendElements(aJob->mSample->data, aJob->mSample->size);
mCDM->Decrypt(aJob->mId, aJob->mSample->crypto, data);
mDecryptionJobs.AppendElement(aJob.forget());
}
void
CDMProxy::gmp_Decrypted(uint32_t aId,
GMPErr aResult,
const nsTArray<uint8_t>& aDecryptedData)
{
MOZ_ASSERT(IsOnGMPThread());
for (size_t i = 0; i < mDecryptionJobs.Length(); i++) {
DecryptJob* job = mDecryptionJobs[i];
if (job->mId == aId) {
if (aDecryptedData.Length() != job->mSample->size) {
NS_WARNING("CDM returned incorrect number of decrypted bytes");
}
PodCopy(job->mSample->data,
aDecryptedData.Elements(),
std::min<size_t>(aDecryptedData.Length(), job->mSample->size));
nsresult rv = GMP_SUCCEEDED(aResult) ? NS_OK : NS_ERROR_FAILURE;
job->mClient->Decrypted(rv, job->mSample.forget());
mDecryptionJobs.RemoveElementAt(i);
return;
} else {
NS_WARNING("GMPDecryptorChild returned incorrect job ID");
}
}
}
void
CDMProxy::gmp_Terminated()
{
MOZ_ASSERT(IsOnGMPThread());
EME_LOG("CDM terminated");
if (mCDM) {
mCDM->Close();
mCDM = nullptr;
}
}
} // namespace mozilla

View File

@ -9,22 +9,30 @@
#include "nsString.h"
#include "nsAutoPtr.h"
#include "nsProxyRelease.h"
#include "mozilla/dom/MediaKeys.h"
#include "mozilla/dom/TypedArray.h"
class nsIThread;
#include "mozilla/Monitor.h"
#include "nsIThread.h"
#include "GMPDecryptorProxy.h"
#include "mozilla/CDMCaps.h"
#include "mp4_demuxer/DecoderData.h"
namespace mozilla {
class CDMCallbackProxy;
namespace dom {
class MediaKeySession;
}
// A placeholder proxy to the CDM.
// TODO: The functions here need to do IPC to talk to the CDM via the
// Gecko Media Plugin API, which we'll need to extend for H.264 and EME
// content.
class DecryptionClient {
public:
virtual ~DecryptionClient() {}
virtual void Decrypted(nsresult aResult,
mp4_demuxer::MP4Sample* aSample) = 0;
};
// Proxies calls GMP/CDM, and proxies calls back.
// Note: Promises are passed in via a PromiseId, so that the ID can be
// passed via IPC to the CDM, which can then signal when to reject or
// resolve the promise using its PromiseId.
@ -95,8 +103,124 @@ public:
// Main thread only.
void Shutdown();
// Threadsafe.
const nsAString& GetOrigin() const;
// Main thread only.
void OnResolveNewSessionPromise(uint32_t aPromiseId,
const nsAString& aSessionId);
// Main thread only.
void OnSessionMessage(const nsAString& aSessionId,
nsTArray<uint8_t>& aMessage,
const nsAString& aDestinationURL);
// Main thread only.
void OnExpirationChange(const nsAString& aSessionId,
GMPTimestamp aExpiryTime);
// Main thread only.
void OnSessionClosed(const nsAString& aSessionId);
// Main thread only.
void OnSessionError(const nsAString& aSessionId,
nsresult aException,
uint32_t aSystemCode,
const nsAString& aMsg);
// Main thread only.
void OnRejectPromise(uint32_t aPromiseId,
nsresult aDOMException,
const nsAString& aMsg);
// Threadsafe.
void Decrypt(mp4_demuxer::MP4Sample* aSample,
DecryptionClient* aSink);
// Reject promise with DOMException corresponding to aExceptionCode.
// Can be called from any thread.
void RejectPromise(PromiseId aId, nsresult aExceptionCode);
// Resolves promise with "undefined".
// Can be called from any thread.
void ResolvePromise(PromiseId aId);
// Threadsafe.
const nsString& KeySystem() const;
// GMP thread only.
void gmp_Decrypted(uint32_t aId,
GMPErr aResult,
const nsTArray<uint8_t>& aDecryptedData);
// GMP thread only.
void gmp_Terminated();
CDMCaps& Capabilites();
#ifdef DEBUG
bool IsOnGMPThread();
#endif
private:
// GMP thread only.
void gmp_Init(uint32_t aPromiseId);
// Main thread only.
void OnCDMCreated(uint32_t aPromiseId);
struct CreateSessionData {
dom::SessionType mSessionType;
PromiseId mPromiseId;
nsAutoCString mInitDataType;
nsTArray<uint8_t> mInitData;
};
// GMP thread only.
void gmp_CreateSession(nsAutoPtr<CreateSessionData> aData);
struct SessionOpData {
PromiseId mPromiseId;
nsAutoCString mSessionId;
};
// GMP thread only.
void gmp_LoadSession(nsAutoPtr<SessionOpData> aData);
struct SetServerCertificateData {
PromiseId mPromiseId;
nsTArray<uint8_t> mCert;
};
// GMP thread only.
void gmp_SetServerCertificate(nsAutoPtr<SetServerCertificateData> aData);
struct UpdateSessionData {
PromiseId mPromiseId;
nsAutoCString mSessionId;
nsTArray<uint8_t> mResponse;
};
// GMP thread only.
void gmp_UpdateSession(nsAutoPtr<UpdateSessionData> aData);
// GMP thread only.
void gmp_CloseSession(nsAutoPtr<SessionOpData> aData);
// GMP thread only.
void gmp_RemoveSession(nsAutoPtr<SessionOpData> aData);
struct DecryptJob {
DecryptJob(mp4_demuxer::MP4Sample* aSample,
DecryptionClient* aClient)
: mId(0)
, mSample(aSample)
, mClient(aClient)
{}
uint32_t mId;
nsAutoPtr<mp4_demuxer::MP4Sample> mSample;
nsAutoPtr<DecryptionClient> mClient;
};
// GMP thread only.
void gmp_Decrypt(nsAutoPtr<DecryptJob> aJob);
class RejectPromiseTask : public nsRunnable {
public:
RejectPromiseTask(CDMProxy* aProxy,
@ -117,13 +241,6 @@ private:
nsresult mCode;
};
// Reject promise with DOMException corresponding to aExceptionCode.
// Can be called from any thread.
void RejectPromise(PromiseId aId, nsresult aExceptionCode);
// Resolves promise with "undefined".
// Can be called from any thread.
void ResolvePromise(PromiseId aId);
~CDMProxy();
// Helper to enforce that a raw pointer is only accessed on the main thread.
@ -164,8 +281,26 @@ private:
// Gecko Media Plugin thread. All interactions with the out-of-process
// EME plugin must come from this thread.
nsRefPtr<nsIThread> mGMPThread;
nsAutoString mOrigin;
GMPDecryptorProxy* mCDM;
CDMCaps mCapabilites;
nsAutoPtr<CDMCallbackProxy> mCallback;
// Decryption jobs sent to CDM, awaiting result.
// GMP thread only.
nsTArray<nsAutoPtr<DecryptJob>> mDecryptionJobs;
// Number of buffers we've decrypted. Used to uniquely identify
// decryption jobs sent to CDM. Note we can't just use the length of
// mDecryptionJobs as that shrinks as jobs are completed and removed
// from it.
// GMP thread only.
uint32_t mDecryptionJobCount;
};
} // namespace mozilla
#endif // CDMProxy_h_

View File

@ -4,32 +4,45 @@
* 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/. */
#ifndef EME_LOG_H_
#define EME_LOG_H_
#include "prlog.h"
namespace mozilla {
#ifdef PR_LOGGING
#ifndef EME_LOG
PRLogModuleInfo* GetEMELog();
#define EME_LOG(...) PR_LOG(GetEMELog(), PR_LOG_DEBUG, (__VA_ARGS__))
#endif
#ifndef EME_LOG
PRLogModuleInfo* GetEMELog();
#define EME_LOG(...) PR_LOG(GetEMELog(), PR_LOG_DEBUG, (__VA_ARGS__))
#endif
#ifndef EME_VERBOSE_LOG
PRLogModuleInfo* GetEMEVerboseLog();
#define EME_VERBOSE_LOG(...) PR_LOG(GetEMEVerboseLog(), PR_LOG_DEBUG, (__VA_ARGS__))
#ifndef EME_VERBOSE_LOG
PRLogModuleInfo* GetEMEVerboseLog();
#define EME_VERBOSE_LOG(...) PR_LOG(GetEMEVerboseLog(), PR_LOG_DEBUG, (__VA_ARGS__))
#else
#ifndef EME_LOG
#define EME_LOG(...)
#endif
#ifndef EME_VERBOSE_LOG
#define EME_VERBOSE_LOG(...)
#endif
#endif
#else
#ifndef EME_LOG
#define EME_LOG(...)
#endif
#ifndef EME_LOG
#define EME_LOG(...)
#endif
#ifndef EME_VERBOSE_LOG
#define EME_VERBOSE_LOG(...)
#endif
#endif
#ifndef EME_VERBOSE_LOG
#define EME_VERBOSE_LOG(...)
#endif
#endif // PR_LOGGING
} // namespace mozilla
#endif // EME_LOG_H_

View File

@ -16,6 +16,7 @@ MediaKeyError::MediaKeyError(EventTarget* aOwner, uint32_t aSystemCode)
, mSystemCode(aSystemCode)
{
SetIsDOMBinding();
InitEvent(NS_LITERAL_STRING("error"), false, false);
}
MediaKeyError::~MediaKeyError()

View File

@ -67,7 +67,13 @@ MediaKeySession::GetKeySystem(nsString& aKeySystem) const
void
MediaKeySession::GetSessionId(nsString& aSessionId) const
{
aSessionId = mSessionId;
aSessionId = GetSessionId();
}
const nsString&
MediaKeySession::GetSessionId() const
{
return mSessionId;
}
JSObject*
@ -166,7 +172,7 @@ MediaKeySession::Remove(ErrorResult& aRv)
void
MediaKeySession::DispatchKeyMessage(const nsTArray<uint8_t>& aMessage,
const nsString& aURL)
const nsAString& aURL)
{
nsRefPtr<MediaKeyMessageEvent> event(
MediaKeyMessageEvent::Constructor(this, aURL, aMessage));

View File

@ -53,6 +53,8 @@ public:
void GetSessionId(nsString& aRetval) const;
const nsString& GetSessionId() const;
// Number of ms since epoch at which expiration occurs, or NaN if unknown.
// TODO: The type of this attribute is still under contention.
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=25902
@ -68,7 +70,7 @@ public:
already_AddRefed<Promise> Remove(ErrorResult& aRv);
void DispatchKeyMessage(const nsTArray<uint8_t>& aMessage,
const nsString& aURL);
const nsAString& aURL);
void DispatchKeyError(uint32_t system_code);

View File

@ -12,8 +12,13 @@
#include "mozilla/dom/MediaKeySession.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/CDMProxy.h"
#include "mozilla/EMELog.h"
#include "nsContentUtils.h"
#include "EMELog.h"
#include "nsIScriptObjectPrincipal.h"
#include "mozilla/Preferences.h"
#ifdef XP_WIN
#include "mozilla/WindowsVersion.h"
#endif
namespace mozilla {
@ -32,8 +37,9 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeys)
NS_INTERFACE_MAP_END
MediaKeys::MediaKeys(nsPIDOMWindow* aParent, const nsAString& aKeySystem)
: mParent(aParent),
mKeySystem(aKeySystem)
: mParent(aParent)
, mKeySystem(aKeySystem)
, mCreatePromiseId(0)
{
SetIsDOMBinding();
}
@ -76,6 +82,18 @@ MediaKeys::SetServerCertificate(const Uint8Array& aCert, ErrorResult& aRv)
return promise.forget();
}
static bool
IsSupportedKeySystem(const nsAString& aKeySystem)
{
return aKeySystem.EqualsASCII("org.w3.clearkey") ||
#ifdef XP_WIN
(aKeySystem.EqualsASCII("com.adobe.access") &&
IsVistaOrLater() &&
Preferences::GetBool("media.eme.adobe-access.enabled", false)) ||
#endif
false;
}
/* static */
IsTypeSupportedResult
MediaKeys::IsTypeSupported(const GlobalObject& aGlobal,
@ -84,10 +102,10 @@ MediaKeys::IsTypeSupported(const GlobalObject& aGlobal,
const Optional<nsAString>& aContentType,
const Optional<nsAString>& aCapability)
{
// TODO: Query list of known CDMs and their supported content types.
// TODO: Should really get spec changed to this is async, so we can wait
// for user to consent to running plugin.
return IsTypeSupportedResult::Maybe;
return IsSupportedKeySystem(aKeySystem) ? IsTypeSupportedResult::Maybe
: IsTypeSupportedResult::_empty;
}
already_AddRefed<Promise>
@ -115,6 +133,7 @@ MediaKeys::StorePromise(Promise* aPromise)
already_AddRefed<Promise>
MediaKeys::RetrievePromise(PromiseId aId)
{
MOZ_ASSERT(mPromises.Contains(aId));
nsRefPtr<Promise> promise;
mPromises.Remove(aId, getter_AddRefs(promise));
return promise.forget();
@ -138,6 +157,11 @@ MediaKeys::RejectPromise(PromiseId aId, nsresult aExceptionCode)
MOZ_ASSERT(NS_FAILED(aExceptionCode));
promise->MaybeReject(aExceptionCode);
if (mCreatePromiseId == aId) {
// Note: This will probably destroy the MediaKeys object!
Release();
}
}
void
@ -148,10 +172,24 @@ MediaKeys::ResolvePromise(PromiseId aId)
NS_WARNING("MediaKeys tried to resolve a non-existent promise");
return;
}
// We should not resolve CreateSession or LoadSession calls via this path,
// OnSessionActivated() should be called instead.
MOZ_ASSERT(!mPendingSessions.Contains(aId));
promise->MaybeResolve(JS::UndefinedHandleValue);
if (mPendingSessions.Contains(aId)) {
// We should only resolve LoadSession calls via this path,
// not CreateSession() promises.
nsRefPtr<MediaKeySession> session;
if (!mPendingSessions.Get(aId, getter_AddRefs(session)) ||
!session ||
session->GetSessionId().IsEmpty()) {
NS_WARNING("Received activation for non-existent session!");
promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
mPendingSessions.Remove(aId);
return;
}
mPendingSessions.Remove(aId);
mKeySessions.Put(session->GetSessionId(), session);
promise->MaybeResolve(session);
} else {
promise->MaybeResolve(JS::UndefinedHandleValue);
}
}
/* static */
@ -174,13 +212,25 @@ MediaKeys::Create(const GlobalObject& aGlobal,
return nullptr;
}
if (!aKeySystem.EqualsASCII("org.w3.clearkey")) {
if (!IsSupportedKeySystem(aKeySystem)) {
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return nullptr;
}
keys->mProxy = new CDMProxy(keys, aKeySystem);
keys->mProxy->Init(keys->StorePromise(promise));
// The CDMProxy's initialization is asynchronous. The MediaKeys is
// refcounted, and its instance is returned to JS by promise once
// it's been initialized. No external refs exist to the MediaKeys while
// we're waiting for the promise to be resolved, so we must hold a
// reference to the new MediaKeys object until it's been created,
// or its creation has failed. Store the id of the promise returned
// here, and hold a self-reference until that promise is resolved or
// rejected.
MOZ_ASSERT(!keys->mCreatePromiseId, "Should only be created once!");
keys->mCreatePromiseId = keys->StorePromise(promise);
keys->AddRef();
keys->mProxy->Init(keys->mCreatePromiseId);
return promise.forget();
}
@ -195,6 +245,9 @@ MediaKeys::OnCDMCreated(PromiseId aId)
}
nsRefPtr<MediaKeys> keys(this);
promise->MaybeResolve(keys);
if (mCreatePromiseId == aId) {
Release();
}
}
already_AddRefed<Promise>
@ -224,9 +277,10 @@ MediaKeys::LoadSession(const nsAString& aSessionId, ErrorResult& aRv)
return nullptr;
}
// Proxy owns session object until resolving promise.
mProxy->LoadSession(StorePromise(promise),
aSessionId);
session->Init(aSessionId);
auto pid = StorePromise(promise);
mPendingSessions.Put(pid, session);
mProxy->LoadSession(pid, aSessionId);
return promise.forget();
}
@ -262,7 +316,7 @@ MediaKeys::CreateSession(const nsAString& initDataType,
}
void
MediaKeys::OnSessionActivated(PromiseId aId, const nsAString& aSessionId)
MediaKeys::OnSessionCreated(PromiseId aId, const nsAString& aSessionId)
{
nsRefPtr<Promise> promise(RetrievePromise(aId));
if (!promise) {
@ -272,16 +326,17 @@ MediaKeys::OnSessionActivated(PromiseId aId, const nsAString& aSessionId)
MOZ_ASSERT(mPendingSessions.Contains(aId));
nsRefPtr<MediaKeySession> session;
if (!mPendingSessions.Get(aId, getter_AddRefs(session)) || !session) {
bool gotSession = mPendingSessions.Get(aId, getter_AddRefs(session));
// Session has completed creation/loading, remove it from mPendingSessions,
// and resolve the promise with it. We store it in mKeySessions, so we can
// find it again if we need to send messages to it etc.
mPendingSessions.Remove(aId);
if (!gotSession || !session) {
NS_WARNING("Received activation for non-existent session!");
promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return;
}
// Session has completed creation/loading, remove it from mPendingSessions,
// and resolve the promise with it. We store it in mKeySessions, so we can
// find it again if we need to send messages to it etc.
mPendingSessions.Remove(aId);
session->Init(aSessionId);
mKeySessions.Put(aSessionId, session);
promise->MaybeResolve(session);
@ -303,5 +358,27 @@ MediaKeys::GetSession(const nsAString& aSessionId)
return session.forget();
}
nsresult
MediaKeys::GetOrigin(nsString& aOutOrigin)
{
MOZ_ASSERT(NS_IsMainThread());
// TODO: Bug 1035637, return a combination of origin and URL bar origin.
nsIPrincipal* principal = nullptr;
nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(GetParentObject());
nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
do_QueryInterface(pWindow);
if (scriptPrincipal) {
principal = scriptPrincipal->GetPrincipal();
}
NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
nsresult res = nsContentUtils::GetUTFOrigin(principal, aOutOrigin);
EME_LOG("EME Origin = '%s'", NS_ConvertUTF16toUTF8(aOutOrigin).get());
return res;
}
} // namespace dom
} // namespace mozilla

View File

@ -83,7 +83,7 @@ public:
// Called once a Create() operation succeeds.
void OnCDMCreated(PromiseId aId);
// Called once a CreateSession or LoadSession succeeds.
void OnSessionActivated(PromiseId aId, const nsAString& aSessionId);
void OnSessionCreated(PromiseId aId, const nsAString& aSessionId);
// Called once a session has closed.
void OnSessionClosed(MediaKeySession* aSession);
@ -101,6 +101,8 @@ public:
// Resolves promise with "undefined".
void ResolvePromise(PromiseId aId);
nsresult GetOrigin(nsString& aOutOrigin);
private:
// Removes promise from mPromises, and returns it.
@ -115,6 +117,7 @@ private:
KeySessionHashMap mKeySessions;
PromiseHashMap mPromises;
PendingKeySessionsHashMap mPendingSessions;
PromiseId mCreatePromiseId;
};
} // namespace dom

View File

@ -13,10 +13,15 @@ EXPORTS.mozilla.dom += [
]
EXPORTS.mozilla += [
'CDMCallbackProxy.h',
'CDMCaps.h',
'CDMProxy.h',
'EMELog.h'
]
UNIFIED_SOURCES += [
'CDMCallbackProxy.cpp',
'CDMCaps.cpp',
'CDMProxy.cpp',
'EMELog.cpp',
'MediaKeyError.cpp',

View File

@ -7,6 +7,7 @@
#define GMPDecryptorProxy_h_
#include "GMPCallbackBase.h"
#include "gmp-decryption.h"
namespace mp4_demuxer {
class CryptoSample;