summaryrefslogtreecommitdiff
path: root/dom
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2022-04-20 12:37:57 -0500
committerMatt A. Tobin <email@mattatobin.com>2022-04-20 12:37:57 -0500
commit7c598437c237a3a6da4f74f611bc2f043c32abc7 (patch)
treed6352487c1d32b06928f43344d41ba1ca1b0fac7 /dom
parent87dabd4e0b724bb81db3eaeefa09cfd2c5545a36 (diff)
downloadaura-central-7c598437c237a3a6da4f74f611bc2f043c32abc7.tar.gz
Issue #1 - Restore EME (but not the configure logic to enable it)
Diffstat (limited to 'dom')
-rw-r--r--dom/base/Navigator.cpp141
-rw-r--r--dom/base/Navigator.h12
-rw-r--r--dom/base/nsDocument.cpp34
-rw-r--r--dom/base/nsDocument.h4
-rw-r--r--dom/canvas/CanvasRenderingContext2D.cpp7
-rw-r--r--dom/html/HTMLMediaElement.cpp322
-rw-r--r--dom/html/HTMLMediaElement.h37
-rw-r--r--dom/media/AbstractMediaDecoder.h4
-rw-r--r--dom/media/MediaDecoder.cpp24
-rw-r--r--dom/media/MediaDecoder.h20
-rw-r--r--dom/media/MediaDecoderOwner.h8
-rw-r--r--dom/media/MediaDecoderReader.h7
-rw-r--r--dom/media/MediaDecoderReaderWrapper.h4
-rw-r--r--dom/media/MediaDecoderStateMachine.cpp62
-rw-r--r--dom/media/MediaDecoderStateMachine.h8
-rw-r--r--dom/media/MediaFormatReader.cpp62
-rw-r--r--dom/media/MediaFormatReader.h13
-rw-r--r--dom/media/eme/CDMCaps.cpp169
-rw-r--r--dom/media/eme/CDMCaps.h112
-rw-r--r--dom/media/eme/CDMProxy.h278
-rw-r--r--dom/media/eme/DecryptorProxyCallback.h54
-rw-r--r--dom/media/eme/DetailedPromise.cpp57
-rw-r--r--dom/media/eme/DetailedPromise.h57
-rw-r--r--dom/media/eme/EMEUtils.cpp86
-rw-r--r--dom/media/eme/EMEUtils.h104
-rw-r--r--dom/media/eme/MediaEncryptedEvent.cpp128
-rw-r--r--dom/media/eme/MediaEncryptedEvent.h66
-rw-r--r--dom/media/eme/MediaKeyError.cpp39
-rw-r--r--dom/media/eme/MediaKeyError.h38
-rw-r--r--dom/media/eme/MediaKeyMessageEvent.cpp122
-rw-r--r--dom/media/eme/MediaKeyMessageEvent.h66
-rw-r--r--dom/media/eme/MediaKeySession.cpp670
-rw-r--r--dom/media/eme/MediaKeySession.h136
-rw-r--r--dom/media/eme/MediaKeyStatusMap.cpp120
-rw-r--r--dom/media/eme/MediaKeyStatusMap.h96
-rw-r--r--dom/media/eme/MediaKeySystemAccess.cpp1042
-rw-r--r--dom/media/eme/MediaKeySystemAccess.h81
-rw-r--r--dom/media/eme/MediaKeySystemAccessManager.cpp336
-rw-r--r--dom/media/eme/MediaKeySystemAccessManager.h83
-rw-r--r--dom/media/eme/MediaKeys.cpp579
-rw-r--r--dom/media/eme/MediaKeys.h167
-rw-r--r--dom/media/eme/moz.build41
-rw-r--r--dom/media/fmp4/MP4Decoder.cpp4
-rw-r--r--dom/media/gmp/GMPCDMCallbackProxy.cpp250
-rw-r--r--dom/media/gmp/GMPCDMCallbackProxy.h71
-rw-r--r--dom/media/gmp/GMPCDMProxy.cpp798
-rw-r--r--dom/media/gmp/GMPCDMProxy.h265
-rw-r--r--dom/media/gmp/GMPChild.cpp9
-rw-r--r--dom/media/gmp/GMPDecryptorParent.cpp175
-rw-r--r--dom/media/gmp/GMPDecryptorProxy.h8
-rw-r--r--dom/media/gmp/GMPParent.cpp57
-rw-r--r--dom/media/gmp/moz.build15
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineAdapter.cpp168
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineAdapter.h59
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp554
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineDecryptor.h132
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineFileIO.cpp97
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineFileIO.h46
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineUtils.cpp95
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineUtils.h73
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp400
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h80
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp126
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineVideoFrame.h50
-rw-r--r--dom/media/gmp/widevine-adapter/content_decryption_module.h1278
-rw-r--r--dom/media/gmp/widevine-adapter/content_decryption_module_export.h22
-rw-r--r--dom/media/gmp/widevine-adapter/content_decryption_module_ext.h64
-rw-r--r--dom/media/gmp/widevine-adapter/moz.build24
-rw-r--r--dom/media/mediasource/TrackBuffersManager.cpp36
-rw-r--r--dom/media/moz.build3
-rw-r--r--dom/media/platforms/PDMFactory.cpp15
-rw-r--r--dom/media/platforms/PDMFactory.h14
-rw-r--r--dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp44
-rw-r--r--dom/media/platforms/agnostic/eme/EMEAudioDecoder.h37
-rw-r--r--dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp307
-rw-r--r--dom/media/platforms/agnostic/eme/EMEDecoderModule.h52
-rw-r--r--dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp68
-rw-r--r--dom/media/platforms/agnostic/eme/EMEVideoDecoder.h45
-rw-r--r--dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp85
-rw-r--r--dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h58
-rw-r--r--dom/media/platforms/agnostic/eme/moz.build22
-rw-r--r--dom/media/platforms/moz.build3
-rwxr-xr-xdom/media/webaudio/AudioContext.cpp8
-rw-r--r--dom/webidl/HTMLMediaElement.webidl18
-rw-r--r--dom/webidl/MediaEncryptedEvent.webidl23
-rw-r--r--dom/webidl/MediaKeyError.webidl19
-rw-r--r--dom/webidl/MediaKeyMessageEvent.webidl30
-rw-r--r--dom/webidl/MediaKeySession.webidl47
-rw-r--r--dom/webidl/MediaKeyStatusMap.webidl30
-rw-r--r--dom/webidl/MediaKeySystemAccess.webidl41
-rw-r--r--dom/webidl/MediaKeys.webidl30
-rw-r--r--dom/webidl/MediaKeysRequestStatus.webidl23
-rw-r--r--dom/webidl/WidevineCDMManifest.webidl15
-rw-r--r--dom/webidl/moz.build13
94 files changed, 11487 insertions, 15 deletions
diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp
index e72e95a61..11fe91b9a 100644
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -88,6 +88,11 @@
#endif
#include "mozilla/dom/ContentChild.h"
+#ifdef MOZ_EME
+#include "mozilla/EMEUtils.h"
+#include "mozilla/DetailedPromise.h"
+#endif
+
namespace mozilla {
namespace dom {
@@ -190,6 +195,9 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Navigator)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerContainer)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+#ifdef MOZ_EME
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeySystemAccessManager)
+#endif
#ifdef MOZ_GAMEPAD
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepadServiceTest)
#endif
@@ -250,6 +258,13 @@ Navigator::Invalidate()
mServiceWorkerContainer = nullptr;
+#ifdef MOZ_EME
+ if (mMediaKeySystemAccessManager) {
+ mMediaKeySystemAccessManager->Shutdown();
+ mMediaKeySystemAccessManager = nullptr;
+ }
+#endif
+
#ifdef MOZ_GAMEPAD
if (mGamepadServiceTest) {
mGamepadServiceTest->Shutdown();
@@ -1483,5 +1498,131 @@ Navigator::GetUserAgent(nsPIDOMWindowInner* aWindow, nsIURI* aURI,
return siteSpecificUA->GetUserAgentForURIAndWindow(aURI, aWindow, aUserAgent);
}
+#ifdef MOZ_EME
+static nsCString
+ToCString(const nsString& aString)
+{
+ nsCString str("'");
+ str.Append(NS_ConvertUTF16toUTF8(aString));
+ str.AppendLiteral("'");
+ return str;
+}
+
+static nsCString
+ToCString(const MediaKeysRequirement aValue)
+{
+ nsCString str("'");
+ str.Append(nsDependentCString(MediaKeysRequirementValues::strings[static_cast<uint32_t>(aValue)].value));
+ str.AppendLiteral("'");
+ return str;
+}
+
+static nsCString
+ToCString(const MediaKeySystemMediaCapability& aValue)
+{
+ nsCString str;
+ str.AppendLiteral("{contentType=");
+ str.Append(ToCString(aValue.mContentType));
+ str.AppendLiteral(", robustness=");
+ str.Append(ToCString(aValue.mRobustness));
+ str.AppendLiteral("}");
+ return str;
+}
+
+template<class Type>
+static nsCString
+ToCString(const Sequence<Type>& aSequence)
+{
+ nsCString str;
+ str.AppendLiteral("[");
+ for (size_t i = 0; i < aSequence.Length(); i++) {
+ if (i != 0) {
+ str.AppendLiteral(",");
+ }
+ str.Append(ToCString(aSequence[i]));
+ }
+ str.AppendLiteral("]");
+ return str;
+}
+
+template<class Type>
+static nsCString
+ToCString(const Optional<Sequence<Type>>& aOptional)
+{
+ nsCString str;
+ if (aOptional.WasPassed()) {
+ str.Append(ToCString(aOptional.Value()));
+ } else {
+ str.AppendLiteral("[]");
+ }
+ return str;
+}
+
+static nsCString
+ToCString(const MediaKeySystemConfiguration& aConfig)
+{
+ nsCString str;
+ str.AppendLiteral("{label=");
+ str.Append(ToCString(aConfig.mLabel));
+
+ str.AppendLiteral(", initDataTypes=");
+ str.Append(ToCString(aConfig.mInitDataTypes));
+
+ str.AppendLiteral(", audioCapabilities=");
+ str.Append(ToCString(aConfig.mAudioCapabilities));
+
+ str.AppendLiteral(", videoCapabilities=");
+ str.Append(ToCString(aConfig.mVideoCapabilities));
+
+ str.AppendLiteral(", distinctiveIdentifier=");
+ str.Append(ToCString(aConfig.mDistinctiveIdentifier));
+
+ str.AppendLiteral(", persistentState=");
+ str.Append(ToCString(aConfig.mPersistentState));
+
+ str.AppendLiteral(", sessionTypes=");
+ str.Append(ToCString(aConfig.mSessionTypes));
+
+ str.AppendLiteral("}");
+
+ return str;
+}
+
+static nsCString
+RequestKeySystemAccessLogString(const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs)
+{
+ nsCString str;
+ str.AppendPrintf("Navigator::RequestMediaKeySystemAccess(keySystem='%s' options=",
+ NS_ConvertUTF16toUTF8(aKeySystem).get());
+ str.Append(ToCString(aConfigs));
+ str.AppendLiteral(")");
+ return str;
+}
+
+already_AddRefed<Promise>
+Navigator::RequestMediaKeySystemAccess(const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ ErrorResult& aRv)
+{
+ EME_LOG("%s", RequestKeySystemAccessLogString(aKeySystem, aConfigs).get());
+
+ nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
+ RefPtr<DetailedPromise> promise =
+ DetailedPromise::Create(go, aRv,
+ NS_LITERAL_CSTRING("navigator.requestMediaKeySystemAccess"));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (!mMediaKeySystemAccessManager) {
+ mMediaKeySystemAccessManager = new MediaKeySystemAccessManager(mWindow);
+ }
+
+ mMediaKeySystemAccessManager->Request(promise, aKeySystem, aConfigs);
+ return promise.forget();
+}
+#endif
+
} // namespace dom
} // namespace mozilla
diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h
index a1c4794c4..2915b5069 100644
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -17,6 +17,9 @@
#include "nsString.h"
#include "nsTArray.h"
#include "nsWeakPtr.h"
+#ifdef MOZ_EME
+#include "mozilla/dom/MediaKeySystemAccessManager.h"
+#endif
class nsPluginArray;
class nsMimeTypeArray;
@@ -235,6 +238,15 @@ public:
// any, else null.
static already_AddRefed<nsPIDOMWindowInner> GetWindowFromGlobal(JSObject* aGlobal);
+#ifdef MOZ_EME
+ already_AddRefed<Promise>
+ RequestMediaKeySystemAccess(const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfig,
+ ErrorResult& aRv);
+private:
+ RefPtr<MediaKeySystemAccessManager> mMediaKeySystemAccessManager;
+#endif
+
private:
virtual ~Navigator();
diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp
index 08c78c88d..52113f4d8 100644
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -4289,6 +4289,32 @@ nsDocument::SetScopeObject(nsIGlobalObject* aGlobal)
}
}
+#ifdef MOZ_EME
+static void
+CheckIfContainsEMEContent(nsISupports* aSupports, void* aContainsEME)
+{
+ nsCOMPtr<nsIDOMHTMLMediaElement> domMediaElem(do_QueryInterface(aSupports));
+ if (domMediaElem) {
+ nsCOMPtr<nsIContent> content(do_QueryInterface(domMediaElem));
+ MOZ_ASSERT(content, "aSupports is not a content");
+ HTMLMediaElement* mediaElem = static_cast<HTMLMediaElement*>(content.get());
+ bool* contains = static_cast<bool*>(aContainsEME);
+ if (mediaElem->GetMediaKeys()) {
+ *contains = true;
+ }
+ }
+}
+
+bool
+nsDocument::ContainsEMEContent()
+{
+ bool containsEME = false;
+ EnumerateActivityObservers(CheckIfContainsEMEContent,
+ static_cast<void*>(&containsEME));
+ return containsEME;
+}
+#endif // MOZ_EME
+
static void
CheckIfContainsMSEContent(nsISupports* aSupports, void* aContainsMSE)
{
@@ -8065,6 +8091,14 @@ nsDocument::CanSavePresentation(nsIRequest *aNewRequest)
return false;
}
+#ifdef MOZ_EME
+ // Don't save presentations for documents containing EME content, so that
+ // CDMs reliably shutdown upon user navigation.
+ if (ContainsEMEContent()) {
+ return false;
+ }
+#endif
+
// Don't save presentations for documents containing MSE content, to
// reduce memory usage.
if (ContainsMSEContent()) {
diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h
index 024aaaa7e..010f95ae2 100644
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -1115,6 +1115,10 @@ public:
js::ExpandoAndGeneration mExpandoAndGeneration;
+#ifdef MOZ_EME
+ bool ContainsEMEContent();
+#endif
+
bool ContainsMSEContent();
protected:
diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp
index 7d00b2cb2..3965d40f8 100644
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -5008,6 +5008,13 @@ CanvasRenderingContext2D::DrawImage(const CanvasImageSource& aImage,
return;
}
+#ifdef MOZ_EME
+ if (video->ContainsRestrictedContent()) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+#endif
+
uint16_t readyState;
if (NS_SUCCEEDED(video->GetReadyState(&readyState)) &&
readyState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA) {
diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp
index 9b3b725c6..6774504a4 100644
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -12,6 +12,9 @@
#include "mozilla/ArrayUtils.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/AsyncEventDispatcher.h"
+#ifdef MOZ_EME
+#include "mozilla/dom/MediaEncryptedEvent.h"
+#endif
#include "base/basictypes.h"
#include "nsIDOMHTMLMediaElement.h"
@@ -874,6 +877,9 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTM
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList)
+#ifdef MOZ_EME
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
+#endif
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedVideoStreamTrack)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPlayPromises)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
@@ -899,6 +905,9 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLE
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList)
+#ifdef MOZ_EME
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
+#endif
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPlayPromises)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@@ -1151,6 +1160,9 @@ void HTMLMediaElement::AbortExistingLoads()
mDownloadSuspendedByCache = false;
mMediaInfo = MediaInfo();
mIsEncrypted = false;
+#ifdef MOZ_EME
+ mPendingEncryptedInitData.mInitDatas.Clear();
+#endif
mWaitingForKey = NOT_WAITING_FOR_KEY;
mSourcePointer = nullptr;
@@ -1839,6 +1851,20 @@ nsresult HTMLMediaElement::LoadResource()
// Set the media element's CORS mode only when loading a resource
mCORSMode = AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin));
+#ifdef MOZ_EME
+ bool isBlob = false;
+ if (mMediaKeys &&
+ Preferences::GetBool("media.eme.mse-only", true) &&
+ // We only want mediaSource URLs, but they are BlobURL, so we have to
+ // check the schema and abort if they are not MediaStream or real Blob.
+ (NS_FAILED(mLoadingSrc->SchemeIs(BLOBURI_SCHEME, &isBlob)) ||
+ !isBlob ||
+ IsMediaStreamURI(mLoadingSrc) ||
+ IsBlobURI(mLoadingSrc))) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+#endif
+
HTMLMediaElement* other = LookupMediaElementURITable(mLoadingSrc);
if (other && other->mDecoder) {
// Clone it.
@@ -2754,6 +2780,11 @@ HTMLMediaElement::CaptureStreamInternal(bool aFinishWhenEnded,
if (!window) {
return nullptr;
}
+#ifdef MOZ_EME
+ if (ContainsRestrictedContent()) {
+ return nullptr;
+ }
+#endif
if (!mOutputStreams.IsEmpty() &&
aGraph != mOutputStreams[0].mStream->GetInputStream()->Graph()) {
@@ -3950,6 +3981,25 @@ nsresult HTMLMediaElement::FinishDecoderSetup(MediaDecoder* aDecoder,
ms.mFinishWhenEnded);
}
+#ifdef MOZ_EME
+ if (mMediaKeys) {
+ if (mMediaKeys->GetCDMProxy()) {
+ mDecoder->SetCDMProxy(mMediaKeys->GetCDMProxy());
+ } else {
+ // CDM must have crashed.
+ ShutdownDecoder();
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ MediaEventSource<void>* waitingForKeyProducer = mDecoder->WaitingForKeyEvent();
+ // Not every decoder will produce waitingForKey events, only add ones that can
+ if (waitingForKeyProducer) {
+ mWaitingForKeyListener = waitingForKeyProducer->Connect(
+ AbstractThread::MainThread(), this, &HTMLMediaElement::CannotDecryptWaitingForKey);
+ }
+#endif
+
if (mChannelLoader) {
mChannelLoader->Done();
mChannelLoader = nullptr;
@@ -4404,7 +4454,11 @@ void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
SetMediaInfo(*aInfo);
- mIsEncrypted = aInfo->IsEncrypted();
+ mIsEncrypted = aInfo->IsEncrypted()
+#ifdef MOZ_EME
+ || mPendingEncryptedInitData.IsEncrypted()
+#endif
+ ;
mTags = aTags.forget();
mLoadedDataFired = false;
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
@@ -4429,6 +4483,14 @@ void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
"Encrypted content not supported outside of MSE"));
return;
}
+
+#ifdef MOZ_EME
+ // Dispatch a distinct 'encrypted' event for each initData we have.
+ for (const auto& initData : mPendingEncryptedInitData.mInitDatas) {
+ DispatchEncrypted(initData.mInitData, initData.mType);
+ }
+ mPendingEncryptedInitData.mInitDatas.Clear();
+#endif
}
mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
@@ -5362,12 +5424,29 @@ void HTMLMediaElement::SuspendOrResumeElement(bool aPauseElement, bool aSuspendE
UpdateSrcMediaStreamPlaying();
UpdateAudioChannelPlayingState();
if (aPauseElement) {
+#ifdef MOZ_EME
+ // For EME content, we may force destruction of the CDM client (and CDM
+ // instance if this is the last client for that CDM instance) and
+ // the CDM's decoder. This ensures the CDM gets reliable and prompt
+ // shutdown notifications, as it may have book-keeping it needs
+ // to do on shutdown.
+ if (mMediaKeys) {
+ mMediaKeys->Shutdown();
+ mMediaKeys = nullptr;
+ if (mDecoder) {
+ ShutdownDecoder();
+ }
+ }
+#endif
if (mDecoder) {
mDecoder->Pause();
mDecoder->Suspend();
}
mEventDeliveryPaused = aSuspendEvents;
} else {
+#ifdef MOZ_EME
+ MOZ_ASSERT(!mMediaKeys);
+#endif
if (mDecoder) {
mDecoder->Resume();
if (!mPaused && !mDecoder->IsEnded()) {
@@ -5404,6 +5483,17 @@ void HTMLMediaElement::NotifyOwnerDocumentActivityChanged()
bool pauseElement = ShouldElementBePaused();
SuspendOrResumeElement(pauseElement, !IsActive());
+#ifdef MOZ_EME
+ // If the owning document has become inactive we should shutdown the CDM.
+ if (!OwnerDoc()->IsCurrentActiveDocument() && mMediaKeys) {
+ mMediaKeys->Shutdown();
+ mMediaKeys = nullptr;
+ if (mDecoder) {
+ ShutdownDecoder();
+ }
+ }
+#endif
+
AddRemoveSelfReference();
}
@@ -6164,6 +6254,236 @@ HTMLMediaElement::OnVisibilityChange(Visibility aNewVisibility)
}
+#ifdef MOZ_EME
+MediaKeys*
+HTMLMediaElement::GetMediaKeys() const
+{
+ return mMediaKeys;
+}
+
+bool
+HTMLMediaElement::ContainsRestrictedContent()
+{
+ return GetMediaKeys() != nullptr;
+}
+
+already_AddRefed<Promise>
+HTMLMediaElement::SetMediaKeys(mozilla::dom::MediaKeys* aMediaKeys,
+ ErrorResult& aRv)
+{
+ LOG(LogLevel::Debug, ("%p SetMediaKeys(%p) mMediaKeys=%p mDecoder=%p",
+ this, aMediaKeys, mMediaKeys.get(), mDecoder.get()));
+
+ if (MozAudioCaptured()) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(OwnerDoc()->GetInnerWindow());
+ if (!global) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ RefPtr<DetailedPromise> promise = DetailedPromise::Create(global, aRv,
+ NS_LITERAL_CSTRING("HTMLMediaElement.setMediaKeys"));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // 1. If mediaKeys and the mediaKeys attribute are the same object,
+ // return a resolved promise.
+ if (mMediaKeys == aMediaKeys) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+
+ // Note: Our attaching code is synchronous, so we can skip the following steps.
+
+ // 2. If this object's attaching media keys value is true, return a
+ // promise rejected with a new DOMException whose name is InvalidStateError.
+ // 3. Let this object's attaching media keys value be true.
+ // 4. Let promise be a new promise.
+ // 5. Run the following steps in parallel:
+
+ // 5.1 If mediaKeys is not null, CDM instance represented by mediaKeys is
+ // already in use by another media element, and the user agent is unable
+ // to use it with this element, let this object's attaching media keys
+ // value be false and reject promise with a new DOMException whose name
+ // is QuotaExceededError.
+ if (aMediaKeys && aMediaKeys->IsBoundToMediaElement()) {
+ promise->MaybeReject(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR,
+ NS_LITERAL_CSTRING("MediaKeys object is already bound to another HTMLMediaElement"));
+ return promise.forget();
+ }
+
+ // 5.2 If the mediaKeys attribute is not null, run the following steps:
+ if (mMediaKeys) {
+ // 5.2.1 If the user agent or CDM do not support removing the association,
+ // let this object's attaching media keys value be false and reject promise
+ // with a new DOMException whose name is NotSupportedError.
+
+ // 5.2.2 If the association cannot currently be removed, let this object's
+ // attaching media keys value be false and reject promise with a new
+ // DOMException whose name is InvalidStateError.
+ if (mDecoder) {
+ // We don't support swapping out the MediaKeys once we've started to
+ // setup the playback pipeline. Note this also means we don't need to worry
+ // about handling disassociating the MediaKeys from the MediaDecoder.
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Can't change MediaKeys on HTMLMediaElement after load has started"));
+ return promise.forget();
+ }
+
+ // 5.2.3 Stop using the CDM instance represented by the mediaKeys attribute
+ // to decrypt media data and remove the association with the media element.
+ mMediaKeys->Unbind();
+ mMediaKeys = nullptr;
+
+ // 5.2.4 If the preceding step failed, let this object's attaching media
+ // keys value be false and reject promise with a new DOMException whose
+ // name is the appropriate error name.
+ }
+
+ // 5.3. If mediaKeys is not null, run the following steps:
+ if (aMediaKeys) {
+ if (!aMediaKeys->GetCDMProxy()) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("CDM crashed before binding MediaKeys object to HTMLMediaElement"));
+ return promise.forget();
+ }
+
+ // 5.3.1 Associate the CDM instance represented by mediaKeys with the
+ // media element for decrypting media data.
+ if (NS_FAILED(aMediaKeys->Bind(this))) {
+ // 5.3.2 If the preceding step failed, run the following steps:
+ // 5.3.2.1 Set the mediaKeys attribute to null.
+ mMediaKeys = nullptr;
+ // 5.3.2.2 Let this object's attaching media keys value be false.
+ // 5.3.2.3 Reject promise with a new DOMException whose name is
+ // the appropriate error name.
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Failed to bind MediaKeys object to HTMLMediaElement"));
+ return promise.forget();
+ }
+ // 5.3.3 Queue a task to run the "Attempt to Resume Playback If Necessary"
+ // algorithm on the media element.
+ // Note: Setting the CDMProxy on the MediaDecoder will unblock playback.
+ if (mDecoder) {
+ mDecoder->SetCDMProxy(aMediaKeys->GetCDMProxy());
+ }
+ }
+
+ // 5.4 Set the mediaKeys attribute to mediaKeys.
+ mMediaKeys = aMediaKeys;
+
+ // 5.5 Let this object's attaching media keys value be false.
+
+ // 5.6 Resolve promise.
+ promise->MaybeResolveWithUndefined();
+
+ // 6. Return promise.
+ return promise.forget();
+}
+
+EventHandlerNonNull*
+HTMLMediaElement::GetOnencrypted()
+{
+ return EventTarget::GetEventHandler(nsGkAtoms::onencrypted, EmptyString());
+}
+
+void
+HTMLMediaElement::SetOnencrypted(EventHandlerNonNull* aCallback)
+{
+ EventTarget::SetEventHandler(nsGkAtoms::onencrypted, EmptyString(), aCallback);
+}
+
+EventHandlerNonNull*
+HTMLMediaElement::GetOnwaitingforkey()
+{
+ return EventTarget::GetEventHandler(nsGkAtoms::onwaitingforkey, EmptyString());
+}
+
+void
+HTMLMediaElement::SetOnwaitingforkey(EventHandlerNonNull* aCallback)
+{
+ EventTarget::SetEventHandler(nsGkAtoms::onwaitingforkey, EmptyString(), aCallback);
+}
+
+void
+HTMLMediaElement::DispatchEncrypted(const nsTArray<uint8_t>& aInitData,
+ const nsAString& aInitDataType)
+{
+ LOG(LogLevel::Debug,
+ ("%p DispatchEncrypted initDataType='%s'",
+ this, NS_ConvertUTF16toUTF8(aInitDataType).get()));
+
+ if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
+ // Ready state not HAVE_METADATA (yet), don't dispatch encrypted now.
+ // Queueing for later dispatch in MetadataLoaded.
+ mPendingEncryptedInitData.AddInitData(aInitDataType, aInitData);
+ return;
+ }
+
+ RefPtr<MediaEncryptedEvent> event;
+ if (IsCORSSameOrigin()) {
+ event = MediaEncryptedEvent::Constructor(this, aInitDataType, aInitData);
+ } else {
+ event = MediaEncryptedEvent::Constructor(this);
+ }
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, event);
+ asyncDispatcher->PostDOMEvent();
+}
+
+bool
+HTMLMediaElement::IsEventAttributeName(nsIAtom* aName)
+{
+ return aName == nsGkAtoms::onencrypted ||
+ nsGenericHTMLElement::IsEventAttributeName(aName);
+}
+
+already_AddRefed<nsIPrincipal>
+HTMLMediaElement::GetTopLevelPrincipal()
+{
+ RefPtr<nsIPrincipal> principal;
+ nsCOMPtr<nsPIDOMWindowInner> window = OwnerDoc()->GetInnerWindow();
+ if (!window) {
+ return nullptr;
+ }
+ // XXXkhuey better hope we always have an outer ...
+ nsCOMPtr<nsPIDOMWindowOuter> top = window->GetOuterWindow()->GetTop();
+ if (!top) {
+ return nullptr;
+ }
+ nsIDocument* doc = top->GetExtantDoc();
+ if (!doc) {
+ return nullptr;
+ }
+ principal = doc->NodePrincipal();
+ return principal.forget();
+}
+
+void
+HTMLMediaElement::CannotDecryptWaitingForKey()
+{
+ LOG(LogLevel::Debug, ("%p, CannotDecryptWaitingForKey()", this));
+
+ // http://w3c.github.io/encrypted-media/#wait-for-key
+ // 7.3.4 Queue a "waitingforkey" Event
+ // 1. Let the media element be the specified HTMLMediaElement object.
+ // 2. If the media element's waiting for key value is true, abort these steps.
+ if (mWaitingForKey == NOT_WAITING_FOR_KEY) {
+ // 3. Set the media element's waiting for key value to true.
+ // Note: algorithm continues in UpdateReadyStateInternal() when all decoded
+ // data enqueued in the MDSM is consumed.
+ mWaitingForKey = WAITING_FOR_KEY;
+ UpdateReadyStateInternal();
+ }
+}
+#endif //MOZ_EME
+
NS_IMETHODIMP HTMLMediaElement::WindowAudioCaptureChanged(bool aCapture)
{
MOZ_ASSERT(mAudioChannelAgent);
diff --git a/dom/html/HTMLMediaElement.h b/dom/html/HTMLMediaElement.h
index 23799b574..bda9924a6 100644
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -18,6 +18,9 @@
#include "mozilla/dom/TextTrackManager.h"
#include "mozilla/WeakPtr.h"
#include "MediaDecoder.h"
+#ifdef MOZ_EME
+#include "mozilla/dom/MediaKeys.h"
+#endif
#include "mozilla/StateWatching.h"
#include "nsGkAtoms.h"
#include "PrincipalChangeObserver.h"
@@ -613,6 +616,30 @@ public:
// XPCOM MozPreservesPitch() is OK
+#ifdef MOZ_EME
+ MediaKeys* GetMediaKeys() const;
+
+ already_AddRefed<Promise> SetMediaKeys(MediaKeys* mediaKeys,
+ ErrorResult& aRv);
+
+ mozilla::dom::EventHandlerNonNull* GetOnencrypted();
+ void SetOnencrypted(mozilla::dom::EventHandlerNonNull* aCallback);
+
+ mozilla::dom::EventHandlerNonNull* GetOnwaitingforkey();
+ void SetOnwaitingforkey(mozilla::dom::EventHandlerNonNull* aCallback);
+
+ void DispatchEncrypted(const nsTArray<uint8_t>& aInitData,
+ const nsAString& aInitDataType) override;
+
+ bool IsEventAttributeName(nsIAtom* aName) override;
+
+ // Returns the principal of the "top level" document; the origin displayed
+ // in the URL bar of the browser window.
+ already_AddRefed<nsIPrincipal> GetTopLevelPrincipal();
+
+ bool ContainsRestrictedContent();
+#endif // MOZ_EME
+
void CannotDecryptWaitingForKey();
bool MozAutoplayEnabled() const
@@ -1447,6 +1474,11 @@ protected:
// Timer used for updating progress events.
nsCOMPtr<nsITimer> mProgressTimer;
+#ifdef MOZ_EME
+ // Encrypted Media Extension media keys.
+ RefPtr<MediaKeys> mMediaKeys;
+#endif
+
// Stores the time at the start of the current 'played' range.
double mCurrentPlayRangeStart;
@@ -1600,6 +1632,11 @@ protected:
// Listens for waitingForKey events from the owned decoder.
MediaEventListener mWaitingForKeyListener;
+#ifdef MOZ_EME
+ // Init Data that needs to be sent in 'encrypted' events in MetadataLoaded().
+ EncryptionInfo mPendingEncryptedInitData;
+#endif
+
// True if the media's channel's download has been suspended.
Watchable<bool> mDownloadSuspendedByCache;
diff --git a/dom/media/AbstractMediaDecoder.h b/dom/media/AbstractMediaDecoder.h
index 01722f8b1..6babcce17 100644
--- a/dom/media/AbstractMediaDecoder.h
+++ b/dom/media/AbstractMediaDecoder.h
@@ -1,4 +1,5 @@
/* -*- 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/. */
@@ -30,6 +31,9 @@ class MediaResource;
class ReentrantMonitor;
class VideoFrameContainer;
class MediaDecoderOwner;
+#ifdef MOZ_EME
+class CDMProxy;
+#endif
typedef nsDataHashtable<nsCStringHashKey, nsCString> MetadataTags;
diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp
index ebc694b47..10b97f7e6 100644
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -386,6 +386,9 @@ MediaDecoder::MediaDecoder(MediaDecoderOwner* aOwner)
, mLogicalPosition(0.0)
, mDuration(std::numeric_limits<double>::quiet_NaN())
, mResourceCallback(new ResourceCallback())
+#ifdef MOZ_EME
+ , mCDMProxyPromise(mCDMProxyPromiseHolder.Ensure(__func__))
+#endif
, mIgnoreProgressData(false)
, mInfiniteStream(false)
, mOwner(aOwner)
@@ -470,6 +473,10 @@ MediaDecoder::Shutdown()
mResourceCallback->Disconnect();
+#ifdef MOZ_EME
+ mCDMProxyPromiseHolder.RejectIfExists(true, __func__);
+#endif
+
DiscardOngoingSeekIfExists();
// This changes the decoder state to SHUTDOWN and does other things
@@ -1540,6 +1547,23 @@ MediaDecoder::CanPlayThrough()
return GetStatistics().CanPlayThrough();
}
+#ifdef MOZ_EME
+RefPtr<MediaDecoder::CDMProxyPromise>
+MediaDecoder::RequestCDMProxy() const
+{
+ return mCDMProxyPromise;
+}
+
+void
+MediaDecoder::SetCDMProxy(CDMProxy* aProxy)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aProxy);
+
+ mCDMProxyPromiseHolder.ResolveIfExists(aProxy, __func__);
+}
+#endif
+
bool
MediaDecoder::IsOpusEnabled()
{
diff --git a/dom/media/MediaDecoder.h b/dom/media/MediaDecoder.h
index 5bc9e5000..298552433 100644
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -1,4 +1,5 @@
/* -*- 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/. */
@@ -8,6 +9,10 @@
#include "mozilla/Atomics.h"
+#ifdef MOZ_EME
+#include "mozilla/CDMProxy.h"
+#endif
+
#include "mozilla/MozPromise.h"
#include "mozilla/ReentrantMonitor.h"
#include "mozilla/StateMirroring.h"
@@ -431,6 +436,16 @@ private:
MediaDecoderOwner* GetOwner() const override;
+#ifdef MOZ_EME
+ typedef MozPromise<RefPtr<CDMProxy>, bool /* aIgnored */, /* IsExclusive = */ true> CDMProxyPromise;
+
+ // Resolved when a CDMProxy is available and the capabilities are known or
+ // rejected when this decoder is about to shut down.
+ RefPtr<CDMProxyPromise> RequestCDMProxy() const;
+
+ void SetCDMProxy(CDMProxy* aProxy);
+#endif
+
static bool IsOggEnabled();
static bool IsOpusEnabled();
static bool IsWaveEnabled();
@@ -580,6 +595,11 @@ private:
RefPtr<ResourceCallback> mResourceCallback;
+#ifdef MOZ_EME
+ MozPromiseHolder<CDMProxyPromise> mCDMProxyPromiseHolder;
+ RefPtr<CDMProxyPromise> mCDMProxyPromise;
+#endif
+
protected:
// The promise resolving/rejection is queued as a "micro-task" which will be
// handled immediately after the current JS task and before any pending JS
diff --git a/dom/media/MediaDecoderOwner.h b/dom/media/MediaDecoderOwner.h
index 649d2996f..f993b4324 100644
--- a/dom/media/MediaDecoderOwner.h
+++ b/dom/media/MediaDecoderOwner.h
@@ -142,6 +142,14 @@ public:
// The decoder owner should call Shutdown() on the decoder and drop the
// reference to the decoder to prevent further calls into the decoder.
virtual void NotifyXPCOMShutdown() = 0;
+
+#ifdef MOZ_EME
+ // Dispatches a "encrypted" event to the HTMLMediaElement, with the
+ // provided init data. Actual dispatch may be delayed until HAVE_METADATA.
+ // Main thread only.
+ virtual void DispatchEncrypted(const nsTArray<uint8_t>& aInitData,
+ const nsAString& aInitDataType) = 0;
+#endif
};
} // namespace mozilla
diff --git a/dom/media/MediaDecoderReader.h b/dom/media/MediaDecoderReader.h
index 3a3e2ceb8..dd406ed90 100644
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -24,6 +24,9 @@
namespace mozilla {
+#ifdef MOZ_EME
+class CDMProxy;
+#endif
class MediaDecoderReader;
struct WaitForDataRejectValue
@@ -185,6 +188,10 @@ public:
// when to call SetIdle().
virtual void SetIdle() {}
+#ifdef MOZ_EME
+ virtual void SetCDMProxy(CDMProxy* aProxy) {}
+#endif
+
// Tell the reader that the data decoded are not for direct playback, so it
// can accept more files, in particular those which have more channels than
// available in the audio output.
diff --git a/dom/media/MediaDecoderReaderWrapper.h b/dom/media/MediaDecoderReaderWrapper.h
index c85c93d44..a0f845538 100644
--- a/dom/media/MediaDecoderReaderWrapper.h
+++ b/dom/media/MediaDecoderReaderWrapper.h
@@ -112,6 +112,10 @@ public:
return mReader->CanonicalBuffered();
}
+#ifdef MOZ_EME
+ void SetCDMProxy(CDMProxy* aProxy) { mReader->SetCDMProxy(aProxy); }
+#endif
+
void SetVideoBlankDecode(bool aIsBlankDecode);
private:
diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp
index 04916d2f5..c5113bed4 100644
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1,4 +1,5 @@
/* -*- 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/. */
@@ -1227,7 +1228,33 @@ DecodeMetadataState::OnMetadataRead(MetadataHolder* aMetadata)
mMaster->GetAmpleVideoFrames());
}
- SetState<DecodingFirstFrameState>(SeekJob{});
+ // In general, we wait until we know the duration before notifying the decoder.
+ // However, we notify unconditionally in this case without waiting for the start
+ // time, since the caller might be waiting on metadataloaded to be fired before
+ // feeding in the CDM, which we need to decode the first frame (and
+ // thus get the metadata). We could fix this if we could compute the start
+ // time by demuxing without necessaring decoding.
+ bool waitingForCDM =
+#ifdef MOZ_EME
+ mMaster->Info().IsEncrypted() && !mMaster->mCDMProxy;
+#else
+ false;
+#endif
+
+ mMaster->mNotifyMetadataBeforeFirstFrame =
+ mMaster->mDuration.Ref().isSome() || waitingForCDM;
+
+ if (mMaster->mNotifyMetadataBeforeFirstFrame) {
+ mMaster->EnqueueLoadedMetadataEvent();
+ }
+
+ if (waitingForCDM) {
+ // Metadata parsing was successful but we're still waiting for CDM caps
+ // to become available so that we can build the correct decryptor/decoder.
+ SetState<WaitForCDMState>();
+ } else {
+ SetState<DecodingFirstFrameState>(SeekJob{});
+ }
}
void
@@ -1236,6 +1263,9 @@ DormantState::HandlePlayStateChanged(MediaDecoder::PlayState aPlayState)
{
if (aPlayState == MediaDecoder::PLAY_STATE_PLAYING) {
// Exit dormant when the user wants to play.
+#ifdef MOZ_EME
+ MOZ_ASSERT(!Info().IsEncrypted() || mMaster->mCDMProxy);
+#endif
MOZ_ASSERT(mMaster->mSentFirstFrameLoadedEvent);
SetState<SeekingState>(Move(mPendingSeek), EventVisibility::Suppressed);
}
@@ -1548,6 +1578,10 @@ ShutdownState::Enter()
// dispose of the timer.
master->mVideoDecodeSuspendTimer.Reset();
+#ifdef MOZ_EME
+ master->mCDMProxyPromise.DisconnectIfExists();
+#endif
+
if (master->IsPlaying()) {
master->StopPlayback();
}
@@ -2100,6 +2134,13 @@ nsresult MediaDecoderStateMachine::Init(MediaDecoder* aDecoder)
mMediaSink = CreateMediaSink(mAudioCaptured);
+#ifdef MOZ_EME
+ mCDMProxyPromise.Begin(aDecoder->RequestCDMProxy()->Then(
+ OwnerThread(), __func__, this,
+ &MediaDecoderStateMachine::OnCDMProxyReady,
+ &MediaDecoderStateMachine::OnCDMProxyNotReady));
+#endif
+
nsresult rv = mReader->Init();
NS_ENSURE_SUCCESS(rv, rv);
@@ -3074,6 +3115,25 @@ void MediaDecoderStateMachine::OnMediaSinkAudioError(nsresult aResult)
DecodeError(MediaResult(NS_ERROR_DOM_MEDIA_MEDIASINK_ERR, __func__));
}
+#ifdef MOZ_EME
+void
+MediaDecoderStateMachine::OnCDMProxyReady(RefPtr<CDMProxy> aProxy)
+{
+ MOZ_ASSERT(OnTaskQueue());
+ mCDMProxyPromise.Complete();
+ mCDMProxy = aProxy;
+ mReader->SetCDMProxy(aProxy);
+ mStateObj->HandleCDMProxyReady();
+}
+
+void
+MediaDecoderStateMachine::OnCDMProxyNotReady()
+{
+ MOZ_ASSERT(OnTaskQueue());
+ mCDMProxyPromise.Complete();
+}
+#endif
+
void
MediaDecoderStateMachine::SetAudioCaptured(bool aCaptured)
{
diff --git a/dom/media/MediaDecoderStateMachine.h b/dom/media/MediaDecoderStateMachine.h
index 6391c8574..f04f34983 100644
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -1,4 +1,5 @@
/* -*- 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/. */
@@ -767,6 +768,13 @@ private:
// Playback will not start when audio is offloading.
bool mAudioOffloading;
+#ifdef MOZ_EME
+ void OnCDMProxyReady(RefPtr<CDMProxy> aProxy);
+ void OnCDMProxyNotReady();
+ RefPtr<CDMProxy> mCDMProxy;
+ MozPromiseRequestHolder<MediaDecoder::CDMProxyPromise> mCDMProxyPromise;
+#endif
+
private:
// The buffered range. Mirrored from the decoder thread.
Mirror<media::TimeIntervals> mBuffered;
diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp
index 7775f0d9b..98ba57a6e 100644
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -3,6 +3,10 @@
* 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/. */
+#ifdef MOZ_EME
+#include "mozilla/CDMProxy.h"
+#endif
+
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/HTMLMediaElement.h"
#include "mozilla/Preferences.h"
@@ -347,7 +351,12 @@ MediaFormatReader::DecoderFactory::DoCreateDecoder(TrackType aTrack)
if (!mOwner->mPlatform) {
mOwner->mPlatform = new PDMFactory();
if (mOwner->IsEncrypted()) {
+#ifdef MOZ_EME
+ MOZ_ASSERT(mOwner->mCDMProxy);
+ mOwner->mPlatform->SetCDMProxy(mOwner->mCDMProxy);
+#else
return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "EME not supported");
+#endif
}
}
@@ -577,10 +586,54 @@ MediaFormatReader::InitInternal()
return NS_OK;
}
+#ifdef MOZ_EME
+class DispatchKeyNeededEvent : public Runnable {
+public:
+ DispatchKeyNeededEvent(AbstractMediaDecoder* aDecoder,
+ nsTArray<uint8_t>& aInitData,
+ const nsString& aInitDataType)
+ : mDecoder(aDecoder)
+ , mInitData(aInitData)
+ , mInitDataType(aInitDataType)
+ {
+ }
+ NS_IMETHOD Run() override {
+ // Note: Null check the owner, as the decoder could have been shutdown
+ // since this event was dispatched.
+ MediaDecoderOwner* owner = mDecoder->GetOwner();
+ if (owner) {
+ owner->DispatchEncrypted(mInitData, mInitDataType);
+ }
+ mDecoder = nullptr;
+ return NS_OK;
+ }
+private:
+ RefPtr<AbstractMediaDecoder> mDecoder;
+ nsTArray<uint8_t> mInitData;
+ nsString mInitDataType;
+};
+
+void
+MediaFormatReader::SetCDMProxy(CDMProxy* aProxy)
+{
+ RefPtr<CDMProxy> proxy = aProxy;
+ RefPtr<MediaFormatReader> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
+ MOZ_ASSERT(self->OnTaskQueue());
+ self->mCDMProxy = proxy;
+ });
+ OwnerThread()->Dispatch(r.forget());
+}
+#endif // MOZ_EME
+
bool
MediaFormatReader::IsWaitingOnCDMResource() {
- /* STUB */
+ MOZ_ASSERT(OnTaskQueue());
+#ifdef MOZ_EME
+ return IsEncrypted() && !mCDMProxy;
+#else
return false;
+#endif
}
RefPtr<MediaDecoderReader::MetadataPromise>
@@ -687,6 +740,13 @@ MediaFormatReader::OnDemuxerInitDone(nsresult)
UniquePtr<EncryptionInfo> crypto = mDemuxer->GetCrypto();
if (mDecoder && crypto && crypto->IsEncrypted()) {
+#ifdef MOZ_EME
+ // Try and dispatch 'encrypted'. Won't go if ready state still HAVE_NOTHING.
+ for (uint32_t i = 0; i < crypto->mInitDatas.Length(); i++) {
+ NS_DispatchToMainThread(
+ new DispatchKeyNeededEvent(mDecoder, crypto->mInitDatas[i].mInitData, crypto->mInitDatas[i].mType));
+ }
+#endif
mInfo.mCrypto = *crypto;
}
diff --git a/dom/media/MediaFormatReader.h b/dom/media/MediaFormatReader.h
index 3da080450..be0b7cd17 100644
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -1,4 +1,5 @@
/* -*- 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/. */
@@ -19,6 +20,10 @@
namespace mozilla {
+#ifdef MOZ_EME
+class CDMProxy;
+#endif
+
class MediaFormatReader final : public MediaDecoderReader
{
typedef TrackInfo::TrackType TrackType;
@@ -88,6 +93,10 @@ public:
return mTrackDemuxersMayBlock;
}
+#ifdef MOZ_EME
+ void SetCDMProxy(CDMProxy* aProxy) override;
+#endif
+
// Returns a string describing the state of the decoder data.
// Used for debugging purposes.
void GetMozDebugReaderData(nsAString& aString);
@@ -579,6 +588,10 @@ private:
RefPtr<VideoFrameContainer> mVideoFrameContainer;
layers::ImageContainer* GetImageContainer();
+#ifdef MOZ_EME
+ RefPtr<CDMProxy> mCDMProxy;
+#endif
+
RefPtr<GMPCrashHelper> mCrashHelper;
void SetBlankDecode(TrackType aTrack, bool aIsBlankDecode);
diff --git a/dom/media/eme/CDMCaps.cpp b/dom/media/eme/CDMCaps.cpp
new file mode 100644
index 000000000..a30fb59ac
--- /dev/null
+++ b/dom/media/eme/CDMCaps.cpp
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 8; 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 "mozilla/CDMCaps.h"
+#include "mozilla/EMEUtils.h"
+#include "nsThreadUtils.h"
+#include "SamplesWaitingForKey.h"
+
+namespace mozilla {
+
+CDMCaps::CDMCaps()
+ : mMonitor("CDMCaps")
+{
+}
+
+CDMCaps::~CDMCaps()
+{
+}
+
+void
+CDMCaps::Lock()
+{
+ mMonitor.Lock();
+}
+
+void
+CDMCaps::Unlock()
+{
+ mMonitor.Unlock();
+}
+
+CDMCaps::AutoLock::AutoLock(CDMCaps& aInstance)
+ : mData(aInstance)
+{
+ mData.Lock();
+}
+
+CDMCaps::AutoLock::~AutoLock()
+{
+ mData.Unlock();
+}
+
+// Keys with MediaKeyStatus::Usable, MediaKeyStatus::Output_downscaled,
+// or MediaKeyStatus::Output_restricted status can be used by the CDM
+// to decrypt or decrypt-and-decode samples.
+static bool
+IsUsableStatus(dom::MediaKeyStatus aStatus)
+{
+ return aStatus == dom::MediaKeyStatus::Usable ||
+ aStatus == dom::MediaKeyStatus::Output_restricted ||
+ aStatus == dom::MediaKeyStatus::Output_downscaled;
+}
+
+bool
+CDMCaps::AutoLock::IsKeyUsable(const CencKeyId& aKeyId)
+{
+ mData.mMonitor.AssertCurrentThreadOwns();
+ for (const KeyStatus& keyStatus : mData.mKeyStatuses) {
+ if (keyStatus.mId == aKeyId) {
+ return IsUsableStatus(keyStatus.mStatus);
+ }
+ }
+ return false;
+}
+
+bool
+CDMCaps::AutoLock::SetKeyStatus(const CencKeyId& aKeyId,
+ const nsString& aSessionId,
+ const dom::Optional<dom::MediaKeyStatus>& aStatus)
+{
+ mData.mMonitor.AssertCurrentThreadOwns();
+
+ if (!aStatus.WasPassed()) {
+ // Called from ForgetKeyStatus.
+ // Return true if the element is found to notify key changes.
+ return mData.mKeyStatuses.RemoveElement(KeyStatus(aKeyId,
+ aSessionId,
+ dom::MediaKeyStatus::Internal_error));
+ }
+
+ KeyStatus key(aKeyId, aSessionId, aStatus.Value());
+ auto index = mData.mKeyStatuses.IndexOf(key);
+ if (index != mData.mKeyStatuses.NoIndex) {
+ if (mData.mKeyStatuses[index].mStatus == aStatus.Value()) {
+ // No change.
+ return false;
+ }
+ auto oldStatus = mData.mKeyStatuses[index].mStatus;
+ mData.mKeyStatuses[index].mStatus = aStatus.Value();
+ // The old key status was one for which we can decrypt media. We don't
+ // need to do the "notify usable" step below, as it should be impossible
+ // for us to have anything waiting on this key to become usable, since it
+ // was already usable.
+ if (IsUsableStatus(oldStatus)) {
+ return true;
+ }
+ } else {
+ mData.mKeyStatuses.AppendElement(key);
+ }
+
+ // Only call NotifyUsable() for a key when we are going from non-usable
+ // to usable state.
+ if (!IsUsableStatus(aStatus.Value())) {
+ return true;
+ }
+
+ auto& waiters = mData.mWaitForKeys;
+ size_t i = 0;
+ while (i < waiters.Length()) {
+ auto& w = waiters[i];
+ if (w.mKeyId == aKeyId) {
+ w.mListener->NotifyUsable(aKeyId);
+ waiters.RemoveElementAt(i);
+ } else {
+ i++;
+ }
+ }
+ return true;
+}
+
+void
+CDMCaps::AutoLock::NotifyWhenKeyIdUsable(const CencKeyId& aKey,
+ SamplesWaitingForKey* aListener)
+{
+ mData.mMonitor.AssertCurrentThreadOwns();
+ MOZ_ASSERT(!IsKeyUsable(aKey));
+ MOZ_ASSERT(aListener);
+ mData.mWaitForKeys.AppendElement(WaitForKeys(aKey, aListener));
+}
+
+void
+CDMCaps::AutoLock::GetKeyStatusesForSession(const nsAString& aSessionId,
+ nsTArray<KeyStatus>& aOutKeyStatuses)
+{
+ for (const KeyStatus& keyStatus : mData.mKeyStatuses) {
+ if (keyStatus.mSessionId.Equals(aSessionId)) {
+ aOutKeyStatuses.AppendElement(keyStatus);
+ }
+ }
+}
+
+void
+CDMCaps::AutoLock::GetSessionIdsForKeyId(const CencKeyId& aKeyId,
+ nsTArray<nsCString>& aOutSessionIds)
+{
+ for (const KeyStatus& keyStatus : mData.mKeyStatuses) {
+ if (keyStatus.mId == aKeyId) {
+ aOutSessionIds.AppendElement(NS_ConvertUTF16toUTF8(keyStatus.mSessionId));
+ }
+ }
+}
+
+bool
+CDMCaps::AutoLock::RemoveKeysForSession(const nsString& aSessionId)
+{
+ bool changed = false;
+ nsTArray<KeyStatus> statuses;
+ GetKeyStatusesForSession(aSessionId, statuses);
+ for (const KeyStatus& status : statuses) {
+ changed |= SetKeyStatus(status.mId,
+ aSessionId,
+ dom::Optional<dom::MediaKeyStatus>());
+ }
+ return changed;
+}
+
+} // namespace mozilla
diff --git a/dom/media/eme/CDMCaps.h b/dom/media/eme/CDMCaps.h
new file mode 100644
index 000000000..cb4b5e291
--- /dev/null
+++ b/dom/media/eme/CDMCaps.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef CDMCaps_h_
+#define CDMCaps_h_
+
+#include "gmp-decryption.h"
+#include "nsIThread.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "SamplesWaitingForKey.h"
+
+#include "mozilla/Monitor.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/MediaKeyStatusMapBinding.h" // For MediaKeyStatus
+#include "mozilla/dom/BindingDeclarations.h" // For Optional
+
+namespace mozilla {
+
+// CDM capabilities; what keys a CDMProxy can use.
+// Must be locked to access state.
+class CDMCaps {
+public:
+ CDMCaps();
+ ~CDMCaps();
+
+ struct KeyStatus {
+ KeyStatus(const CencKeyId& aId,
+ const nsString& aSessionId,
+ dom::MediaKeyStatus aStatus)
+ : mId(aId)
+ , mSessionId(aSessionId)
+ , mStatus(aStatus)
+ {}
+ KeyStatus(const KeyStatus& aOther)
+ : mId(aOther.mId)
+ , mSessionId(aOther.mSessionId)
+ , mStatus(aOther.mStatus)
+ {}
+ bool operator==(const KeyStatus& aOther) const {
+ return mId == aOther.mId &&
+ mSessionId == aOther.mSessionId;
+ };
+
+ CencKeyId mId;
+ nsString mSessionId;
+ dom::MediaKeyStatus mStatus;
+ };
+
+ // 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();
+
+ bool IsKeyUsable(const CencKeyId& aKeyId);
+
+ // Returns true if key status changed,
+ // i.e. the key status changed from usable to expired.
+ bool SetKeyStatus(const CencKeyId& aKeyId,
+ const nsString& aSessionId,
+ const dom::Optional<dom::MediaKeyStatus>& aStatus);
+
+ void GetKeyStatusesForSession(const nsAString& aSessionId,
+ nsTArray<KeyStatus>& aOutKeyStatuses);
+
+ void GetSessionIdsForKeyId(const CencKeyId& aKeyId,
+ nsTArray<nsCString>& aOutSessionIds);
+
+ // Ensures all keys for a session are marked as 'unknown', i.e. removed.
+ // Returns true if a key status was changed.
+ bool RemoveKeysForSession(const nsString& aSessionId);
+
+ // Notifies the SamplesWaitingForKey when key become usable.
+ void NotifyWhenKeyIdUsable(const CencKeyId& aKey,
+ SamplesWaitingForKey* aSamplesWaiting);
+ private:
+ // Not taking a strong ref, since this should be allocated on the stack.
+ CDMCaps& mData;
+ };
+
+private:
+ void Lock();
+ void Unlock();
+
+ struct WaitForKeys {
+ WaitForKeys(const CencKeyId& aKeyId,
+ SamplesWaitingForKey* aListener)
+ : mKeyId(aKeyId)
+ , mListener(aListener)
+ {}
+ CencKeyId mKeyId;
+ RefPtr<SamplesWaitingForKey> mListener;
+ };
+
+ Monitor mMonitor;
+
+ nsTArray<KeyStatus> mKeyStatuses;
+
+ nsTArray<WaitForKeys> mWaitForKeys;
+
+ // It is not safe to copy this object.
+ CDMCaps(const CDMCaps&) = delete;
+ CDMCaps& operator=(const CDMCaps&) = delete;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/eme/CDMProxy.h b/dom/media/eme/CDMProxy.h
new file mode 100644
index 000000000..a9e783f50
--- /dev/null
+++ b/dom/media/eme/CDMProxy.h
@@ -0,0 +1,278 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef CDMProxy_h_
+#define CDMProxy_h_
+
+#include "mozilla/CDMCaps.h"
+#include "mozilla/MozPromise.h"
+
+#include "mozilla/dom/MediaKeyMessageEvent.h"
+#include "mozilla/dom/MediaKeys.h"
+
+#include "nsIThread.h"
+
+namespace mozilla {
+class MediaRawData;
+
+enum DecryptStatus {
+ Ok = 0,
+ GenericErr = 1,
+ NoKeyErr = 2,
+ AbortedErr = 3,
+};
+
+struct DecryptResult {
+ DecryptResult(DecryptStatus aStatus, MediaRawData* aSample)
+ : mStatus(aStatus)
+ , mSample(aSample)
+ {}
+ DecryptStatus mStatus;
+ RefPtr<MediaRawData> mSample;
+};
+
+class CDMKeyInfo {
+public:
+ explicit CDMKeyInfo(const nsTArray<uint8_t>& aKeyId)
+ : mKeyId(aKeyId)
+ , mStatus()
+ {}
+
+ CDMKeyInfo(const nsTArray<uint8_t>& aKeyId,
+ const dom::Optional<dom::MediaKeyStatus>& aStatus)
+ : mKeyId(aKeyId)
+ , mStatus(aStatus.Value())
+ {}
+
+ // The copy-ctor and copy-assignment operator for Optional<T> are declared as
+ // delete, so override CDMKeyInfo copy-ctor for nsTArray operations.
+ CDMKeyInfo(const CDMKeyInfo& aKeyInfo)
+ {
+ mKeyId = aKeyInfo.mKeyId;
+ if (aKeyInfo.mStatus.WasPassed()) {
+ mStatus.Construct(aKeyInfo.mStatus.Value());
+ }
+ }
+
+ nsTArray<uint8_t> mKeyId;
+ dom::Optional<dom::MediaKeyStatus> mStatus;
+};
+
+typedef int64_t UnixTime;
+
+// Proxies calls 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.
+class CDMProxy {
+protected:
+ typedef dom::PromiseId PromiseId;
+ typedef dom::MediaKeySessionType MediaKeySessionType;
+public:
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
+
+ typedef MozPromise<DecryptResult, DecryptResult, /* IsExclusive = */ true> DecryptPromise;
+
+ // Main thread only.
+ CDMProxy(dom::MediaKeys* aKeys,
+ const nsAString& aKeySystem,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired)
+ : mKeys(aKeys)
+ , mKeySystem(aKeySystem)
+ , mDistinctiveIdentifierRequired(aDistinctiveIdentifierRequired)
+ , mPersistentStateRequired(aPersistentStateRequired)
+ {}
+
+ // Main thread only.
+ // Loads the CDM corresponding to mKeySystem.
+ // Calls MediaKeys::OnCDMCreated() when the CDM is created.
+ virtual void Init(PromiseId aPromiseId,
+ const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aName,
+ bool aInPrivateBrowsing) = 0;
+
+ virtual void OnSetDecryptorId(uint32_t aId) {}
+
+ // Main thread only.
+ // Uses the CDM to create a key session.
+ // Calls MediaKeys::OnSessionActivated() when session is created.
+ // Assumes ownership of (Move()s) aInitData's contents.
+ virtual void CreateSession(uint32_t aCreateSessionToken,
+ MediaKeySessionType aSessionType,
+ PromiseId aPromiseId,
+ const nsAString& aInitDataType,
+ nsTArray<uint8_t>& aInitData) = 0;
+
+ // Main thread only.
+ // Uses the CDM to load a presistent session stored on disk.
+ // Calls MediaKeys::OnSessionActivated() when session is loaded.
+ virtual void LoadSession(PromiseId aPromiseId,
+ const nsAString& aSessionId) = 0;
+
+ // Main thread only.
+ // Sends a new certificate to the CDM.
+ // Calls MediaKeys->ResolvePromise(aPromiseId) after the CDM has
+ // processed the request.
+ // Assumes ownership of (Move()s) aCert's contents.
+ virtual void SetServerCertificate(PromiseId aPromiseId,
+ nsTArray<uint8_t>& aCert) = 0;
+
+ // Main thread only.
+ // Sends an update to the CDM.
+ // Calls MediaKeys->ResolvePromise(aPromiseId) after the CDM has
+ // processed the request.
+ // Assumes ownership of (Move()s) aResponse's contents.
+ virtual void UpdateSession(const nsAString& aSessionId,
+ PromiseId aPromiseId,
+ nsTArray<uint8_t>& aResponse) = 0;
+
+ // Main thread only.
+ // Calls MediaKeys->ResolvePromise(aPromiseId) after the CDM has
+ // processed the request.
+ // If processing this operation results in the session actually closing,
+ // we also call MediaKeySession::OnClosed(), which in turn calls
+ // MediaKeys::OnSessionClosed().
+ virtual void CloseSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) = 0;
+
+ // Main thread only.
+ // Removes all data for a persisent session.
+ // Calls MediaKeys->ResolvePromise(aPromiseId) after the CDM has
+ // processed the request.
+ virtual void RemoveSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) = 0;
+
+ // Main thread only.
+ virtual void Shutdown() = 0;
+
+ // Main thread only.
+ virtual void Terminated() = 0;
+
+ // Threadsafe.
+ virtual const nsCString& GetNodeId() const = 0;
+
+ // Main thread only.
+ virtual void OnSetSessionId(uint32_t aCreateSessionToken,
+ const nsAString& aSessionId) = 0;
+
+ // Main thread only.
+ virtual void OnResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess) = 0;
+
+ // Main thread only.
+ virtual void OnSessionMessage(const nsAString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ nsTArray<uint8_t>& aMessage) = 0;
+
+ // Main thread only.
+ virtual void OnExpirationChange(const nsAString& aSessionId,
+ UnixTime aExpiryTime) = 0;
+
+ // Main thread only.
+ virtual void OnSessionClosed(const nsAString& aSessionId) = 0;
+
+ // Main thread only.
+ virtual void OnSessionError(const nsAString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsAString& aMsg) = 0;
+
+ // Main thread only.
+ virtual void OnRejectPromise(uint32_t aPromiseId,
+ nsresult aDOMException,
+ const nsCString& aMsg) = 0;
+
+ virtual RefPtr<DecryptPromise> Decrypt(MediaRawData* aSample) = 0;
+
+ // Owner thread only.
+ virtual void OnDecrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData) = 0;
+
+ // Reject promise with DOMException corresponding to aExceptionCode.
+ // Can be called from any thread.
+ virtual void RejectPromise(PromiseId aId,
+ nsresult aExceptionCode,
+ const nsCString& aReason) = 0;
+
+ // Resolves promise with "undefined".
+ // Can be called from any thread.
+ virtual void ResolvePromise(PromiseId aId) = 0;
+
+ // Threadsafe.
+ virtual const nsString& KeySystem() const = 0;
+
+ virtual CDMCaps& Capabilites() = 0;
+
+ // Main thread only.
+ virtual void OnKeyStatusesChange(const nsAString& aSessionId) = 0;
+
+ virtual void GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
+ nsTArray<nsCString>& aSessionIds) = 0;
+
+#ifdef DEBUG
+ virtual bool IsOnOwnerThread() = 0;
+#endif
+
+ virtual uint32_t GetDecryptorId() { return 0; }
+
+protected:
+ virtual ~CDMProxy() {}
+
+ // Helper to enforce that a raw pointer is only accessed on the main thread.
+ template<class Type>
+ class MainThreadOnlyRawPtr {
+ public:
+ explicit MainThreadOnlyRawPtr(Type* aPtr)
+ : mPtr(aPtr)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ bool IsNull() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return !mPtr;
+ }
+
+ void Clear() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mPtr = nullptr;
+ }
+
+ Type* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mPtr;
+ }
+ private:
+ Type* mPtr;
+ };
+
+ // Our reference back to the MediaKeys object.
+ // WARNING: This is a non-owning reference that is cleared by MediaKeys
+ // destructor. only use on main thread, and always nullcheck before using!
+ MainThreadOnlyRawPtr<dom::MediaKeys> mKeys;
+
+ const nsString mKeySystem;
+
+ // Onwer specified thread. e.g. Gecko Media Plugin thread.
+ // All interactions with the out-of-process EME plugin must come from this thread.
+ RefPtr<nsIThread> mOwnerThread;
+
+ nsCString mNodeId;
+
+ CDMCaps mCapabilites;
+
+ const bool mDistinctiveIdentifierRequired;
+ const bool mPersistentStateRequired;
+};
+
+
+} // namespace mozilla
+
+#endif // CDMProxy_h_
diff --git a/dom/media/eme/DecryptorProxyCallback.h b/dom/media/eme/DecryptorProxyCallback.h
new file mode 100644
index 000000000..c1fcb49a4
--- /dev/null
+++ b/dom/media/eme/DecryptorProxyCallback.h
@@ -0,0 +1,54 @@
+/* -*- 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/. */
+
+#ifndef DecryptorProxyCallback_h_
+#define DecryptorProxyCallback_h_
+
+#include "mozilla/dom/MediaKeyStatusMapBinding.h" // For MediaKeyStatus
+#include "mozilla/dom/MediaKeyMessageEventBinding.h" // For MediaKeyMessageType
+#include "mozilla/CDMProxy.h"
+
+class DecryptorProxyCallback {
+public:
+
+ virtual ~DecryptorProxyCallback() {}
+
+ virtual void SetDecryptorId(uint32_t aId) = 0;
+
+ virtual void SetSessionId(uint32_t aCreateSessionId,
+ const nsCString& aSessionId) = 0;
+
+ virtual void ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess) = 0;
+
+ virtual void ResolvePromise(uint32_t aPromiseId) = 0;
+
+ virtual void RejectPromise(uint32_t aPromiseId,
+ nsresult aException,
+ const nsCString& aSessionId) = 0;
+
+ virtual void SessionMessage(const nsCString& aSessionId,
+ mozilla::dom::MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage) = 0;
+
+ virtual void ExpirationChange(const nsCString& aSessionId,
+ mozilla::UnixTime aExpiryTime) = 0;
+
+ virtual void SessionClosed(const nsCString& aSessionId) = 0;
+
+ virtual void SessionError(const nsCString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsCString& aMessage) = 0;
+
+ virtual void Decrypted(uint32_t aId,
+ mozilla::DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData) = 0;
+
+ virtual void BatchedKeyStatusChanged(const nsCString& aSessionId,
+ const nsTArray<mozilla::CDMKeyInfo>& aKeyInfos) = 0;
+};
+
+#endif
diff --git a/dom/media/eme/DetailedPromise.cpp b/dom/media/eme/DetailedPromise.cpp
new file mode 100644
index 000000000..1aa83bef5
--- /dev/null
+++ b/dom/media/eme/DetailedPromise.cpp
@@ -0,0 +1,57 @@
+/* -*- 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 "DetailedPromise.h"
+#include "mozilla/dom/DOMException.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla {
+namespace dom {
+
+DetailedPromise::DetailedPromise(nsIGlobalObject* aGlobal,
+ const nsACString& aName)
+ : Promise(aGlobal)
+ , mName(aName)
+ , mResponded(false)
+ , mStartTime(TimeStamp::Now())
+{
+}
+
+DetailedPromise::~DetailedPromise()
+{
+}
+
+void
+DetailedPromise::MaybeReject(nsresult aArg, const nsACString& aReason)
+{
+ nsPrintfCString msg("%s promise rejected 0x%x '%s'", mName.get(), aArg,
+ PromiseFlatCString(aReason).get());
+ EME_LOG(msg.get());
+
+ LogToBrowserConsole(NS_ConvertUTF8toUTF16(msg));
+
+ ErrorResult rv;
+ rv.ThrowDOMException(aArg, aReason);
+ Promise::MaybeReject(rv);
+}
+
+void
+DetailedPromise::MaybeReject(ErrorResult&, const nsACString& aReason)
+{
+ NS_NOTREACHED("nsresult expected in MaybeReject()");
+}
+
+/* static */ already_AddRefed<DetailedPromise>
+DetailedPromise::Create(nsIGlobalObject* aGlobal,
+ ErrorResult& aRv,
+ const nsACString& aName)
+{
+ RefPtr<DetailedPromise> promise = new DetailedPromise(aGlobal, aName);
+ promise->CreateWrapper(nullptr, aRv);
+ return aRv.Failed() ? nullptr : promise.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/DetailedPromise.h b/dom/media/eme/DetailedPromise.h
new file mode 100644
index 000000000..f7f10aa40
--- /dev/null
+++ b/dom/media/eme/DetailedPromise.h
@@ -0,0 +1,57 @@
+/* -*- 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/. */
+
+#ifndef __DetailedPromise_h__
+#define __DetailedPromise_h__
+
+#include "mozilla/dom/Promise.h"
+#include "EMEUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+/*
+ * This is pretty horrible; bug 1160445.
+ * Extend Promise to add custom DOMException messages on rejection.
+ * Get rid of this once we've ironed out EME errors in the wild.
+ */
+class DetailedPromise : public Promise
+{
+public:
+ static already_AddRefed<DetailedPromise>
+ Create(nsIGlobalObject* aGlobal,
+ ErrorResult& aRv,
+ const nsACString& aName);
+
+ template <typename T>
+ void MaybeResolve(const T& aArg)
+ {
+ EME_LOG("%s promise resolved", mName.get());
+ Promise::MaybeResolve<T>(aArg);
+ }
+
+ void MaybeReject(nsresult aArg) = delete;
+ void MaybeReject(nsresult aArg, const nsACString& aReason);
+
+ void MaybeReject(ErrorResult& aArg) = delete;
+ void MaybeReject(ErrorResult&, const nsACString& aReason);
+
+private:
+ explicit DetailedPromise(nsIGlobalObject* aGlobal,
+ const nsACString& aName);
+
+ virtual ~DetailedPromise();
+
+ enum Status { Succeeded, Failed };
+
+ nsCString mName;
+ bool mResponded;
+ TimeStamp mStartTime;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // __DetailedPromise_h__
diff --git a/dom/media/eme/EMEUtils.cpp b/dom/media/eme/EMEUtils.cpp
new file mode 100644
index 000000000..68ef52d83
--- /dev/null
+++ b/dom/media/eme/EMEUtils.cpp
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; 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 "mozilla/EMEUtils.h"
+
+#include "jsfriendapi.h" // for AutoCheckCannotGC
+#include "mozilla/dom/UnionTypes.h"
+
+namespace mozilla {
+
+LogModule* GetEMELog() {
+ static LazyLogModule log("EME");
+ return log;
+}
+
+LogModule* GetEMEVerboseLog() {
+ static LazyLogModule log("EMEV");
+ return log;
+}
+
+ArrayData
+GetArrayBufferViewOrArrayBufferData(const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView)
+{
+ MOZ_ASSERT(aBufferOrView.IsArrayBuffer() || aBufferOrView.IsArrayBufferView());
+ JS::AutoCheckCannotGC nogc;
+ if (aBufferOrView.IsArrayBuffer()) {
+ const dom::ArrayBuffer& buffer = aBufferOrView.GetAsArrayBuffer();
+ buffer.ComputeLengthAndData();
+ return ArrayData(buffer.Data(), buffer.Length());
+ } else if (aBufferOrView.IsArrayBufferView()) {
+ const dom::ArrayBufferView& bufferview = aBufferOrView.GetAsArrayBufferView();
+ bufferview.ComputeLengthAndData();
+ return ArrayData(bufferview.Data(), bufferview.Length());
+ }
+ return ArrayData(nullptr, 0);
+}
+
+void
+CopyArrayBufferViewOrArrayBufferData(const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView,
+ nsTArray<uint8_t>& aOutData)
+{
+ JS::AutoCheckCannotGC nogc;
+ ArrayData data = GetArrayBufferViewOrArrayBufferData(aBufferOrView);
+ aOutData.Clear();
+ if (!data.IsValid()) {
+ return;
+ }
+ aOutData.AppendElements(data.mData, data.mLength);
+}
+
+void CopyArrayBufferViewOrArrayBufferData(const dom::ArrayBuffer& aBuffer,
+ nsTArray<uint8_t>& aOutData) {
+ JS::AutoCheckCannotGC nogc;
+ aBuffer.ComputeLengthAndData();
+ aOutData.Clear();
+ aOutData.AppendElements(aBuffer.Data(), aBuffer.Length());
+}
+
+bool
+IsClearkeyKeySystem(const nsAString& aKeySystem)
+{
+ return !CompareUTF8toUTF16(kEMEKeySystemClearkey, aKeySystem);
+}
+
+bool
+IsWidevineKeySystem(const nsAString& aKeySystem)
+{
+ return !CompareUTF8toUTF16(kEMEKeySystemWidevine, aKeySystem);
+}
+
+nsString
+KeySystemToGMPName(const nsAString& aKeySystem)
+{
+ if (IsClearkeyKeySystem(aKeySystem)) {
+ return NS_LITERAL_STRING("gmp-clearkey");
+ }
+ if (IsWidevineKeySystem(aKeySystem)) {
+ return NS_LITERAL_STRING("gmp-widevinecdm");
+ }
+ MOZ_ASSERT(false, "We should only call this for known GMPs");
+ return EmptyString();
+}
+
+} // namespace mozilla
diff --git a/dom/media/eme/EMEUtils.h b/dom/media/eme/EMEUtils.h
new file mode 100644
index 000000000..aef157482
--- /dev/null
+++ b/dom/media/eme/EMEUtils.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef EME_LOG_H_
+#define EME_LOG_H_
+
+#include "VideoUtils.h"
+#include "mozilla/Logging.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/dom/TypedArray.h"
+
+namespace mozilla {
+
+namespace dom {
+class ArrayBufferViewOrArrayBuffer;
+}
+
+#ifndef EME_LOG
+ LogModule* GetEMELog();
+ #define EME_LOG(...) MOZ_LOG(GetEMELog(), mozilla::LogLevel::Debug, (__VA_ARGS__))
+ #define EME_LOG_ENABLED() MOZ_LOG_TEST(GetEMELog(), mozilla::LogLevel::Debug)
+#endif
+
+#ifndef EME_VERBOSE_LOG
+ LogModule* GetEMEVerboseLog();
+ #define EME_VERBOSE_LOG(...) MOZ_LOG(GetEMEVerboseLog(), mozilla::LogLevel::Debug, (__VA_ARGS__))
+#else
+ #ifndef EME_LOG
+ #define EME_LOG(...)
+ #endif
+
+ #ifndef EME_VERBOSE_LOG
+ #define EME_VERBOSE_LOG(...)
+ #endif
+#endif
+
+// Helper function to extract a copy of data coming in from JS in an
+// (ArrayBuffer or ArrayBufferView) IDL typed function argument.
+//
+// Only call this on a properly initialized ArrayBufferViewOrArrayBuffer.
+void
+CopyArrayBufferViewOrArrayBufferData(const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView,
+ nsTArray<uint8_t>& aOutData);
+
+// Overload for ArrayBuffer
+void CopyArrayBufferViewOrArrayBufferData(const dom::ArrayBuffer& aBufferOrView,
+ nsTArray<uint8_t>& aOutData);
+
+struct ArrayData {
+ explicit ArrayData(const uint8_t* aData, size_t aLength)
+ : mData(aData)
+ , mLength(aLength)
+ {
+ }
+ const uint8_t* mData;
+ const size_t mLength;
+ bool IsValid() const {
+ return mData != nullptr && mLength != 0;
+ }
+ bool operator== (const nsTArray<uint8_t>& aOther) const {
+ return mLength == aOther.Length() &&
+ memcmp(mData, aOther.Elements(), mLength) == 0;
+ }
+};
+
+// Helper function to extract data coming in from JS in an
+// (ArrayBuffer or ArrayBufferView) IDL typed function argument.
+//
+// Be *very* careful with this!
+//
+// Only use returned ArrayData inside the lifetime of the
+// ArrayBufferViewOrArrayBuffer; the ArrayData struct does not contain
+// a copy of the data!
+//
+// And do *not* call out to anything that could call into JavaScript,
+// while the ArrayData is live, as then all bets about the data not changing
+// are off! No calls into JS, no calls into JS-implemented WebIDL or XPIDL,
+// nothing. Beware!
+//
+// Only call this on a properly initialized ArrayBufferViewOrArrayBuffer.
+ArrayData
+GetArrayBufferViewOrArrayBufferData(const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView);
+
+nsString
+KeySystemToGMPName(const nsAString& aKeySystem);
+
+bool
+IsClearkeyKeySystem(const nsAString& aKeySystem);
+
+bool
+IsWidevineKeySystem(const nsAString& aKeySystem);
+
+enum CDMType {
+ eClearKey = 0,
+ eWidevine = 2,
+ eUnknown = 3
+};
+
+} // namespace mozilla
+
+#endif // EME_LOG_H_
diff --git a/dom/media/eme/MediaEncryptedEvent.cpp b/dom/media/eme/MediaEncryptedEvent.cpp
new file mode 100644
index 000000000..a9c83291e
--- /dev/null
+++ b/dom/media/eme/MediaEncryptedEvent.cpp
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 8; 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 "MediaEncryptedEvent.h"
+#include "mozilla/dom/MediaEncryptedEventBinding.h"
+#include "nsContentUtils.h"
+#include "jsfriendapi.h"
+#include "nsINode.h"
+#include "mozilla/dom/MediaKeys.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaEncryptedEvent)
+
+NS_IMPL_ADDREF_INHERITED(MediaEncryptedEvent, Event)
+NS_IMPL_RELEASE_INHERITED(MediaEncryptedEvent, Event)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaEncryptedEvent, Event)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(MediaEncryptedEvent, Event)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mInitData)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaEncryptedEvent, Event)
+ tmp->mInitData = nullptr;
+ mozilla::DropJSObjects(this);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaEncryptedEvent)
+NS_INTERFACE_MAP_END_INHERITING(Event)
+
+MediaEncryptedEvent::MediaEncryptedEvent(EventTarget* aOwner)
+ : Event(aOwner, nullptr, nullptr)
+{
+ mozilla::HoldJSObjects(this);
+}
+
+MediaEncryptedEvent::~MediaEncryptedEvent()
+{
+ mInitData = nullptr;
+ mozilla::DropJSObjects(this);
+}
+
+JSObject*
+MediaEncryptedEvent::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaEncryptedEventBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<MediaEncryptedEvent>
+MediaEncryptedEvent::Constructor(EventTarget* aOwner)
+{
+ RefPtr<MediaEncryptedEvent> e = new MediaEncryptedEvent(aOwner);
+ e->InitEvent(NS_LITERAL_STRING("encrypted"), false, false);
+ e->SetTrusted(true);
+ return e.forget();
+}
+
+already_AddRefed<MediaEncryptedEvent>
+MediaEncryptedEvent::Constructor(EventTarget* aOwner,
+ const nsAString& aInitDataType,
+ const nsTArray<uint8_t>& aInitData)
+{
+ RefPtr<MediaEncryptedEvent> e = new MediaEncryptedEvent(aOwner);
+ e->InitEvent(NS_LITERAL_STRING("encrypted"), false, false);
+ e->mInitDataType = aInitDataType;
+ e->mRawInitData = aInitData;
+ e->SetTrusted(true);
+ return e.forget();
+}
+
+already_AddRefed<MediaEncryptedEvent>
+MediaEncryptedEvent::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aType,
+ const MediaKeyNeededEventInit& aEventInitDict,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports());
+ RefPtr<MediaEncryptedEvent> e = new MediaEncryptedEvent(owner);
+ bool trusted = e->Init(owner);
+ e->InitEvent(aType, aEventInitDict.mBubbles, aEventInitDict.mCancelable);
+ e->mInitDataType = aEventInitDict.mInitDataType;
+ if (!aEventInitDict.mInitData.IsNull()) {
+ const auto& a = aEventInitDict.mInitData.Value();
+ nsTArray<uint8_t> initData;
+ CopyArrayBufferViewOrArrayBufferData(a, initData);
+ e->mInitData = ArrayBuffer::Create(aGlobal.Context(), initData.Length(),
+ initData.Elements());
+ if (!e->mInitData) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+ }
+ e->SetTrusted(trusted);
+ return e.forget();
+}
+
+void
+MediaEncryptedEvent::GetInitDataType(nsString& aRetVal) const
+{
+ aRetVal = mInitDataType;
+}
+
+void
+MediaEncryptedEvent::GetInitData(JSContext* cx,
+ JS::MutableHandle<JSObject*> aData,
+ ErrorResult& aRv)
+{
+ if (mRawInitData.Length()) {
+ mInitData = ArrayBuffer::Create(cx,
+ this,
+ mRawInitData.Length(),
+ mRawInitData.Elements());
+ if (!mInitData) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ mRawInitData.Clear();
+ }
+ aData.set(mInitData);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/MediaEncryptedEvent.h b/dom/media/eme/MediaEncryptedEvent.h
new file mode 100644
index 000000000..c2ac56061
--- /dev/null
+++ b/dom/media/eme/MediaEncryptedEvent.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef mozilla_dom_MediaKeyNeededEvent_h__
+#define mozilla_dom_MediaKeyNeededEvent_h__
+
+#include "mozilla/dom/MediaEncryptedEventBinding.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "js/TypeDecls.h"
+
+namespace mozilla {
+namespace dom {
+
+class MediaEncryptedEvent final : public Event
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(MediaEncryptedEvent, Event)
+protected:
+ virtual ~MediaEncryptedEvent();
+ explicit MediaEncryptedEvent(EventTarget* aOwner);
+
+ nsString mInitDataType;
+ JS::Heap<JSObject*> mInitData;
+
+public:
+
+ JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<MediaEncryptedEvent>
+ Constructor(EventTarget* aOwner);
+
+ static already_AddRefed<MediaEncryptedEvent>
+ Constructor(EventTarget* aOwner,
+ const nsAString& aInitDataType,
+ const nsTArray<uint8_t>& aInitData);
+
+ static already_AddRefed<MediaEncryptedEvent>
+ Constructor(const GlobalObject& aGlobal,
+ const nsAString& aType,
+ const MediaKeyNeededEventInit& aEventInitDict,
+ ErrorResult& aRv);
+
+ void GetInitDataType(nsString& aRetVal) const;
+
+ void GetInitData(JSContext* cx,
+ JS::MutableHandle<JSObject*> aData,
+ ErrorResult& aRv);
+private:
+ nsTArray<uint8_t> mRawInitData;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_MediaKeyNeededEvent_h__
diff --git a/dom/media/eme/MediaKeyError.cpp b/dom/media/eme/MediaKeyError.cpp
new file mode 100644
index 000000000..635c34364
--- /dev/null
+++ b/dom/media/eme/MediaKeyError.cpp
@@ -0,0 +1,39 @@
+/* -*- 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 "MediaKeyError.h"
+#include "mozilla/dom/MediaKeyErrorBinding.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+MediaKeyError::MediaKeyError(EventTarget* aOwner, uint32_t aSystemCode)
+ : Event(aOwner, nullptr, nullptr)
+ , mSystemCode(aSystemCode)
+{
+ InitEvent(NS_LITERAL_STRING("error"), false, false);
+}
+
+MediaKeyError::~MediaKeyError()
+{
+}
+
+uint32_t
+MediaKeyError::SystemCode() const
+{
+ return mSystemCode;
+}
+
+JSObject*
+MediaKeyError::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaKeyErrorBinding::Wrap(aCx, this, aGivenProto);
+}
+
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/MediaKeyError.h b/dom/media/eme/MediaKeyError.h
new file mode 100644
index 000000000..df79b2c95
--- /dev/null
+++ b/dom/media/eme/MediaKeyError.h
@@ -0,0 +1,38 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_MediaKeyError_h
+#define mozilla_dom_MediaKeyError_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/Event.h"
+#include "js/TypeDecls.h"
+
+namespace mozilla {
+namespace dom {
+
+class MediaKeyError final : public Event
+{
+public:
+ NS_FORWARD_TO_EVENT
+
+ MediaKeyError(EventTarget* aOwner, uint32_t aSystemCode);
+ ~MediaKeyError();
+
+ JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ uint32_t SystemCode() const;
+
+private:
+ uint32_t mSystemCode;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/eme/MediaKeyMessageEvent.cpp b/dom/media/eme/MediaKeyMessageEvent.cpp
new file mode 100644
index 000000000..ee13f8d26
--- /dev/null
+++ b/dom/media/eme/MediaKeyMessageEvent.cpp
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 8; 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 "mozilla/dom/MediaKeyMessageEvent.h"
+#include "mozilla/dom/MediaKeyMessageEventBinding.h"
+#include "js/GCAPI.h"
+#include "jsfriendapi.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/PrimitiveConversions.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/TypedArray.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/MediaKeys.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaKeyMessageEvent)
+
+NS_IMPL_ADDREF_INHERITED(MediaKeyMessageEvent, Event)
+NS_IMPL_RELEASE_INHERITED(MediaKeyMessageEvent, Event)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaKeyMessageEvent, Event)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(MediaKeyMessageEvent, Event)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMessage)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaKeyMessageEvent, Event)
+ tmp->mMessage = nullptr;
+ mozilla::DropJSObjects(this);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaKeyMessageEvent)
+NS_INTERFACE_MAP_END_INHERITING(Event)
+
+MediaKeyMessageEvent::MediaKeyMessageEvent(EventTarget* aOwner)
+ : Event(aOwner, nullptr, nullptr)
+{
+ mozilla::HoldJSObjects(this);
+}
+
+MediaKeyMessageEvent::~MediaKeyMessageEvent()
+{
+ mMessage = nullptr;
+ mozilla::DropJSObjects(this);
+}
+
+MediaKeyMessageEvent*
+MediaKeyMessageEvent::AsMediaKeyMessageEvent()
+{
+ return this;
+}
+
+JSObject*
+MediaKeyMessageEvent::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaKeyMessageEventBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<MediaKeyMessageEvent>
+MediaKeyMessageEvent::Constructor(EventTarget* aOwner,
+ MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage)
+{
+ RefPtr<MediaKeyMessageEvent> e = new MediaKeyMessageEvent(aOwner);
+ e->InitEvent(NS_LITERAL_STRING("message"), false, false);
+ e->mMessageType = aMessageType;
+ e->mRawMessage = aMessage;
+ e->SetTrusted(true);
+ return e.forget();
+}
+
+already_AddRefed<MediaKeyMessageEvent>
+MediaKeyMessageEvent::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aType,
+ const MediaKeyMessageEventInit& aEventInitDict,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports());
+ RefPtr<MediaKeyMessageEvent> e = new MediaKeyMessageEvent(owner);
+ bool trusted = e->Init(owner);
+ e->InitEvent(aType, aEventInitDict.mBubbles, aEventInitDict.mCancelable);
+ nsTArray<uint8_t> initData;
+ CopyArrayBufferViewOrArrayBufferData(aEventInitDict.mMessage, initData);
+ e->mMessage = ArrayBuffer::Create(aGlobal.Context(),
+ initData.Length(),
+ initData.Elements());
+ if (!e->mMessage) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+ e->mMessageType = aEventInitDict.mMessageType;
+ e->SetTrusted(trusted);
+ e->SetComposed(aEventInitDict.mComposed);
+ return e.forget();
+}
+
+void
+MediaKeyMessageEvent::GetMessage(JSContext* cx,
+ JS::MutableHandle<JSObject*> aMessage,
+ ErrorResult& aRv)
+{
+ if (!mMessage) {
+ mMessage = ArrayBuffer::Create(cx,
+ this,
+ mRawMessage.Length(),
+ mRawMessage.Elements());
+ if (!mMessage) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ mRawMessage.Clear();
+ }
+ aMessage.set(mMessage);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/MediaKeyMessageEvent.h b/dom/media/eme/MediaKeyMessageEvent.h
new file mode 100644
index 000000000..2ec50f4f0
--- /dev/null
+++ b/dom/media/eme/MediaKeyMessageEvent.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef mozilla_dom_MediaKeyMessageEvent_h__
+#define mozilla_dom_MediaKeyMessageEvent_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/TypedArray.h"
+#include "js/TypeDecls.h"
+#include "mozilla/dom/MediaKeyMessageEventBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+struct MediaKeyMessageEventInit;
+
+class MediaKeyMessageEvent final : public Event
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(MediaKeyMessageEvent, Event)
+protected:
+ virtual ~MediaKeyMessageEvent();
+ explicit MediaKeyMessageEvent(EventTarget* aOwner);
+
+ MediaKeyMessageType mMessageType;
+ JS::Heap<JSObject*> mMessage;
+
+public:
+ virtual MediaKeyMessageEvent* AsMediaKeyMessageEvent();
+
+ JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<MediaKeyMessageEvent>
+ Constructor(EventTarget* aOwner,
+ MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage);
+
+ static already_AddRefed<MediaKeyMessageEvent>
+ Constructor(const GlobalObject& aGlobal,
+ const nsAString& aType,
+ const MediaKeyMessageEventInit& aEventInitDict,
+ ErrorResult& aRv);
+
+ MediaKeyMessageType MessageType() const { return mMessageType; }
+
+ void GetMessage(JSContext* cx,
+ JS::MutableHandle<JSObject*> aMessage,
+ ErrorResult& aRv);
+
+private:
+ nsTArray<uint8_t> mRawMessage;
+};
+
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_MediaKeyMessageEvent_h__
diff --git a/dom/media/eme/MediaKeySession.cpp b/dom/media/eme/MediaKeySession.cpp
new file mode 100644
index 000000000..b5f3fe498
--- /dev/null
+++ b/dom/media/eme/MediaKeySession.cpp
@@ -0,0 +1,670 @@
+/* -*- Mode: C++; tab-width: 8; 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 "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozilla/dom/MediaKeyError.h"
+#include "mozilla/dom/MediaKeyMessageEvent.h"
+#include "mozilla/dom/MediaEncryptedEvent.h"
+#include "mozilla/dom/MediaKeyStatusMap.h"
+#include "mozilla/dom/MediaKeySystemAccess.h"
+#include "mozilla/dom/KeyIdsInitDataBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/CDMProxy.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Move.h"
+#include "nsContentUtils.h"
+#include "mozilla/EMEUtils.h"
+#include "GMPUtils.h"
+#include "nsPrintfCString.h"
+#include "psshparser/PsshParser.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaKeySession,
+ DOMEventTargetHelper,
+ mMediaKeyError,
+ mKeys,
+ mKeyStatusMap,
+ mClosed)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaKeySession)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(MediaKeySession, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(MediaKeySession, DOMEventTargetHelper)
+
+// Count of number of instances. Used to give each instance a
+// unique token.
+static uint32_t sMediaKeySessionNum = 0;
+
+// Max length of keyId in EME "keyIds" or WebM init data format, as enforced
+// by web platform tests.
+static const uint32_t MAX_KEY_ID_LENGTH = 512;
+
+// Max length of CENC PSSH init data tolerated, as enforced by web
+// platform tests.
+static const uint32_t MAX_CENC_INIT_DATA_LENGTH = 64 * 1024;
+
+
+MediaKeySession::MediaKeySession(JSContext* aCx,
+ nsPIDOMWindowInner* aParent,
+ MediaKeys* aKeys,
+ const nsAString& aKeySystem,
+ MediaKeySessionType aSessionType,
+ ErrorResult& aRv)
+ : DOMEventTargetHelper(aParent)
+ , mKeys(aKeys)
+ , mKeySystem(aKeySystem)
+ , mSessionType(aSessionType)
+ , mToken(sMediaKeySessionNum++)
+ , mIsClosed(false)
+ , mUninitialized(true)
+ , mKeyStatusMap(new MediaKeyStatusMap(aParent))
+ , mExpiration(JS::GenericNaN())
+{
+ EME_LOG("MediaKeySession[%p,''] ctor", this);
+
+ MOZ_ASSERT(aParent);
+ if (aRv.Failed()) {
+ return;
+ }
+ mClosed = MakePromise(aRv, NS_LITERAL_CSTRING("MediaKeys.createSession"));
+}
+
+void MediaKeySession::SetSessionId(const nsAString& aSessionId)
+{
+ EME_LOG("MediaKeySession[%p,'%s'] session Id set",
+ this, NS_ConvertUTF16toUTF8(aSessionId).get());
+
+ if (NS_WARN_IF(!mSessionId.IsEmpty())) {
+ return;
+ }
+ mSessionId = aSessionId;
+ mKeys->OnSessionIdReady(this);
+}
+
+MediaKeySession::~MediaKeySession()
+{
+}
+
+MediaKeyError*
+MediaKeySession::GetError() const
+{
+ return mMediaKeyError;
+}
+
+void
+MediaKeySession::GetKeySystem(nsString& aOutKeySystem) const
+{
+ aOutKeySystem.Assign(mKeySystem);
+}
+
+void
+MediaKeySession::GetSessionId(nsString& aSessionId) const
+{
+ aSessionId = GetSessionId();
+}
+
+const nsString&
+MediaKeySession::GetSessionId() const
+{
+ return mSessionId;
+}
+
+JSObject*
+MediaKeySession::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaKeySessionBinding::Wrap(aCx, this, aGivenProto);
+}
+
+double
+MediaKeySession::Expiration() const
+{
+ return mExpiration;
+}
+
+Promise*
+MediaKeySession::Closed() const
+{
+ return mClosed;
+}
+
+void
+MediaKeySession::UpdateKeyStatusMap()
+{
+ MOZ_ASSERT(!IsClosed());
+ if (!mKeys->GetCDMProxy()) {
+ return;
+ }
+
+ nsTArray<CDMCaps::KeyStatus> keyStatuses;
+ {
+ CDMCaps::AutoLock caps(mKeys->GetCDMProxy()->Capabilites());
+ caps.GetKeyStatusesForSession(mSessionId, keyStatuses);
+ }
+
+ mKeyStatusMap->Update(keyStatuses);
+
+ if (EME_LOG_ENABLED()) {
+ nsAutoCString message(
+ nsPrintfCString("MediaKeySession[%p,'%s'] key statuses change {",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get()));
+ using IntegerType = typename std::underlying_type<MediaKeyStatus>::type;
+ for (const CDMCaps::KeyStatus& status : keyStatuses) {
+ message.Append(nsPrintfCString(" (%s,%s)", ToBase64(status.mId).get(),
+ MediaKeyStatusValues::strings[static_cast<IntegerType>(status.mStatus)].value));
+ }
+ message.Append(" }");
+ EME_LOG(message.get());
+ }
+}
+
+MediaKeyStatusMap*
+MediaKeySession::KeyStatuses() const
+{
+ return mKeyStatusMap;
+}
+
+// The user agent MUST thoroughly validate the Initialization Data before
+// passing it to the CDM. This includes verifying that the length and
+// values of fields are reasonable, verifying that values are within
+// reasonable limits, and stripping irrelevant, unsupported, or unknown
+// data or fields. It is RECOMMENDED that user agents pre-parse, sanitize,
+// and/or generate a fully sanitized version of the Initialization Data.
+// If the Initialization Data format specified by initDataType supports
+// multiple entries, the user agent SHOULD remove entries that are not
+// needed by the CDM. The user agent MUST NOT re-order entries within
+// the Initialization Data.
+static bool
+ValidateInitData(const nsTArray<uint8_t>& aInitData, const nsAString& aInitDataType)
+{
+ if (aInitDataType.LowerCaseEqualsLiteral("webm")) {
+ // WebM initData consists of a single keyId. Ensure it's of reasonable length.
+ return aInitData.Length() <= MAX_KEY_ID_LENGTH;
+ } else if (aInitDataType.LowerCaseEqualsLiteral("cenc")) {
+ // Limit initData to less than 64KB.
+ if (aInitData.Length() > MAX_CENC_INIT_DATA_LENGTH) {
+ return false;
+ }
+ std::vector<std::vector<uint8_t>> keyIds;
+ return ParseCENCInitData(aInitData.Elements(), aInitData.Length(), keyIds);
+ } else if (aInitDataType.LowerCaseEqualsLiteral("keyids")) {
+ if (aInitData.Length() > MAX_KEY_ID_LENGTH) {
+ return false;
+ }
+ // Ensure that init data matches the expected JSON format.
+ mozilla::dom::KeyIdsInitData keyIds;
+ nsString json;
+ nsDependentCSubstring raw(reinterpret_cast<const char*>(aInitData.Elements()), aInitData.Length());
+ if (NS_FAILED(nsContentUtils::ConvertStringFromEncoding(NS_LITERAL_CSTRING("UTF-8"), raw, json))) {
+ return false;
+ }
+ if (!keyIds.Init(json)) {
+ return false;
+ }
+ if (keyIds.mKids.Length() == 0) {
+ return false;
+ }
+ for (const auto& kid : keyIds.mKids) {
+ if (kid.IsEmpty()) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+// Generates a license request based on the initData. A message of type
+// "license-request" or "individualization-request" will always be queued
+// if the algorithm succeeds and the promise is resolved.
+already_AddRefed<Promise>
+MediaKeySession::GenerateRequest(const nsAString& aInitDataType,
+ const ArrayBufferViewOrArrayBuffer& aInitData,
+ ErrorResult& aRv)
+{
+ RefPtr<DetailedPromise> promise(MakePromise(aRv,
+ NS_LITERAL_CSTRING("MediaKeySession.generateRequest")));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // If this object is closed, return a promise rejected with an InvalidStateError.
+ if (IsClosed()) {
+ EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() failed, closed",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Session is closed in MediaKeySession.generateRequest()"));
+ return promise.forget();
+ }
+
+ // If this object's uninitialized value is false, return a promise rejected
+ // with an InvalidStateError.
+ if (!mUninitialized) {
+ EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() failed, uninitialized",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Session is already initialized in MediaKeySession.generateRequest()"));
+ return promise.forget();
+ }
+
+ // Let this object's uninitialized value be false.
+ mUninitialized = false;
+
+ // If initDataType is the empty string, return a promise rejected
+ // with a newly created TypeError.
+ if (aInitDataType.IsEmpty()) {
+ promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("Empty initDataType passed to MediaKeySession.generateRequest()"));
+ EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() failed, empty initDataType",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ return promise.forget();
+ }
+
+ // If initData is an empty array, return a promise rejected with
+ // a newly created TypeError.
+ nsTArray<uint8_t> data;
+ CopyArrayBufferViewOrArrayBufferData(aInitData, data);
+ if (data.IsEmpty()) {
+ promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("Empty initData passed to MediaKeySession.generateRequest()"));
+ EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() failed, empty initData",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ return promise.forget();
+ }
+
+ // If the Key System implementation represented by this object's
+ // cdm implementation value does not support initDataType as an
+ // Initialization Data Type, return a promise rejected with a
+ // NotSupportedError. String comparison is case-sensitive.
+ if (!MediaKeySystemAccess::KeySystemSupportsInitDataType(mKeySystem, aInitDataType)) {
+ promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+ NS_LITERAL_CSTRING("Unsupported initDataType passed to MediaKeySession.generateRequest()"));
+ EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() failed, unsupported initDataType",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ return promise.forget();
+ }
+
+ // Let init data be a copy of the contents of the initData parameter.
+ // Note: Handled by the CopyArrayBufferViewOrArrayBufferData call above.
+
+ // Let session type be this object's session type.
+
+ // Let promise be a new promise.
+
+ // Run the following steps in parallel:
+
+ // If the init data is not valid for initDataType, reject promise with
+ // a newly created TypeError.
+ if (!ValidateInitData(data, aInitDataType)) {
+ // If the preceding step failed, reject promise with a newly created TypeError.
+ promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("initData sanitization failed in MediaKeySession.generateRequest()"));
+ EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() initData sanitization failed",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ return promise.forget();
+ }
+
+ // Let sanitized init data be a validated and sanitized version of init data.
+
+ // If sanitized init data is empty, reject promise with a NotSupportedError.
+
+ // Note: Remaining steps of generateRequest method continue in CDM.
+
+ // Convert initData to base64 for easier logging.
+ // Note: CreateSession() Move()s the data out of the array, so we have
+ // to copy it here.
+ nsAutoCString base64InitData(ToBase64(data));
+ PromiseId pid = mKeys->StorePromise(promise);
+ mKeys->ConnectPendingPromiseIdWithToken(pid, Token());
+ mKeys->GetCDMProxy()->CreateSession(Token(),
+ mSessionType,
+ pid,
+ aInitDataType, data);
+
+ EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() sent, "
+ "promiseId=%d initData(base64)='%s' initDataType='%s'",
+ this,
+ NS_ConvertUTF16toUTF8(mSessionId).get(),
+ pid,
+ base64InitData.get(),
+ NS_ConvertUTF16toUTF8(aInitDataType).get());
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+MediaKeySession::Load(const nsAString& aSessionId, ErrorResult& aRv)
+{
+ RefPtr<DetailedPromise> promise(MakePromise(aRv,
+ NS_LITERAL_CSTRING("MediaKeySession.load")));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // 1. If this object is closed, return a promise rejected with an InvalidStateError.
+ if (IsClosed()) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Session is closed in MediaKeySession.load()"));
+ EME_LOG("MediaKeySession[%p,'%s'] Load() failed, closed",
+ this, NS_ConvertUTF16toUTF8(aSessionId).get());
+ return promise.forget();
+ }
+
+ // 2.If this object's uninitialized value is false, return a promise rejected
+ // with an InvalidStateError.
+ if (!mUninitialized) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Session is already initialized in MediaKeySession.load()"));
+ EME_LOG("MediaKeySession[%p,'%s'] Load() failed, uninitialized",
+ this, NS_ConvertUTF16toUTF8(aSessionId).get());
+ return promise.forget();
+ }
+
+ // 3.Let this object's uninitialized value be false.
+ mUninitialized = false;
+
+ // 4. If sessionId is the empty string, return a promise rejected with a newly created TypeError.
+ if (aSessionId.IsEmpty()) {
+ promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("Trying to load a session with empty session ID"));
+ // "The sessionId parameter is empty."
+ EME_LOG("MediaKeySession[%p,''] Load() failed, no sessionId", this);
+ return promise.forget();
+ }
+
+ // 5. If the result of running the Is persistent session type? algorithm
+ // on this object's session type is false, return a promise rejected with
+ // a newly created TypeError.
+ if (mSessionType == MediaKeySessionType::Temporary) {
+ promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("Trying to load() into a non-persistent session"));
+ EME_LOG("MediaKeySession[%p,''] Load() failed, can't load in a non-persistent session", this);
+ return promise.forget();
+ }
+
+ // Note: We don't support persistent sessions in any keysystem, so all calls
+ // to Load() should reject with a TypeError in the preceding check. Omitting
+ // implementing the rest of the specified MediaKeySession::Load() algorithm.
+
+ // We now know the sessionId being loaded into this session. Remove the
+ // session from its owning MediaKey's set of sessions awaiting a sessionId.
+ RefPtr<MediaKeySession> session(mKeys->GetPendingSession(Token()));
+ MOZ_ASSERT(session == this, "Session should be awaiting id on its own token");
+
+ // Associate with the known sessionId.
+ SetSessionId(aSessionId);
+
+ PromiseId pid = mKeys->StorePromise(promise);
+ mKeys->GetCDMProxy()->LoadSession(pid, aSessionId);
+
+ EME_LOG("MediaKeySession[%p,'%s'] Load() sent to CDM, promiseId=%d",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get(), pid);
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+MediaKeySession::Update(const ArrayBufferViewOrArrayBuffer& aResponse, ErrorResult& aRv)
+{
+ RefPtr<DetailedPromise> promise(MakePromise(aRv,
+ NS_LITERAL_CSTRING("MediaKeySession.update")));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (!IsCallable()) {
+ // If this object's callable value is false, return a promise rejected
+ // with a new DOMException whose name is InvalidStateError.
+ EME_LOG("MediaKeySession[%p,''] Update() called before sessionId set by CDM", this);
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("MediaKeySession.Update() called before sessionId set by CDM"));
+ return promise.forget();
+ }
+
+ nsTArray<uint8_t> data;
+ if (IsClosed() || !mKeys->GetCDMProxy()) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Session is closed or was not properly initialized"));
+ EME_LOG("MediaKeySession[%p,'%s'] Update() failed, session is closed or was not properly initialised.",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ return promise.forget();
+ }
+ CopyArrayBufferViewOrArrayBufferData(aResponse, data);
+ if (data.IsEmpty()) {
+ promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("Empty response buffer passed to MediaKeySession.update()"));
+ EME_LOG("MediaKeySession[%p,'%s'] Update() failed, empty response buffer",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ return promise.forget();
+ }
+
+
+ // Convert response to base64 for easier logging.
+ // Note: UpdateSession() Move()s the data out of the array, so we have
+ // to copy it here.
+ nsAutoCString base64Response(ToBase64(data));
+
+ PromiseId pid = mKeys->StorePromise(promise);
+ mKeys->GetCDMProxy()->UpdateSession(mSessionId,
+ pid,
+ data);
+
+ EME_LOG("MediaKeySession[%p,'%s'] Update() sent to CDM, "
+ "promiseId=%d Response(base64)='%s'",
+ this,
+ NS_ConvertUTF16toUTF8(mSessionId).get(),
+ pid,
+ base64Response.get());
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+MediaKeySession::Close(ErrorResult& aRv)
+{
+ RefPtr<DetailedPromise> promise(MakePromise(aRv,
+ NS_LITERAL_CSTRING("MediaKeySession.close")));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ // 1. Let session be the associated MediaKeySession object.
+ // 2. If session is closed, return a resolved promise.
+ if (IsClosed()) {
+ EME_LOG("MediaKeySession[%p,'%s'] Close() already closed",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+ // 3. If session's callable value is false, return a promise rejected
+ // with an InvalidStateError.
+ if (!IsCallable()) {
+ EME_LOG("MediaKeySession[%p,''] Close() called before sessionId set by CDM", this);
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("MediaKeySession.Close() called before sessionId set by CDM"));
+ return promise.forget();
+ }
+ if (!mKeys->GetCDMProxy()) {
+ EME_LOG("MediaKeySession[%p,'%s'] Close() null CDMProxy",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("MediaKeySession.Close() lost reference to CDM"));
+ return promise.forget();
+ }
+ // 4. Let promise be a new promise.
+ PromiseId pid = mKeys->StorePromise(promise);
+ // 5. Run the following steps in parallel:
+ // 5.1 Let cdm be the CDM instance represented by session's cdm instance value.
+ // 5.2 Use cdm to close the session associated with session.
+ mKeys->GetCDMProxy()->CloseSession(mSessionId, pid);
+
+ EME_LOG("MediaKeySession[%p,'%s'] Close() sent to CDM, promiseId=%d",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get(), pid);
+
+ // Session Closed algorithm is run when CDM causes us to run OnSessionClosed().
+
+ // 6. Return promise.
+ return promise.forget();
+}
+
+void
+MediaKeySession::OnClosed()
+{
+ if (IsClosed()) {
+ return;
+ }
+ EME_LOG("MediaKeySession[%p,'%s'] session close operation complete.",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ mIsClosed = true;
+ mKeys->OnSessionClosed(this);
+ mKeys = nullptr;
+ mClosed->MaybeResolveWithUndefined();
+}
+
+bool
+MediaKeySession::IsClosed() const
+{
+ return mIsClosed;
+}
+
+already_AddRefed<Promise>
+MediaKeySession::Remove(ErrorResult& aRv)
+{
+ RefPtr<DetailedPromise> promise(MakePromise(aRv,
+ NS_LITERAL_CSTRING("MediaKeySession.remove")));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ if (!IsCallable()) {
+ // If this object's callable value is false, return a promise rejected
+ // with a new DOMException whose name is InvalidStateError.
+ EME_LOG("MediaKeySession[%p,''] Remove() called before sessionId set by CDM", this);
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("MediaKeySession.Remove() called before sessionId set by CDM"));
+ return promise.forget();
+ }
+ if (mSessionType != MediaKeySessionType::Persistent_license) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR,
+ NS_LITERAL_CSTRING("Calling MediaKeySession.remove() on non-persistent session"));
+ // "The operation is not supported on session type sessions."
+ EME_LOG("MediaKeySession[%p,'%s'] Remove() failed, sesion not persisrtent.",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ return promise.forget();
+ }
+ if (IsClosed() || !mKeys->GetCDMProxy()) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("MediaKeySesison.remove() called but session is not active"));
+ // "The session is closed."
+ EME_LOG("MediaKeySession[%p,'%s'] Remove() failed, already session closed.",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ return promise.forget();
+ }
+ PromiseId pid = mKeys->StorePromise(promise);
+ mKeys->GetCDMProxy()->RemoveSession(mSessionId, pid);
+ EME_LOG("MediaKeySession[%p,'%s'] Remove() sent to CDM, promiseId=%d.",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get(), pid);
+
+ return promise.forget();
+}
+
+void
+MediaKeySession::DispatchKeyMessage(MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage)
+{
+ if (EME_LOG_ENABLED()) {
+ EME_LOG("MediaKeySession[%p,'%s'] DispatchKeyMessage() type=%s message(base64)='%s'",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get(),
+ MediaKeyMessageTypeValues::strings[uint32_t(aMessageType)].value,
+ ToBase64(aMessage).get());
+ }
+
+ RefPtr<MediaKeyMessageEvent> event(
+ MediaKeyMessageEvent::Constructor(this, aMessageType, aMessage));
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, event);
+ asyncDispatcher->PostDOMEvent();
+}
+
+void
+MediaKeySession::DispatchKeyError(uint32_t aSystemCode)
+{
+ EME_LOG("MediaKeySession[%p,'%s'] DispatchKeyError() systemCode=%u.",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get(), aSystemCode);
+
+ RefPtr<MediaKeyError> event(new MediaKeyError(this, aSystemCode));
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, event);
+ asyncDispatcher->PostDOMEvent();
+}
+
+void
+MediaKeySession::DispatchKeyStatusesChange()
+{
+ if (IsClosed()) {
+ return;
+ }
+
+ UpdateKeyStatusMap();
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, NS_LITERAL_STRING("keystatuseschange"), false);
+ asyncDispatcher->PostDOMEvent();
+}
+
+uint32_t
+MediaKeySession::Token() const
+{
+ return mToken;
+}
+
+already_AddRefed<DetailedPromise>
+MediaKeySession::MakePromise(ErrorResult& aRv, const nsACString& aName)
+{
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ if (!global) {
+ NS_WARNING("Passed non-global to MediaKeys ctor!");
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ return DetailedPromise::Create(global, aRv, aName);
+}
+
+void
+MediaKeySession::SetExpiration(double aExpiration)
+{
+ EME_LOG("MediaKeySession[%p,'%s'] SetExpiry(%lf)",
+ this,
+ NS_ConvertUTF16toUTF8(mSessionId).get(),
+ aExpiration);
+ mExpiration = aExpiration;
+}
+
+EventHandlerNonNull*
+MediaKeySession::GetOnkeystatuseschange()
+{
+ return GetEventHandler(nsGkAtoms::onkeystatuseschange, EmptyString());
+}
+
+void
+MediaKeySession::SetOnkeystatuseschange(EventHandlerNonNull* aCallback)
+{
+ SetEventHandler(nsGkAtoms::onkeystatuseschange, EmptyString(), aCallback);
+}
+
+EventHandlerNonNull*
+MediaKeySession::GetOnmessage()
+{
+ return GetEventHandler(nsGkAtoms::onmessage, EmptyString());
+}
+
+void
+MediaKeySession::SetOnmessage(EventHandlerNonNull* aCallback)
+{
+ SetEventHandler(nsGkAtoms::onmessage, EmptyString(), aCallback);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/MediaKeySession.h b/dom/media/eme/MediaKeySession.h
new file mode 100644
index 000000000..6b7137046
--- /dev/null
+++ b/dom/media/eme/MediaKeySession.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef mozilla_dom_MediaKeySession_h
+#define mozilla_dom_MediaKeySession_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/dom/Date.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/DetailedPromise.h"
+#include "mozilla/dom/MediaKeySessionBinding.h"
+#include "mozilla/dom/MediaKeysBinding.h"
+#include "mozilla/dom/MediaKeyMessageEventBinding.h"
+
+struct JSContext;
+
+namespace mozilla {
+namespace dom {
+
+class ArrayBufferViewOrArrayBuffer;
+class MediaKeyError;
+class MediaKeyStatusMap;
+
+class MediaKeySession final : public DOMEventTargetHelper
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaKeySession,
+ DOMEventTargetHelper)
+public:
+ MediaKeySession(JSContext* aCx,
+ nsPIDOMWindowInner* aParent,
+ MediaKeys* aKeys,
+ const nsAString& aKeySystem,
+ MediaKeySessionType aSessionType,
+ ErrorResult& aRv);
+
+ void SetSessionId(const nsAString& aSessionId);
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // Mark this as resultNotAddRefed to return raw pointers
+ MediaKeyError* GetError() const;
+
+ MediaKeyStatusMap* KeyStatuses() const;
+
+ void GetKeySystem(nsString& aRetval) const;
+
+ 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
+ double Expiration() const;
+
+ Promise* Closed() const;
+
+ already_AddRefed<Promise> GenerateRequest(const nsAString& aInitDataType,
+ const ArrayBufferViewOrArrayBuffer& aInitData,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> Load(const nsAString& aSessionId,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> Update(const ArrayBufferViewOrArrayBuffer& response,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> Close(ErrorResult& aRv);
+
+ already_AddRefed<Promise> Remove(ErrorResult& aRv);
+
+ void DispatchKeyMessage(MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage);
+
+ void DispatchKeyError(uint32_t system_code);
+
+ void DispatchKeyStatusesChange();
+
+ void OnClosed();
+
+ bool IsClosed() const;
+
+ void SetExpiration(double aExpiry);
+
+ mozilla::dom::EventHandlerNonNull* GetOnkeystatuseschange();
+ void SetOnkeystatuseschange(mozilla::dom::EventHandlerNonNull* aCallback);
+
+ mozilla::dom::EventHandlerNonNull* GetOnmessage();
+ void SetOnmessage(mozilla::dom::EventHandlerNonNull* aCallback);
+
+ // Process-unique identifier.
+ uint32_t Token() const;
+
+private:
+ ~MediaKeySession();
+
+ void UpdateKeyStatusMap();
+
+ bool IsCallable() const {
+ // The EME spec sets the "callable value" to true whenever the CDM sets
+ // the sessionId. When the session is initialized, sessionId is empty and
+ // callable is thus false.
+ return !mSessionId.IsEmpty();
+ }
+
+ already_AddRefed<DetailedPromise> MakePromise(ErrorResult& aRv,
+ const nsACString& aName);
+
+ RefPtr<DetailedPromise> mClosed;
+
+ RefPtr<MediaKeyError> mMediaKeyError;
+ RefPtr<MediaKeys> mKeys;
+ const nsString mKeySystem;
+ nsString mSessionId;
+ const MediaKeySessionType mSessionType;
+ const uint32_t mToken;
+ bool mIsClosed;
+ bool mUninitialized;
+ RefPtr<MediaKeyStatusMap> mKeyStatusMap;
+ double mExpiration;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/eme/MediaKeyStatusMap.cpp b/dom/media/eme/MediaKeyStatusMap.cpp
new file mode 100644
index 000000000..677fd0db2
--- /dev/null
+++ b/dom/media/eme/MediaKeyStatusMap.cpp
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 8; 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 "mozilla/dom/MediaKeyStatusMap.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/EMEUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeyStatusMap)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeyStatusMap)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeyStatusMap)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeyStatusMap, mParent)
+
+MediaKeyStatusMap::MediaKeyStatusMap(nsPIDOMWindowInner* aParent)
+ : mParent(aParent)
+{
+}
+
+MediaKeyStatusMap::~MediaKeyStatusMap()
+{
+}
+
+JSObject*
+MediaKeyStatusMap::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaKeyStatusMapBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner*
+MediaKeyStatusMap::GetParentObject() const
+{
+ return mParent;
+}
+
+void
+MediaKeyStatusMap::Get(JSContext* aCx,
+ const ArrayBufferViewOrArrayBuffer& aKey,
+ JS::MutableHandle<JS::Value> aOutValue,
+ ErrorResult& aOutRv) const
+{
+ ArrayData keyId = GetArrayBufferViewOrArrayBufferData(aKey);
+ if (!keyId.IsValid()) {
+ aOutValue.setUndefined();
+ return;
+ }
+ for (const KeyStatus& status : mStatuses) {
+ if (keyId == status.mKeyId) {
+ bool ok = ToJSValue(aCx, status.mStatus, aOutValue);
+ if (!ok) {
+ aOutRv.NoteJSContextException(aCx);
+ }
+ return;
+ }
+ }
+ aOutValue.setUndefined();
+}
+
+bool
+MediaKeyStatusMap::Has(const ArrayBufferViewOrArrayBuffer& aKey) const
+{
+ ArrayData keyId = GetArrayBufferViewOrArrayBufferData(aKey);
+ if (!keyId.IsValid()) {
+ return false;
+ }
+
+ for (const KeyStatus& status : mStatuses) {
+ if (keyId == status.mKeyId) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+uint32_t
+MediaKeyStatusMap::GetIterableLength() const
+{
+ return mStatuses.Length();
+}
+
+TypedArrayCreator<ArrayBuffer>
+MediaKeyStatusMap::GetKeyAtIndex(uint32_t aIndex) const
+{
+ MOZ_ASSERT(aIndex < GetIterableLength());
+ return TypedArrayCreator<ArrayBuffer>(mStatuses[aIndex].mKeyId);
+}
+
+MediaKeyStatus
+MediaKeyStatusMap::GetValueAtIndex(uint32_t aIndex) const
+{
+ MOZ_ASSERT(aIndex < GetIterableLength());
+ return mStatuses[aIndex].mStatus;
+}
+
+uint32_t
+MediaKeyStatusMap::Size() const
+{
+ return mStatuses.Length();
+}
+
+void
+MediaKeyStatusMap::Update(const nsTArray<CDMCaps::KeyStatus>& aKeys)
+{
+ mStatuses.Clear();
+ for (const auto& key : aKeys) {
+ mStatuses.InsertElementSorted(KeyStatus(key.mId, key.mStatus));
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/MediaKeyStatusMap.h b/dom/media/eme/MediaKeyStatusMap.h
new file mode 100644
index 000000000..8bfbd4d07
--- /dev/null
+++ b/dom/media/eme/MediaKeyStatusMap.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef mozilla_dom_MediaKeyStatuses_h
+#define mozilla_dom_MediaKeyStatuses_h
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Attributes.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/MediaKeyStatusMapBinding.h"
+#include "mozilla/CDMCaps.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+class ArrayBufferViewOrArrayBuffer;
+
+// The MediaKeyStatusMap WebIDL interface; maps a keyId to its status.
+// Note that the underlying "map" is stored in an array, since we assume
+// that a MediaKeySession won't have many key statuses to report.
+class MediaKeyStatusMap final : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaKeyStatusMap)
+
+public:
+ explicit MediaKeyStatusMap(nsPIDOMWindowInner* aParent);
+
+protected:
+ ~MediaKeyStatusMap();
+
+public:
+ nsPIDOMWindowInner* GetParentObject() const;
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void Get(JSContext* aCx,
+ const ArrayBufferViewOrArrayBuffer& aKey,
+ JS::MutableHandle<JS::Value> aOutValue,
+ ErrorResult& aOutRv) const;
+ bool Has(const ArrayBufferViewOrArrayBuffer& aKey) const;
+ uint32_t Size() const;
+
+ uint32_t GetIterableLength() const;
+ TypedArrayCreator<ArrayBuffer> GetKeyAtIndex(uint32_t aIndex) const;
+ MediaKeyStatus GetValueAtIndex(uint32_t aIndex) const;
+
+ void Update(const nsTArray<CDMCaps::KeyStatus>& keys);
+
+private:
+
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+
+ struct KeyStatus {
+ KeyStatus(const nsTArray<uint8_t>& aKeyId,
+ MediaKeyStatus aStatus)
+ : mKeyId(aKeyId)
+ , mStatus(aStatus)
+ {
+ }
+ bool operator== (const KeyStatus& aOther) const {
+ return aOther.mKeyId == mKeyId;
+ }
+ bool operator<(const KeyStatus& aOther) const {
+ // Copy chromium and compare keys' bytes.
+ // Update once https://github.com/w3c/encrypted-media/issues/69
+ // is resolved.
+ const nsTArray<uint8_t>& other = aOther.mKeyId;
+ const nsTArray<uint8_t>& self = mKeyId;
+ size_t length = std::min<size_t>(other.Length(), self.Length());
+ int cmp = memcmp(self.Elements(), other.Elements(), length);
+ if (cmp != 0) {
+ return cmp < 0;
+ }
+ return self.Length() <= other.Length();
+ }
+ nsTArray<uint8_t> mKeyId;
+ MediaKeyStatus mStatus;
+ };
+
+ nsTArray<KeyStatus> mStatuses;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/eme/MediaKeySystemAccess.cpp b/dom/media/eme/MediaKeySystemAccess.cpp
new file mode 100644
index 000000000..c65c57ed0
--- /dev/null
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -0,0 +1,1042 @@
+/* -*- 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 "mozilla/dom/MediaKeySystemAccess.h"
+#include "mozilla/dom/MediaKeySystemAccessBinding.h"
+#include "mozilla/Preferences.h"
+#include "MediaPrefs.h"
+#include "nsContentTypeParser.h"
+#ifdef MOZ_FMP4
+#include "MP4Decoder.h"
+#endif
+#ifdef XP_WIN
+#include "mozilla/WindowsVersion.h"
+#include "WMFDecoderModule.h"
+#endif
+#include "nsContentCID.h"
+#include "nsServiceManagerUtils.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "VideoUtils.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+#include "mozilla/EMEUtils.h"
+#include "GMPUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsXULAppAPI.h"
+#include "gmp-audio-decode.h"
+#include "gmp-video-decode.h"
+#include "DecoderDoctorDiagnostics.h"
+#include "WebMDecoder.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/dom/MediaSource.h"
+#include "DecoderTraits.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess,
+ mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccess)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+MediaKeySystemAccess::MediaKeySystemAccess(nsPIDOMWindowInner* aParent,
+ const nsAString& aKeySystem,
+ const MediaKeySystemConfiguration& aConfig)
+ : mParent(aParent)
+ , mKeySystem(aKeySystem)
+ , mConfig(aConfig)
+{
+}
+
+MediaKeySystemAccess::~MediaKeySystemAccess()
+{
+}
+
+JSObject*
+MediaKeySystemAccess::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaKeySystemAccessBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner*
+MediaKeySystemAccess::GetParentObject() const
+{
+ return mParent;
+}
+
+void
+MediaKeySystemAccess::GetKeySystem(nsString& aOutKeySystem) const
+{
+ aOutKeySystem.Assign(mKeySystem);
+}
+
+void
+MediaKeySystemAccess::GetConfiguration(MediaKeySystemConfiguration& aConfig)
+{
+ aConfig = mConfig;
+}
+
+already_AddRefed<Promise>
+MediaKeySystemAccess::CreateMediaKeys(ErrorResult& aRv)
+{
+ RefPtr<MediaKeys> keys(new MediaKeys(mParent,
+ mKeySystem,
+ mConfig));
+ return keys->Init(aRv);
+}
+
+static bool
+HavePluginForKeySystem(const nsCString& aKeySystem)
+{
+ bool havePlugin = HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
+ { aKeySystem });
+ return havePlugin;
+}
+
+static MediaKeySystemStatus
+EnsureCDMInstalled(const nsAString& aKeySystem,
+ nsACString& aOutMessage)
+{
+ if (!HavePluginForKeySystem(NS_ConvertUTF16toUTF8(aKeySystem))) {
+ aOutMessage = NS_LITERAL_CSTRING("CDM is not installed");
+ return MediaKeySystemStatus::Cdm_not_installed;
+ }
+
+ return MediaKeySystemStatus::Available;
+}
+
+/* static */
+MediaKeySystemStatus
+MediaKeySystemAccess::GetKeySystemStatus(const nsAString& aKeySystem,
+ nsACString& aOutMessage)
+{
+ MOZ_ASSERT(MediaPrefs::EMEEnabled() || IsClearkeyKeySystem(aKeySystem));
+
+ if (IsClearkeyKeySystem(aKeySystem)) {
+ return EnsureCDMInstalled(aKeySystem, aOutMessage);
+ }
+
+ if (IsWidevineKeySystem(aKeySystem)) {
+ if (Preferences::GetBool("media.gmp-widevinecdm.visible", false)) {
+ if (!Preferences::GetBool("media.gmp-widevinecdm.enabled", false)) {
+ aOutMessage = NS_LITERAL_CSTRING("Widevine EME disabled");
+ return MediaKeySystemStatus::Cdm_disabled;
+ }
+ return EnsureCDMInstalled(aKeySystem, aOutMessage);
+ }
+ }
+
+ return MediaKeySystemStatus::Cdm_not_supported;
+}
+
+typedef nsCString EMECodecString;
+
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_AAC, "aac");
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_OPUS, "opus");
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VORBIS, "vorbis");
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_H264, "h264");
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VP8, "vp8");
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VP9, "vp9");
+
+EMECodecString
+ToEMEAPICodecString(const nsString& aCodec)
+{
+ if (IsAACCodecString(aCodec)) {
+ return EME_CODEC_AAC;
+ }
+ if (aCodec.EqualsLiteral("opus")) {
+ return EME_CODEC_OPUS;
+ }
+ if (aCodec.EqualsLiteral("vorbis")) {
+ return EME_CODEC_VORBIS;
+ }
+ if (IsH264CodecString(aCodec)) {
+ return EME_CODEC_H264;
+ }
+ if (IsVP8CodecString(aCodec)) {
+ return EME_CODEC_VP8;
+ }
+ if (IsVP9CodecString(aCodec)) {
+ return EME_CODEC_VP9;
+ }
+ return EmptyCString();
+}
+
+// A codec can be decrypted-and-decoded by the CDM, or only decrypted
+// by the CDM and decoded by Gecko. Not both.
+struct KeySystemContainerSupport
+{
+ bool IsSupported() const
+ {
+ return !mCodecsDecoded.IsEmpty() || !mCodecsDecrypted.IsEmpty();
+ }
+
+ // CDM decrypts and decodes using a DRM robust decoder, and passes decoded
+ // samples back to Gecko for rendering.
+ bool DecryptsAndDecodes(EMECodecString aCodec) const
+ {
+ return mCodecsDecoded.Contains(aCodec);
+ }
+
+ // CDM decrypts and passes the decrypted samples back to Gecko for decoding.
+ bool Decrypts(EMECodecString aCodec) const
+ {
+ return mCodecsDecrypted.Contains(aCodec);
+ }
+
+ void SetCanDecryptAndDecode(EMECodecString aCodec)
+ {
+ // Can't both decrypt and decrypt-and-decode a codec.
+ MOZ_ASSERT(!Decrypts(aCodec));
+ // Prevent duplicates.
+ MOZ_ASSERT(!DecryptsAndDecodes(aCodec));
+ mCodecsDecoded.AppendElement(aCodec);
+ }
+
+ void SetCanDecrypt(EMECodecString aCodec)
+ {
+ // Prevent duplicates.
+ MOZ_ASSERT(!Decrypts(aCodec));
+ // Can't both decrypt and decrypt-and-decode a codec.
+ MOZ_ASSERT(!DecryptsAndDecodes(aCodec));
+ mCodecsDecrypted.AppendElement(aCodec);
+ }
+
+private:
+ nsTArray<EMECodecString> mCodecsDecoded;
+ nsTArray<EMECodecString> mCodecsDecrypted;
+};
+
+enum class KeySystemFeatureSupport
+{
+ Prohibited = 1,
+ Requestable = 2,
+ Required = 3,
+};
+
+struct KeySystemConfig
+{
+ nsString mKeySystem;
+ nsTArray<nsString> mInitDataTypes;
+ KeySystemFeatureSupport mPersistentState = KeySystemFeatureSupport::Prohibited;
+ KeySystemFeatureSupport mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
+ nsTArray<MediaKeySessionType> mSessionTypes;
+ nsTArray<nsString> mVideoRobustness;
+ nsTArray<nsString> mAudioRobustness;
+ KeySystemContainerSupport mMP4;
+ KeySystemContainerSupport mWebM;
+};
+
+static nsTArray<KeySystemConfig>
+GetSupportedKeySystems()
+{
+ nsTArray<KeySystemConfig> keySystemConfigs;
+
+ {
+ if (HavePluginForKeySystem(kEMEKeySystemClearkey)) {
+ KeySystemConfig clearkey;
+ clearkey.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemClearkey);
+ clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
+ clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("keyids"));
+ clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("webm"));
+ clearkey.mPersistentState = KeySystemFeatureSupport::Requestable;
+ clearkey.mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
+ clearkey.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
+ if (MediaPrefs::ClearKeyPersistentLicenseEnabled()) {
+ clearkey.mSessionTypes.AppendElement(MediaKeySessionType::Persistent_license);
+ }
+#if defined(XP_WIN)
+ // Clearkey CDM uses WMF decoders on Windows.
+ if (WMFDecoderModule::HasAAC()) {
+ clearkey.mMP4.SetCanDecryptAndDecode(EME_CODEC_AAC);
+ } else {
+ clearkey.mMP4.SetCanDecrypt(EME_CODEC_AAC);
+ }
+ if (WMFDecoderModule::HasH264()) {
+ clearkey.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
+ } else {
+ clearkey.mMP4.SetCanDecrypt(EME_CODEC_H264);
+ }
+#else
+ clearkey.mMP4.SetCanDecrypt(EME_CODEC_AAC);
+ clearkey.mMP4.SetCanDecrypt(EME_CODEC_H264);
+#endif
+ clearkey.mWebM.SetCanDecrypt(EME_CODEC_VORBIS);
+ clearkey.mWebM.SetCanDecrypt(EME_CODEC_OPUS);
+ clearkey.mWebM.SetCanDecrypt(EME_CODEC_VP8);
+ clearkey.mWebM.SetCanDecrypt(EME_CODEC_VP9);
+ keySystemConfigs.AppendElement(Move(clearkey));
+ }
+ }
+ {
+ if (HavePluginForKeySystem(kEMEKeySystemWidevine)) {
+ KeySystemConfig widevine;
+ widevine.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemWidevine);
+ widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
+ widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("keyids"));
+ widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("webm"));
+ widevine.mPersistentState = KeySystemFeatureSupport::Requestable;
+ widevine.mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
+ widevine.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
+ widevine.mAudioRobustness.AppendElement(NS_LITERAL_STRING("SW_SECURE_CRYPTO"));
+ widevine.mVideoRobustness.AppendElement(NS_LITERAL_STRING("SW_SECURE_DECODE"));
+#if defined(XP_WIN)
+ // Widevine CDM doesn't include an AAC decoder. So if WMF can't
+ // decode AAC, and a codec wasn't specified, be conservative
+ // and reject the MediaKeys request, since our policy is to prevent
+ // the Adobe GMP's unencrypted AAC decoding path being used to
+ // decode content decrypted by the Widevine CDM.
+ if (WMFDecoderModule::HasAAC()) {
+ widevine.mMP4.SetCanDecrypt(EME_CODEC_AAC);
+ }
+#endif
+
+ widevine.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
+ widevine.mWebM.SetCanDecrypt(EME_CODEC_VORBIS);
+ widevine.mWebM.SetCanDecrypt(EME_CODEC_OPUS);
+ widevine.mWebM.SetCanDecryptAndDecode(EME_CODEC_VP8);
+ widevine.mWebM.SetCanDecryptAndDecode(EME_CODEC_VP9);
+ keySystemConfigs.AppendElement(Move(widevine));
+ }
+ }
+
+ return keySystemConfigs;
+}
+
+static bool
+GetKeySystemConfig(const nsAString& aKeySystem, KeySystemConfig& aOutKeySystemConfig)
+{
+ for (auto&& config : GetSupportedKeySystems()) {
+ if (config.mKeySystem.Equals(aKeySystem)) {
+ aOutKeySystemConfig = mozilla::Move(config);
+ return true;
+ }
+ }
+ // No matching key system found.
+ return false;
+}
+
+/* static */
+bool
+MediaKeySystemAccess::KeySystemSupportsInitDataType(const nsAString& aKeySystem,
+ const nsAString& aInitDataType)
+{
+ KeySystemConfig implementation;
+ return GetKeySystemConfig(aKeySystem, implementation) &&
+ implementation.mInitDataTypes.Contains(aInitDataType);
+}
+
+enum CodecType
+{
+ Audio,
+ Video,
+ Invalid
+};
+
+static bool
+CanDecryptAndDecode(const nsString& aKeySystem,
+ const nsString& aContentType,
+ CodecType aCodecType,
+ const KeySystemContainerSupport& aContainerSupport,
+ const nsTArray<EMECodecString>& aCodecs,
+ DecoderDoctorDiagnostics* aDiagnostics)
+{
+ MOZ_ASSERT(aCodecType != Invalid);
+ for (const EMECodecString& codec : aCodecs) {
+ MOZ_ASSERT(!codec.IsEmpty());
+
+ if (aContainerSupport.DecryptsAndDecodes(codec)) {
+ // GMP can decrypt-and-decode this codec.
+ continue;
+ }
+
+ if (aContainerSupport.Decrypts(codec) &&
+ NS_SUCCEEDED(MediaSource::IsTypeSupported(aContentType, aDiagnostics))) {
+ // GMP can decrypt and is allowed to return compressed samples to
+ // Gecko to decode, and Gecko has a decoder.
+ continue;
+ }
+
+ // Neither the GMP nor Gecko can both decrypt and decode. We don't
+ // support this codec.
+
+#if defined(XP_WIN)
+ // Widevine CDM doesn't include an AAC decoder. So if WMF can't
+ // decode AAC, and a codec wasn't specified, be conservative
+ // and reject the MediaKeys request, since our policy is to prevent
+ // the Adobe GMP's unencrypted AAC decoding path being used to
+ // decode content decrypted by the Widevine CDM.
+ if (codec == EME_CODEC_AAC &&
+ IsWidevineKeySystem(aKeySystem) &&
+ !WMFDecoderModule::HasAAC()) {
+ if (aDiagnostics) {
+ aDiagnostics->SetKeySystemIssue(
+ DecoderDoctorDiagnostics::eWidevineWithNoWMF);
+ }
+ }
+#endif
+ return false;
+ }
+ return true;
+}
+
+static bool
+ToSessionType(const nsAString& aSessionType, MediaKeySessionType& aOutType)
+{
+ using MediaKeySessionTypeValues::strings;
+ const char* temporary =
+ strings[static_cast<uint32_t>(MediaKeySessionType::Temporary)].value;
+ if (aSessionType.EqualsASCII(temporary)) {
+ aOutType = MediaKeySessionType::Temporary;
+ return true;
+ }
+ const char* persistentLicense =
+ strings[static_cast<uint32_t>(MediaKeySessionType::Persistent_license)].value;
+ if (aSessionType.EqualsASCII(persistentLicense)) {
+ aOutType = MediaKeySessionType::Persistent_license;
+ return true;
+ }
+ return false;
+}
+
+// 5.2.1 Is persistent session type?
+static bool
+IsPersistentSessionType(MediaKeySessionType aSessionType)
+{
+ return aSessionType == MediaKeySessionType::Persistent_license;
+}
+
+CodecType
+GetMajorType(const nsAString& aContentType)
+{
+ if (CaseInsensitiveFindInReadable(NS_LITERAL_STRING("audio/"), aContentType)) {
+ return Audio;
+ }
+ if (CaseInsensitiveFindInReadable(NS_LITERAL_STRING("video/"), aContentType)) {
+ return Video;
+ }
+ return Invalid;
+}
+
+static CodecType
+GetCodecType(const EMECodecString& aCodec)
+{
+ if (aCodec.Equals(EME_CODEC_AAC) ||
+ aCodec.Equals(EME_CODEC_OPUS) ||
+ aCodec.Equals(EME_CODEC_VORBIS)) {
+ return Audio;
+ }
+ if (aCodec.Equals(EME_CODEC_H264) ||
+ aCodec.Equals(EME_CODEC_VP8) ||
+ aCodec.Equals(EME_CODEC_VP9)) {
+ return Video;
+ }
+ return Invalid;
+}
+
+static bool
+AllCodecsOfType(const nsTArray<EMECodecString>& aCodecs, const CodecType aCodecType)
+{
+ for (const EMECodecString& codec : aCodecs) {
+ if (GetCodecType(codec) != aCodecType) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool
+IsParameterUnrecognized(const nsAString& aContentType)
+{
+ nsAutoString contentType(aContentType);
+ contentType.StripWhitespace();
+
+ nsTArray<nsString> params;
+ nsAString::const_iterator start, end, semicolon, equalSign;
+ contentType.BeginReading(start);
+ contentType.EndReading(end);
+ semicolon = start;
+ // Find any substring between ';' & '='.
+ while (semicolon != end) {
+ if (FindCharInReadable(';', semicolon, end)) {
+ equalSign = ++semicolon;
+ if (FindCharInReadable('=', equalSign, end)) {
+ params.AppendElement(Substring(semicolon, equalSign));
+ semicolon = equalSign;
+ }
+ }
+ }
+
+ for (auto param : params) {
+ if (!param.LowerCaseEqualsLiteral("codecs") &&
+ !param.LowerCaseEqualsLiteral("profiles")) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// 3.1.2.3 Get Supported Capabilities for Audio/Video Type
+static Sequence<MediaKeySystemMediaCapability>
+GetSupportedCapabilities(const CodecType aCodecType,
+ const nsTArray<MediaKeySystemMediaCapability>& aRequestedCapabilities,
+ const MediaKeySystemConfiguration& aPartialConfig,
+ const KeySystemConfig& aKeySystem,
+ DecoderDoctorDiagnostics* aDiagnostics)
+{
+ // Let local accumulated configuration be a local copy of partial configuration.
+ // (Note: It's not necessary for us to maintain a local copy, as we don't need
+ // to test whether capabilites from previous calls to this algorithm work with
+ // the capabilities currently being considered in this call. )
+
+ // Let supported media capabilities be an empty sequence of
+ // MediaKeySystemMediaCapability dictionaries.
+ Sequence<MediaKeySystemMediaCapability> supportedCapabilities;
+
+ // For each requested media capability in requested media capabilities:
+ for (const MediaKeySystemMediaCapability& capabilities : aRequestedCapabilities) {
+ // Let content type be requested media capability's contentType member.
+ const nsString& contentType = capabilities.mContentType;
+ // Let robustness be requested media capability's robustness member.
+ const nsString& robustness = capabilities.mRobustness;
+ // If content type is the empty string, return null.
+ if (contentType.IsEmpty()) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') rejected; "
+ "audio or video capability has empty contentType.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ return Sequence<MediaKeySystemMediaCapability>();
+ }
+ // If content type is an invalid or unrecognized MIME type, continue
+ // to the next iteration.
+ nsAutoString container;
+ nsTArray<nsString> codecStrings;
+ if (!ParseMIMETypeString(contentType, container, codecStrings)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "failed to parse contentType as MIME type.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+ bool invalid = false;
+ nsTArray<EMECodecString> codecs;
+ for (const nsString& codecString : codecStrings) {
+ EMECodecString emeCodec = ToEMEAPICodecString(codecString);
+ if (emeCodec.IsEmpty()) {
+ invalid = true;
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "'%s' is an invalid codec string.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(codecString).get());
+ break;
+ }
+ codecs.AppendElement(emeCodec);
+ }
+ if (invalid) {
+ continue;
+ }
+
+ // If the user agent does not support container, continue to the next iteration.
+ // The case-sensitivity of string comparisons is determined by the appropriate RFC.
+ // (Note: Per RFC 6838 [RFC6838], "Both top-level type and subtype names are
+ // case-insensitive."'. We're using nsContentTypeParser and that is
+ // case-insensitive and converts all its parameter outputs to lower case.)
+ NS_ConvertUTF16toUTF8 container_utf8(container);
+ const bool isMP4 = DecoderTraits::IsMP4TypeAndEnabled(container_utf8, aDiagnostics);
+ if (isMP4 && !aKeySystem.mMP4.IsSupported()) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "MP4 requested but unsupported.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+ const bool isWebM = DecoderTraits::IsWebMTypeAndEnabled(container_utf8);
+ if (isWebM && !aKeySystem.mWebM.IsSupported()) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "WebM requested but unsupported.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+ if (!isMP4 && !isWebM) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "Unsupported or unrecognized container requested.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+
+ // Let parameters be the RFC 6381[RFC6381] parameters, if any, specified by
+ // content type.
+ // If the user agent does not recognize one or more parameters, continue to
+ // the next iteration.
+ if (IsParameterUnrecognized(contentType)) {
+ continue;
+ }
+
+ // Let media types be the set of codecs and codec constraints specified by
+ // parameters. The case-sensitivity of string comparisons is determined by
+ // the appropriate RFC or other specification.
+ // (Note: codecs array is 'parameter').
+
+ // If media types is empty:
+ if (codecs.IsEmpty()) {
+ // If container normatively implies a specific set of codecs and codec constraints:
+ // Let parameters be that set.
+ if (isMP4) {
+ if (aCodecType == Audio) {
+ codecs.AppendElement(EME_CODEC_AAC);
+ } else if (aCodecType == Video) {
+ codecs.AppendElement(EME_CODEC_H264);
+ }
+ } else if (isWebM) {
+ if (aCodecType == Audio) {
+ codecs.AppendElement(EME_CODEC_VORBIS);
+ } else if (aCodecType == Video) {
+ codecs.AppendElement(EME_CODEC_VP8);
+ }
+ }
+ // Otherwise: Continue to the next iteration.
+ // (Note: all containers we support have implied codecs, so don't continue here.)
+ }
+
+ // If content type is not strictly a audio/video type, continue to the next iteration.
+ const auto majorType = GetMajorType(container);
+ if (majorType == Invalid) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "MIME type is not an audio or video MIME type.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+ if (majorType != aCodecType || !AllCodecsOfType(codecs, aCodecType)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "MIME type mixes audio codecs in video capabilities "
+ "or video codecs in audio capabilities.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+ // If robustness is not the empty string and contains an unrecognized
+ // value or a value not supported by implementation, continue to the
+ // next iteration. String comparison is case-sensitive.
+ if (!robustness.IsEmpty()) {
+ if (majorType == Audio && !aKeySystem.mAudioRobustness.Contains(robustness)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "unsupported robustness string.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+ if (majorType == Video && !aKeySystem.mVideoRobustness.Contains(robustness)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "unsupported robustness string.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+ // Note: specified robustness requirements are satisfied.
+ }
+
+ // If the user agent and implementation definitely support playback of
+ // encrypted media data for the combination of container, media types,
+ // robustness and local accumulated configuration in combination with
+ // restrictions...
+ const auto& containerSupport = isMP4 ? aKeySystem.mMP4 : aKeySystem.mWebM;
+ if (!CanDecryptAndDecode(aKeySystem.mKeySystem,
+ contentType,
+ majorType,
+ containerSupport,
+ codecs,
+ aDiagnostics)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "codec unsupported by CDM requested.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+
+ // ... add requested media capability to supported media capabilities.
+ if (!supportedCapabilities.AppendElement(capabilities, mozilla::fallible)) {
+ NS_WARNING("GetSupportedCapabilities: Malloc failure");
+ return Sequence<MediaKeySystemMediaCapability>();
+ }
+
+ // Note: omitting steps 3.13.2, our robustness is not sophisticated enough
+ // to require considering all requirements together.
+ }
+ return Move(supportedCapabilities);
+}
+
+// "Get Supported Configuration and Consent" algorithm, steps 4-7 for
+// distinctive identifier, and steps 8-11 for persistent state. The steps
+// are the same for both requirements/features, so we factor them out into
+// a single function.
+static bool
+CheckRequirement(const MediaKeysRequirement aRequirement,
+ const KeySystemFeatureSupport aFeatureSupport,
+ MediaKeysRequirement& aOutRequirement)
+{
+ // Let requirement be the value of candidate configuration's member.
+ MediaKeysRequirement requirement = aRequirement;
+ // If requirement is "optional" and feature is not allowed according to
+ // restrictions, set requirement to "not-allowed".
+ if (aRequirement == MediaKeysRequirement::Optional &&
+ aFeatureSupport == KeySystemFeatureSupport::Prohibited) {
+ requirement = MediaKeysRequirement::Not_allowed;
+ }
+
+ // Follow the steps for requirement from the following list:
+ switch (requirement) {
+ case MediaKeysRequirement::Required: {
+ // If the implementation does not support use of requirement in combination
+ // with accumulated configuration and restrictions, return NotSupported.
+ if (aFeatureSupport == KeySystemFeatureSupport::Prohibited) {
+ return false;
+ }
+ break;
+ }
+ case MediaKeysRequirement::Optional: {
+ // Continue with the following steps.
+ break;
+ }
+ case MediaKeysRequirement::Not_allowed: {
+ // If the implementation requires use of feature in combination with
+ // accumulated configuration and restrictions, return NotSupported.
+ if (aFeatureSupport == KeySystemFeatureSupport::Required) {
+ return false;
+ }
+ break;
+ }
+ default: {
+ return false;
+ }
+ }
+
+ // Set the requirement member of accumulated configuration to equal
+ // calculated requirement.
+ aOutRequirement = requirement;
+
+ return true;
+}
+
+// 3.1.2.2, step 12
+// Follow the steps for the first matching condition from the following list:
+// If the sessionTypes member is present in candidate configuration.
+// Let session types be candidate configuration's sessionTypes member.
+// Otherwise let session types be ["temporary"].
+// Note: This returns an empty array on malloc failure.
+static Sequence<nsString>
+UnboxSessionTypes(const Optional<Sequence<nsString>>& aSessionTypes)
+{
+ Sequence<nsString> sessionTypes;
+ if (aSessionTypes.WasPassed()) {
+ sessionTypes = aSessionTypes.Value();
+ } else {
+ using MediaKeySessionTypeValues::strings;
+ const char* temporary = strings[static_cast<uint32_t>(MediaKeySessionType::Temporary)].value;
+ // Note: fallible. Results in an empty array.
+ sessionTypes.AppendElement(NS_ConvertUTF8toUTF16(nsDependentCString(temporary)), mozilla::fallible);
+ }
+ return sessionTypes;
+}
+
+// 3.1.2.2 Get Supported Configuration and Consent
+static bool
+GetSupportedConfig(const KeySystemConfig& aKeySystem,
+ const MediaKeySystemConfiguration& aCandidate,
+ MediaKeySystemConfiguration& aOutConfig,
+ DecoderDoctorDiagnostics* aDiagnostics)
+{
+ // Let accumulated configuration be a new MediaKeySystemConfiguration dictionary.
+ MediaKeySystemConfiguration config;
+ // Set the label member of accumulated configuration to equal the label member of
+ // candidate configuration.
+ config.mLabel = aCandidate.mLabel;
+ // If the initDataTypes member of candidate configuration is non-empty, run the
+ // following steps:
+ if (!aCandidate.mInitDataTypes.IsEmpty()) {
+ // Let supported types be an empty sequence of DOMStrings.
+ nsTArray<nsString> supportedTypes;
+ // For each value in candidate configuration's initDataTypes member:
+ for (const nsString& initDataType : aCandidate.mInitDataTypes) {
+ // Let initDataType be the value.
+ // If the implementation supports generating requests based on initDataType,
+ // add initDataType to supported types. String comparison is case-sensitive.
+ // The empty string is never supported.
+ if (aKeySystem.mInitDataTypes.Contains(initDataType)) {
+ supportedTypes.AppendElement(initDataType);
+ }
+ }
+ // If supported types is empty, return NotSupported.
+ if (supportedTypes.IsEmpty()) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "no supported initDataTypes provided.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+ // Set the initDataTypes member of accumulated configuration to supported types.
+ if (!config.mInitDataTypes.Assign(supportedTypes)) {
+ return false;
+ }
+ }
+
+ if (!CheckRequirement(aCandidate.mDistinctiveIdentifier,
+ aKeySystem.mDistinctiveIdentifier,
+ config.mDistinctiveIdentifier)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "distinctiveIdentifier requirement not satisfied.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+
+ if (!CheckRequirement(aCandidate.mPersistentState,
+ aKeySystem.mPersistentState,
+ config.mPersistentState)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "persistentState requirement not satisfied.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+
+ Sequence<nsString> sessionTypes(UnboxSessionTypes(aCandidate.mSessionTypes));
+ if (sessionTypes.IsEmpty()) {
+ // Malloc failure.
+ return false;
+ }
+
+ // For each value in session types:
+ for (const auto& sessionTypeString : sessionTypes) {
+ // Let session type be the value.
+ MediaKeySessionType sessionType;
+ if (!ToSessionType(sessionTypeString, sessionType)) {
+ // (Assume invalid sessionType is unsupported as per steps below).
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "invalid session type specified.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+ // If accumulated configuration's persistentState value is "not-allowed"
+ // and the Is persistent session type? algorithm returns true for session
+ // type return NotSupported.
+ if (config.mPersistentState == MediaKeysRequirement::Not_allowed &&
+ IsPersistentSessionType(sessionType)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "persistent session requested but keysystem doesn't"
+ "support persistent state.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+ // If the implementation does not support session type in combination
+ // with accumulated configuration and restrictions for other reasons,
+ // return NotSupported.
+ if (!aKeySystem.mSessionTypes.Contains(sessionType)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "session type '%s' unsupported by keySystem.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get(),
+ NS_ConvertUTF16toUTF8(sessionTypeString).get());
+ return false;
+ }
+ // If accumulated configuration's persistentState value is "optional"
+ // and the result of running the Is persistent session type? algorithm
+ // on session type is true, change accumulated configuration's
+ // persistentState value to "required".
+ if (config.mPersistentState == MediaKeysRequirement::Optional &&
+ IsPersistentSessionType(sessionType)) {
+ config.mPersistentState = MediaKeysRequirement::Required;
+ }
+ }
+ // Set the sessionTypes member of accumulated configuration to session types.
+ config.mSessionTypes.Construct(Move(sessionTypes));
+
+ // If the videoCapabilities and audioCapabilities members in candidate
+ // configuration are both empty, return NotSupported.
+ // TODO: Most sites using EME still don't pass capabilities, so we
+ // can't reject on it yet without breaking them. So add this later.
+
+ // If the videoCapabilities member in candidate configuration is non-empty:
+ if (!aCandidate.mVideoCapabilities.IsEmpty()) {
+ // Let video capabilities be the result of executing the Get Supported
+ // Capabilities for Audio/Video Type algorithm on Video, candidate
+ // configuration's videoCapabilities member, accumulated configuration,
+ // and restrictions.
+ Sequence<MediaKeySystemMediaCapability> caps =
+ GetSupportedCapabilities(Video,
+ aCandidate.mVideoCapabilities,
+ config,
+ aKeySystem,
+ aDiagnostics);
+ // If video capabilities is null, return NotSupported.
+ if (caps.IsEmpty()) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "no supported video capabilities.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+ // Set the videoCapabilities member of accumulated configuration to video capabilities.
+ config.mVideoCapabilities = Move(caps);
+ } else {
+ // Otherwise:
+ // Set the videoCapabilities member of accumulated configuration to an empty sequence.
+ }
+
+ // If the audioCapabilities member in candidate configuration is non-empty:
+ if (!aCandidate.mAudioCapabilities.IsEmpty()) {
+ // Let audio capabilities be the result of executing the Get Supported Capabilities
+ // for Audio/Video Type algorithm on Audio, candidate configuration's audioCapabilities
+ // member, accumulated configuration, and restrictions.
+ Sequence<MediaKeySystemMediaCapability> caps =
+ GetSupportedCapabilities(Audio,
+ aCandidate.mAudioCapabilities,
+ config,
+ aKeySystem,
+ aDiagnostics);
+ // If audio capabilities is null, return NotSupported.
+ if (caps.IsEmpty()) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "no supported audio capabilities.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+ // Set the audioCapabilities member of accumulated configuration to audio capabilities.
+ config.mAudioCapabilities = Move(caps);
+ } else {
+ // Otherwise:
+ // Set the audioCapabilities member of accumulated configuration to an empty sequence.
+ }
+
+ // If accumulated configuration's distinctiveIdentifier value is "optional", follow the
+ // steps for the first matching condition from the following list:
+ if (config.mDistinctiveIdentifier == MediaKeysRequirement::Optional) {
+ // If the implementation requires use Distinctive Identifier(s) or
+ // Distinctive Permanent Identifier(s) for any of the combinations
+ // in accumulated configuration
+ if (aKeySystem.mDistinctiveIdentifier == KeySystemFeatureSupport::Required) {
+ // Change accumulated configuration's distinctiveIdentifier value to "required".
+ config.mDistinctiveIdentifier = MediaKeysRequirement::Required;
+ } else {
+ // Otherwise, change accumulated configuration's distinctiveIdentifier
+ // value to "not-allowed".
+ config.mDistinctiveIdentifier = MediaKeysRequirement::Not_allowed;
+ }
+ }
+
+ // If accumulated configuration's persistentState value is "optional", follow the
+ // steps for the first matching condition from the following list:
+ if (config.mPersistentState == MediaKeysRequirement::Optional) {
+ // If the implementation requires persisting state for any of the combinations
+ // in accumulated configuration
+ if (aKeySystem.mPersistentState == KeySystemFeatureSupport::Required) {
+ // Change accumulated configuration's persistentState value to "required".
+ config.mPersistentState = MediaKeysRequirement::Required;
+ } else {
+ // Otherwise, change accumulated configuration's persistentState
+ // value to "not-allowed".
+ config.mPersistentState = MediaKeysRequirement::Not_allowed;
+ }
+ }
+
+ // Note: Omitting steps 20-22. We don't ask for consent.
+
+#if defined(XP_WIN)
+ // Widevine CDM doesn't include an AAC decoder. So if WMF can't decode AAC,
+ // and a codec wasn't specified, be conservative and reject the MediaKeys request.
+ if (IsWidevineKeySystem(aKeySystem.mKeySystem) &&
+ (aCandidate.mAudioCapabilities.IsEmpty() ||
+ aCandidate.mVideoCapabilities.IsEmpty()) &&
+ !WMFDecoderModule::HasAAC()) {
+ if (aDiagnostics) {
+ aDiagnostics->SetKeySystemIssue(
+ DecoderDoctorDiagnostics::eWidevineWithNoWMF);
+ }
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "WMF required for Widevine decoding, but it's not available.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+#endif
+
+ // Return accumulated configuration.
+ aOutConfig = config;
+
+ return true;
+}
+
+/* static */
+bool
+MediaKeySystemAccess::GetSupportedConfig(const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ MediaKeySystemConfiguration& aOutConfig,
+ DecoderDoctorDiagnostics* aDiagnostics)
+{
+ KeySystemConfig implementation;
+ if (!GetKeySystemConfig(aKeySystem, implementation)) {
+ return false;
+ }
+ for (const MediaKeySystemConfiguration& candidate : aConfigs) {
+ if (mozilla::dom::GetSupportedConfig(implementation,
+ candidate,
+ aOutConfig,
+ aDiagnostics)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/* static */
+void
+MediaKeySystemAccess::NotifyObservers(nsPIDOMWindowInner* aWindow,
+ const nsAString& aKeySystem,
+ MediaKeySystemStatus aStatus)
+{
+ RequestMediaKeySystemAccessNotification data;
+ data.mKeySystem = aKeySystem;
+ data.mStatus = aStatus;
+ nsAutoString json;
+ data.ToJSON(json);
+ EME_LOG("MediaKeySystemAccess::NotifyObservers() %s", NS_ConvertUTF16toUTF8(json).get());
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(aWindow, "mediakeys-request", json.get());
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/MediaKeySystemAccess.h b/dom/media/eme/MediaKeySystemAccess.h
new file mode 100644
index 000000000..a166ac22e
--- /dev/null
+++ b/dom/media/eme/MediaKeySystemAccess.h
@@ -0,0 +1,81 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_MediaKeySystemAccess_h
+#define mozilla_dom_MediaKeySystemAccess_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/MediaKeySystemAccessBinding.h"
+#include "mozilla/dom/MediaKeysRequestStatusBinding.h"
+
+#include "js/TypeDecls.h"
+
+namespace mozilla {
+
+class DecoderDoctorDiagnostics;
+
+namespace dom {
+
+class MediaKeySystemAccess final : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaKeySystemAccess)
+
+public:
+ explicit MediaKeySystemAccess(nsPIDOMWindowInner* aParent,
+ const nsAString& aKeySystem,
+ const MediaKeySystemConfiguration& aConfig);
+
+protected:
+ ~MediaKeySystemAccess();
+
+public:
+ nsPIDOMWindowInner* GetParentObject() const;
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void GetKeySystem(nsString& aRetVal) const;
+
+ void GetConfiguration(MediaKeySystemConfiguration& aConfig);
+
+ already_AddRefed<Promise> CreateMediaKeys(ErrorResult& aRv);
+
+ static MediaKeySystemStatus GetKeySystemStatus(const nsAString& aKeySystem,
+ nsACString& aOutExceptionMessage);
+
+ static bool IsSupported(const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ DecoderDoctorDiagnostics* aDiagnostics);
+
+ static void NotifyObservers(nsPIDOMWindowInner* aWindow,
+ const nsAString& aKeySystem,
+ MediaKeySystemStatus aStatus);
+
+ static bool GetSupportedConfig(const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ MediaKeySystemConfiguration& aOutConfig,
+ DecoderDoctorDiagnostics* aDiagnostics);
+
+ static bool KeySystemSupportsInitDataType(const nsAString& aKeySystem,
+ const nsAString& aInitDataType);
+
+private:
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ const nsString mKeySystem;
+ const MediaKeySystemConfiguration mConfig;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_MediaKeySystemAccess_h
diff --git a/dom/media/eme/MediaKeySystemAccessManager.cpp b/dom/media/eme/MediaKeySystemAccessManager.cpp
new file mode 100644
index 000000000..a1e1254ad
--- /dev/null
+++ b/dom/media/eme/MediaKeySystemAccessManager.cpp
@@ -0,0 +1,336 @@
+/* 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 "MediaKeySystemAccessManager.h"
+#include "DecoderDoctorDiagnostics.h"
+#include "MediaPrefs.h"
+#include "mozilla/EMEUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/DetailedPromise.h"
+#ifdef XP_WIN
+#include "mozilla/WindowsVersion.h"
+#endif
+#include "nsPrintfCString.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccessManager)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccessManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccessManager)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaKeySystemAccessManager)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaKeySystemAccessManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+ for (size_t i = 0; i < tmp->mRequests.Length(); i++) {
+ tmp->mRequests[i].RejectPromise(NS_LITERAL_CSTRING("Promise still outstanding at MediaKeySystemAccessManager GC"));
+ tmp->mRequests[i].CancelTimer();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRequests[i].mPromise)
+ }
+ tmp->mRequests.Clear();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaKeySystemAccessManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+ for (size_t i = 0; i < tmp->mRequests.Length(); i++) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRequests[i].mPromise)
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+MediaKeySystemAccessManager::MediaKeySystemAccessManager(nsPIDOMWindowInner* aWindow)
+ : mWindow(aWindow)
+ , mAddedObservers(false)
+{
+}
+
+MediaKeySystemAccessManager::~MediaKeySystemAccessManager()
+{
+ Shutdown();
+}
+
+void
+MediaKeySystemAccessManager::Request(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs)
+{
+ Request(aPromise, aKeySystem, aConfigs, RequestType::Initial);
+}
+
+void
+MediaKeySystemAccessManager::Request(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ RequestType aType)
+{
+ EME_LOG("MediaKeySystemAccessManager::Request %s", NS_ConvertUTF16toUTF8(aKeySystem).get());
+
+ if (aKeySystem.IsEmpty()) {
+ aPromise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("Key system string is empty"));
+ // Don't notify DecoderDoctor, as there's nothing we or the user can
+ // do to fix this situation; the site is using the API wrong.
+ return;
+ }
+ if (aConfigs.IsEmpty()) {
+ aPromise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("Candidate MediaKeySystemConfigs is empty"));
+ // Don't notify DecoderDoctor, as there's nothing we or the user can
+ // do to fix this situation; the site is using the API wrong.
+ return;
+ }
+
+ DecoderDoctorDiagnostics diagnostics;
+
+ // Ensure keysystem is supported.
+ if (!IsWidevineKeySystem(aKeySystem) &&
+ !IsClearkeyKeySystem(aKeySystem)) {
+ // Not to inform user, because nothing to do if the keySystem is not
+ // supported.
+ aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+ NS_LITERAL_CSTRING("Key system is unsupported"));
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+ aKeySystem, false, __func__);
+ return;
+ }
+
+ if (!MediaPrefs::EMEEnabled() && !IsClearkeyKeySystem(aKeySystem)) {
+ // EME disabled by user, send notification to chrome so UI can inform user.
+ // Clearkey is allowed even when EME is disabled because we want the pref
+ // "media.eme.enabled" only taking effect on proprietary DRMs.
+ MediaKeySystemAccess::NotifyObservers(mWindow,
+ aKeySystem,
+ MediaKeySystemStatus::Api_disabled);
+ aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+ NS_LITERAL_CSTRING("EME has been preffed off"));
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+ aKeySystem, false, __func__);
+ return;
+ }
+
+ nsAutoCString message;
+ MediaKeySystemStatus status =
+ MediaKeySystemAccess::GetKeySystemStatus(aKeySystem, message);
+
+ nsPrintfCString msg("MediaKeySystemAccess::GetKeySystemStatus(%s) "
+ "result=%s msg='%s'",
+ NS_ConvertUTF16toUTF8(aKeySystem).get(),
+ MediaKeySystemStatusValues::strings[(size_t)status].value,
+ message.get());
+ LogToBrowserConsole(NS_ConvertUTF8toUTF16(msg));
+
+ if (status == MediaKeySystemStatus::Cdm_not_installed &&
+ IsWidevineKeySystem(aKeySystem)) {
+ // These are cases which could be resolved by downloading a new(er) CDM.
+ // When we send the status to chrome, chrome's GMPProvider will attempt to
+ // download or update the CDM. In AwaitInstall() we add listeners to wait
+ // for the update to complete, and we'll call this function again with
+ // aType==Subsequent once the download has completed and the GMPService
+ // has had a new plugin added. AwaitInstall() sets a timer to fail if the
+ // update/download takes too long or fails.
+ if (aType == RequestType::Initial &&
+ AwaitInstall(aPromise, aKeySystem, aConfigs)) {
+ // Notify chrome that we're going to wait for the CDM to download/update.
+ // Note: If we're re-trying, we don't re-send the notification,
+ // as chrome is already displaying the "we can't play, updating"
+ // notification.
+ MediaKeySystemAccess::NotifyObservers(mWindow, aKeySystem, status);
+ } else {
+ // We waited or can't wait for an update and we still can't service
+ // the request. Give up. Chrome will still be showing a "I can't play,
+ // updating" notification.
+ aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+ NS_LITERAL_CSTRING("Gave up while waiting for a CDM update"));
+ }
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+ aKeySystem, false, __func__);
+ return;
+ }
+ if (status != MediaKeySystemStatus::Available) {
+ // Failed due to user disabling something, send a notification to
+ // chrome, so we can show some UI to explain how the user can rectify
+ // the situation.
+ MediaKeySystemAccess::NotifyObservers(mWindow, aKeySystem, status);
+ aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR, message);
+ return;
+ }
+
+ MediaKeySystemConfiguration config;
+ if (MediaKeySystemAccess::GetSupportedConfig(aKeySystem, aConfigs, config, &diagnostics)) {
+ RefPtr<MediaKeySystemAccess> access(
+ new MediaKeySystemAccess(mWindow, aKeySystem, config));
+ aPromise->MaybeResolve(access);
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+ aKeySystem, true, __func__);
+ return;
+ }
+ // Not to inform user, because nothing to do if the corresponding keySystem
+ // configuration is not supported.
+ aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+ NS_LITERAL_CSTRING("Key system configuration is not supported"));
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+ aKeySystem, false, __func__);
+}
+
+MediaKeySystemAccessManager::PendingRequest::PendingRequest(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ nsITimer* aTimer)
+ : mPromise(aPromise)
+ , mKeySystem(aKeySystem)
+ , mConfigs(aConfigs)
+ , mTimer(aTimer)
+{
+ MOZ_COUNT_CTOR(MediaKeySystemAccessManager::PendingRequest);
+}
+
+MediaKeySystemAccessManager::PendingRequest::PendingRequest(const PendingRequest& aOther)
+ : mPromise(aOther.mPromise)
+ , mKeySystem(aOther.mKeySystem)
+ , mConfigs(aOther.mConfigs)
+ , mTimer(aOther.mTimer)
+{
+ MOZ_COUNT_CTOR(MediaKeySystemAccessManager::PendingRequest);
+}
+
+MediaKeySystemAccessManager::PendingRequest::~PendingRequest()
+{
+ MOZ_COUNT_DTOR(MediaKeySystemAccessManager::PendingRequest);
+}
+
+void
+MediaKeySystemAccessManager::PendingRequest::CancelTimer()
+{
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+}
+
+void
+MediaKeySystemAccessManager::PendingRequest::RejectPromise(const nsCString& aReason)
+{
+ if (mPromise) {
+ mPromise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR, aReason);
+ }
+}
+
+bool
+MediaKeySystemAccessManager::AwaitInstall(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs)
+{
+ EME_LOG("MediaKeySystemAccessManager::AwaitInstall %s", NS_ConvertUTF16toUTF8(aKeySystem).get());
+
+ if (!EnsureObserversAdded()) {
+ NS_WARNING("Failed to add pref observer");
+ return false;
+ }
+
+ nsCOMPtr<nsITimer> timer(do_CreateInstance("@mozilla.org/timer;1"));
+ if (!timer || NS_FAILED(timer->Init(this, 60 * 1000, nsITimer::TYPE_ONE_SHOT))) {
+ NS_WARNING("Failed to create timer to await CDM install.");
+ return false;
+ }
+
+ mRequests.AppendElement(PendingRequest(aPromise, aKeySystem, aConfigs, timer));
+ return true;
+}
+
+void
+MediaKeySystemAccessManager::RetryRequest(PendingRequest& aRequest)
+{
+ aRequest.CancelTimer();
+ Request(aRequest.mPromise, aRequest.mKeySystem, aRequest.mConfigs, RequestType::Subsequent);
+}
+
+nsresult
+MediaKeySystemAccessManager::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ EME_LOG("MediaKeySystemAccessManager::Observe %s", aTopic);
+
+ if (!strcmp(aTopic, "gmp-changed")) {
+ // Filter out the requests where the CDM's install-status is no longer
+ // "unavailable". This will be the CDMs which have downloaded since the
+ // initial request.
+ // Note: We don't have a way to communicate from chrome that the CDM has
+ // failed to download, so we'll just let the timeout fail us in that case.
+ nsTArray<PendingRequest> requests;
+ for (size_t i = mRequests.Length(); i-- > 0; ) {
+ PendingRequest& request = mRequests[i];
+ nsAutoCString message;
+ MediaKeySystemStatus status =
+ MediaKeySystemAccess::GetKeySystemStatus(request.mKeySystem, message);
+ if (status == MediaKeySystemStatus::Cdm_not_installed) {
+ // Not yet installed, don't retry. Keep waiting until timeout.
+ continue;
+ }
+ // Status has changed, retry request.
+ requests.AppendElement(Move(request));
+ mRequests.RemoveElementAt(i);
+ }
+ // Retry all pending requests, but this time fail if the CDM is not installed.
+ for (PendingRequest& request : requests) {
+ RetryRequest(request);
+ }
+ } else if (!strcmp(aTopic, "timer-callback")) {
+ // Find the timer that expired and re-run the request for it.
+ nsCOMPtr<nsITimer> timer(do_QueryInterface(aSubject));
+ for (size_t i = 0; i < mRequests.Length(); i++) {
+ if (mRequests[i].mTimer == timer) {
+ EME_LOG("MediaKeySystemAccessManager::AwaitInstall resuming request");
+ PendingRequest request = mRequests[i];
+ mRequests.RemoveElementAt(i);
+ RetryRequest(request);
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+bool
+MediaKeySystemAccessManager::EnsureObserversAdded()
+{
+ if (mAddedObservers) {
+ return true;
+ }
+
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!obsService)) {
+ return false;
+ }
+ mAddedObservers = NS_SUCCEEDED(obsService->AddObserver(this, "gmp-changed", false));
+ return mAddedObservers;
+}
+
+void
+MediaKeySystemAccessManager::Shutdown()
+{
+ EME_LOG("MediaKeySystemAccessManager::Shutdown");
+ nsTArray<PendingRequest> requests(Move(mRequests));
+ for (PendingRequest& request : requests) {
+ // Cancel all requests; we're shutting down.
+ request.CancelTimer();
+ request.RejectPromise(NS_LITERAL_CSTRING("Promise still outstanding at MediaKeySystemAccessManager shutdown"));
+ }
+ if (mAddedObservers) {
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ if (obsService) {
+ obsService->RemoveObserver(this, "gmp-changed");
+ mAddedObservers = false;
+ }
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/MediaKeySystemAccessManager.h b/dom/media/eme/MediaKeySystemAccessManager.h
new file mode 100644
index 000000000..9c092248e
--- /dev/null
+++ b/dom/media/eme/MediaKeySystemAccessManager.h
@@ -0,0 +1,83 @@
+/* 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 mozilla_dom_MediaKeySystemAccessManager_h
+#define mozilla_dom_MediaKeySystemAccessManager_h
+
+#include "mozilla/dom/MediaKeySystemAccess.h"
+#include "nsIObserver.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupportsImpl.h"
+#include "nsITimer.h"
+
+namespace mozilla {
+namespace dom {
+
+class DetailedPromise;
+class TestGMPVideoDecoder;
+
+class MediaKeySystemAccessManager final : public nsIObserver
+{
+public:
+
+ explicit MediaKeySystemAccessManager(nsPIDOMWindowInner* aWindow);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(MediaKeySystemAccessManager, nsIObserver)
+ NS_DECL_NSIOBSERVER
+
+ void Request(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfig);
+
+ void Shutdown();
+
+ struct PendingRequest {
+ PendingRequest(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfig,
+ nsITimer* aTimer);
+ PendingRequest(const PendingRequest& aOther);
+ ~PendingRequest();
+ void CancelTimer();
+ void RejectPromise(const nsCString& aReason);
+
+ RefPtr<DetailedPromise> mPromise;
+ const nsString mKeySystem;
+ const Sequence<MediaKeySystemConfiguration> mConfigs;
+ nsCOMPtr<nsITimer> mTimer;
+ };
+
+private:
+
+ enum RequestType {
+ Initial,
+ Subsequent
+ };
+
+ void Request(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfig,
+ RequestType aType);
+
+ ~MediaKeySystemAccessManager();
+
+ bool EnsureObserversAdded();
+
+ bool AwaitInstall(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfig);
+
+ void RetryRequest(PendingRequest& aRequest);
+
+ nsTArray<PendingRequest> mRequests;
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ bool mAddedObservers;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/eme/MediaKeys.cpp b/dom/media/eme/MediaKeys.cpp
new file mode 100644
index 000000000..862100757
--- /dev/null
+++ b/dom/media/eme/MediaKeys.cpp
@@ -0,0 +1,579 @@
+/* -*- Mode: C++; tab-width: 8; 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 "mozilla/dom/MediaKeys.h"
+#include "GMPService.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/MediaKeysBinding.h"
+#include "mozilla/dom/MediaKeyMessageEvent.h"
+#include "mozilla/dom/MediaKeyError.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "GMPCDMProxy.h"
+#include "mozilla/EMEUtils.h"
+#include "nsContentUtils.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsContentTypeParser.h"
+#ifdef MOZ_FMP4
+#include "MP4Decoder.h"
+#endif
+#ifdef XP_WIN
+#include "mozilla/WindowsVersion.h"
+#endif
+#include "nsContentCID.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/dom/MediaKeySystemAccess.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla {
+
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeys,
+ mElement,
+ mParent,
+ mKeySessions,
+ mPromises,
+ mPendingSessions);
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeys)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeys)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeys)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+MediaKeys::MediaKeys(nsPIDOMWindowInner* aParent,
+ const nsAString& aKeySystem,
+ const MediaKeySystemConfiguration& aConfig)
+ : mParent(aParent)
+ , mKeySystem(aKeySystem)
+ , mCreatePromiseId(0)
+ , mConfig(aConfig)
+{
+ EME_LOG("MediaKeys[%p] constructed keySystem=%s",
+ this, NS_ConvertUTF16toUTF8(mKeySystem).get());
+}
+
+MediaKeys::~MediaKeys()
+{
+ Shutdown();
+ EME_LOG("MediaKeys[%p] destroyed", this);
+}
+
+void
+MediaKeys::Terminated()
+{
+ EME_LOG("MediaKeys[%p] CDM crashed unexpectedly", this);
+
+ KeySessionHashMap keySessions;
+ // Remove entries during iteration will screw it. Make a copy first.
+ for (auto iter = mKeySessions.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<MediaKeySession>& session = iter.Data();
+ keySessions.Put(session->GetSessionId(), session);
+ }
+ for (auto iter = keySessions.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<MediaKeySession>& session = iter.Data();
+ session->OnClosed();
+ }
+ keySessions.Clear();
+ MOZ_ASSERT(mKeySessions.Count() == 0);
+
+ // Notify the element about that CDM has terminated.
+ if (mElement) {
+ mElement->DecodeError(NS_ERROR_DOM_MEDIA_CDM_ERR);
+ }
+
+ Shutdown();
+}
+
+void
+MediaKeys::Shutdown()
+{
+ if (mProxy) {
+ mProxy->Shutdown();
+ mProxy = nullptr;
+ }
+
+ RefPtr<MediaKeys> kungFuDeathGrip = this;
+
+ for (auto iter = mPromises.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<dom::DetailedPromise>& promise = iter.Data();
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Promise still outstanding at MediaKeys shutdown"));
+ Release();
+ }
+ mPromises.Clear();
+}
+
+nsPIDOMWindowInner*
+MediaKeys::GetParentObject() const
+{
+ return mParent;
+}
+
+JSObject*
+MediaKeys::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaKeysBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+MediaKeys::GetKeySystem(nsString& aOutKeySystem) const
+{
+ aOutKeySystem.Assign(mKeySystem);
+}
+
+already_AddRefed<DetailedPromise>
+MediaKeys::SetServerCertificate(const ArrayBufferViewOrArrayBuffer& aCert, ErrorResult& aRv)
+{
+ RefPtr<DetailedPromise> promise(MakePromise(aRv,
+ NS_LITERAL_CSTRING("MediaKeys.setServerCertificate")));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (!mProxy) {
+ NS_WARNING("Tried to use a MediaKeys without a CDM");
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in MediaKeys.setServerCertificate()"));
+ return promise.forget();
+ }
+
+ nsTArray<uint8_t> data;
+ CopyArrayBufferViewOrArrayBufferData(aCert, data);
+ if (data.IsEmpty()) {
+ promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("Empty certificate passed to MediaKeys.setServerCertificate()"));
+ return promise.forget();
+ }
+
+ mProxy->SetServerCertificate(StorePromise(promise), data);
+ return promise.forget();
+}
+
+already_AddRefed<DetailedPromise>
+MediaKeys::MakePromise(ErrorResult& aRv, const nsACString& aName)
+{
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ if (!global) {
+ NS_WARNING("Passed non-global to MediaKeys ctor!");
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ return DetailedPromise::Create(global, aRv, aName);
+}
+
+PromiseId
+MediaKeys::StorePromise(DetailedPromise* aPromise)
+{
+ static uint32_t sEMEPromiseCount = 1;
+ MOZ_ASSERT(aPromise);
+ uint32_t id = sEMEPromiseCount++;
+
+ EME_LOG("MediaKeys[%p]::StorePromise() id=%d", this, id);
+
+ // Keep MediaKeys alive for the lifetime of its promises. Any still-pending
+ // promises are rejected in Shutdown().
+ AddRef();
+
+#ifdef DEBUG
+ // We should not have already stored this promise!
+ for (auto iter = mPromises.ConstIter(); !iter.Done(); iter.Next()) {
+ MOZ_ASSERT(iter.Data() != aPromise);
+ }
+#endif
+
+ mPromises.Put(id, aPromise);
+ return id;
+}
+
+void
+MediaKeys::ConnectPendingPromiseIdWithToken(PromiseId aId, uint32_t aToken)
+{
+ // Should only be called from MediaKeySession::GenerateRequest.
+ mPromiseIdToken.Put(aId, aToken);
+ EME_LOG("MediaKeys[%p]::ConnectPendingPromiseIdWithToken() id=%u => token(%u)",
+ this, aId, aToken);
+}
+
+already_AddRefed<DetailedPromise>
+MediaKeys::RetrievePromise(PromiseId aId)
+{
+ if (!mPromises.Contains(aId)) {
+ NS_WARNING(nsPrintfCString("Tried to retrieve a non-existent promise id=%d", aId).get());
+ return nullptr;
+ }
+ RefPtr<DetailedPromise> promise;
+ mPromises.Remove(aId, getter_AddRefs(promise));
+ Release();
+ return promise.forget();
+}
+
+void
+MediaKeys::RejectPromise(PromiseId aId, nsresult aExceptionCode,
+ const nsCString& aReason)
+{
+ EME_LOG("MediaKeys[%p]::RejectPromise(%d, 0x%x)", this, aId, aExceptionCode);
+
+ RefPtr<DetailedPromise> promise(RetrievePromise(aId));
+ if (!promise) {
+ return;
+ }
+
+ // This promise could be a createSession or loadSession promise,
+ // so we might have a pending session waiting to be resolved into
+ // the promise on success. We've been directed to reject to promise,
+ // so we can throw away the corresponding session object.
+ uint32_t token = 0;
+ if (mPromiseIdToken.Get(aId, &token)) {
+ MOZ_ASSERT(mPendingSessions.Contains(token));
+ mPendingSessions.Remove(token);
+ mPromiseIdToken.Remove(aId);
+ }
+
+ MOZ_ASSERT(NS_FAILED(aExceptionCode));
+ promise->MaybeReject(aExceptionCode, aReason);
+
+ if (mCreatePromiseId == aId) {
+ // Note: This will probably destroy the MediaKeys object!
+ Release();
+ }
+}
+
+void
+MediaKeys::OnSessionIdReady(MediaKeySession* aSession)
+{
+ if (!aSession) {
+ NS_WARNING("Invalid MediaKeySession passed to OnSessionIdReady()");
+ return;
+ }
+ if (mKeySessions.Contains(aSession->GetSessionId())) {
+ NS_WARNING("MediaKeySession's made ready multiple times!");
+ return;
+ }
+ if (mPendingSessions.Contains(aSession->Token())) {
+ NS_WARNING("MediaKeySession made ready when it wasn't waiting to be ready!");
+ return;
+ }
+ if (aSession->GetSessionId().IsEmpty()) {
+ NS_WARNING("MediaKeySession with invalid sessionId passed to OnSessionIdReady()");
+ return;
+ }
+ mKeySessions.Put(aSession->GetSessionId(), aSession);
+}
+
+void
+MediaKeys::ResolvePromise(PromiseId aId)
+{
+ EME_LOG("MediaKeys[%p]::ResolvePromise(%d)", this, aId);
+
+ RefPtr<DetailedPromise> promise(RetrievePromise(aId));
+ MOZ_ASSERT(!mPromises.Contains(aId));
+ if (!promise) {
+ return;
+ }
+
+ uint32_t token = 0;
+ if (!mPromiseIdToken.Get(aId, &token)) {
+ promise->MaybeResolveWithUndefined();
+ return;
+ } else if (!mPendingSessions.Contains(token)) {
+ // Pending session for CreateSession() should be removed when sessionId
+ // is ready.
+ promise->MaybeResolveWithUndefined();
+ mPromiseIdToken.Remove(aId);
+ return;
+ }
+ mPromiseIdToken.Remove(aId);
+
+ // We should only resolve LoadSession calls via this path,
+ // not CreateSession() promises.
+ RefPtr<MediaKeySession> session;
+ mPendingSessions.Remove(token, getter_AddRefs(session));
+ if (!session || session->GetSessionId().IsEmpty()) {
+ NS_WARNING("Received activation for non-existent session!");
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR,
+ NS_LITERAL_CSTRING("CDM LoadSession() returned a different session ID than requested"));
+ return;
+ }
+ mKeySessions.Put(session->GetSessionId(), session);
+ promise->MaybeResolve(session);
+}
+
+class MediaKeysGMPCrashHelper : public GMPCrashHelper
+{
+public:
+ explicit MediaKeysGMPCrashHelper(MediaKeys* aMediaKeys)
+ : mMediaKeys(aMediaKeys)
+ {
+ MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
+ }
+ already_AddRefed<nsPIDOMWindowInner>
+ GetPluginCrashedEventTarget() override
+ {
+ MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
+ EME_LOG("MediaKeysGMPCrashHelper::GetPluginCrashedEventTarget()");
+ return (mMediaKeys && mMediaKeys->GetParentObject()) ?
+ do_AddRef(mMediaKeys->GetParentObject()) : nullptr;
+ }
+private:
+ WeakPtr<MediaKeys> mMediaKeys;
+};
+
+already_AddRefed<CDMProxy>
+MediaKeys::CreateCDMProxy()
+{
+ RefPtr<CDMProxy> proxy;
+ {
+ proxy = new GMPCDMProxy(this,
+ mKeySystem,
+ new MediaKeysGMPCrashHelper(this),
+ mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required,
+ mConfig.mPersistentState == MediaKeysRequirement::Required);
+ }
+ return proxy.forget();
+}
+
+already_AddRefed<DetailedPromise>
+MediaKeys::Init(ErrorResult& aRv)
+{
+ RefPtr<DetailedPromise> promise(MakePromise(aRv,
+ NS_LITERAL_CSTRING("MediaKeys::Init()")));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ mProxy = CreateCDMProxy();
+
+ // Determine principal (at creation time) of the MediaKeys object.
+ nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetParentObject());
+ if (!sop) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get script principal in MediaKeys::Init"));
+ return promise.forget();
+ }
+ mPrincipal = sop->GetPrincipal();
+
+ // Determine principal of the "top-level" window; the principal of the
+ // page that will display in the URL bar.
+ nsCOMPtr<nsPIDOMWindowInner> window = GetParentObject();
+ if (!window) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get top-level window in MediaKeys::Init"));
+ return promise.forget();
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> top = window->GetOuterWindow()->GetTop();
+ if (!top || !top->GetExtantDoc()) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get document in MediaKeys::Init"));
+ return promise.forget();
+ }
+
+ mTopLevelPrincipal = top->GetExtantDoc()->NodePrincipal();
+
+ if (!mPrincipal || !mTopLevelPrincipal) {
+ NS_WARNING("Failed to get principals when creating MediaKeys");
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get principal(s) in MediaKeys::Init"));
+ return promise.forget();
+ }
+
+ nsAutoCString origin;
+ nsresult rv = mPrincipal->GetOrigin(origin);
+ if (NS_FAILED(rv)) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get principal origin string in MediaKeys::Init"));
+ return promise.forget();
+ }
+ nsAutoCString topLevelOrigin;
+ rv = mTopLevelPrincipal->GetOrigin(topLevelOrigin);
+ if (NS_FAILED(rv)) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get top-level principal origin string in MediaKeys::Init"));
+ return promise.forget();
+ }
+
+ nsIDocument* doc = window->GetExtantDoc();
+ const bool inPrivateBrowsing = nsContentUtils::IsInPrivateBrowsing(doc);
+
+ EME_LOG("MediaKeys[%p]::Create() (%s, %s), %s",
+ this,
+ origin.get(),
+ topLevelOrigin.get(),
+ (inPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"));
+
+ // 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(!mCreatePromiseId, "Should only be created once!");
+ mCreatePromiseId = StorePromise(promise);
+ AddRef();
+ mProxy->Init(mCreatePromiseId,
+ NS_ConvertUTF8toUTF16(origin),
+ NS_ConvertUTF8toUTF16(topLevelOrigin),
+ KeySystemToGMPName(mKeySystem),
+ inPrivateBrowsing);
+
+ return promise.forget();
+}
+
+void
+MediaKeys::OnCDMCreated(PromiseId aId, const nsACString& aNodeId, const uint32_t aPluginId)
+{
+ RefPtr<DetailedPromise> promise(RetrievePromise(aId));
+ if (!promise) {
+ return;
+ }
+ mNodeId = aNodeId;
+ RefPtr<MediaKeys> keys(this);
+ EME_LOG("MediaKeys[%p]::OnCDMCreated() resolve promise id=%d", this, aId);
+ promise->MaybeResolve(keys);
+ if (mCreatePromiseId == aId) {
+ Release();
+ }
+
+ MediaKeySystemAccess::NotifyObservers(mParent,
+ mKeySystem,
+ MediaKeySystemStatus::Cdm_created);
+
+}
+
+static bool
+IsSessionTypeSupported(const MediaKeySessionType aSessionType,
+ const MediaKeySystemConfiguration& aConfig)
+{
+ if (aSessionType == MediaKeySessionType::Temporary) {
+ // Temporary is always supported.
+ return true;
+ }
+ if (!aConfig.mSessionTypes.WasPassed()) {
+ // No other session types supported.
+ return false;
+ }
+ using MediaKeySessionTypeValues::strings;
+ const char* sessionType = strings[static_cast<uint32_t>(aSessionType)].value;
+ for (const nsString& s : aConfig.mSessionTypes.Value()) {
+ if (s.EqualsASCII(sessionType)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+already_AddRefed<MediaKeySession>
+MediaKeys::CreateSession(JSContext* aCx,
+ MediaKeySessionType aSessionType,
+ ErrorResult& aRv)
+{
+ if (!IsSessionTypeSupported(aSessionType, mConfig)) {
+ EME_LOG("MediaKeys[%p,'%s'] CreateSession() failed, unsupported session type", this);
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ if (!mProxy) {
+ NS_WARNING("Tried to use a MediaKeys which lost its CDM");
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ EME_LOG("MediaKeys[%p] Creating session", this);
+
+ RefPtr<MediaKeySession> session = new MediaKeySession(aCx,
+ GetParentObject(),
+ this,
+ mKeySystem,
+ aSessionType,
+ aRv);
+
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Add session to the set of sessions awaiting their sessionId being ready.
+ mPendingSessions.Put(session->Token(), session);
+
+ return session.forget();
+}
+
+void
+MediaKeys::OnSessionLoaded(PromiseId aId, bool aSuccess)
+{
+ RefPtr<DetailedPromise> promise(RetrievePromise(aId));
+ if (!promise) {
+ return;
+ }
+ EME_LOG("MediaKeys[%p]::OnSessionLoaded() resolve promise id=%d", this, aId);
+
+ promise->MaybeResolve(aSuccess);
+}
+
+void
+MediaKeys::OnSessionClosed(MediaKeySession* aSession)
+{
+ nsAutoString id;
+ aSession->GetSessionId(id);
+ mKeySessions.Remove(id);
+}
+
+already_AddRefed<MediaKeySession>
+MediaKeys::GetSession(const nsAString& aSessionId)
+{
+ RefPtr<MediaKeySession> session;
+ mKeySessions.Get(aSessionId, getter_AddRefs(session));
+ return session.forget();
+}
+
+already_AddRefed<MediaKeySession>
+MediaKeys::GetPendingSession(uint32_t aToken)
+{
+ RefPtr<MediaKeySession> session;
+ mPendingSessions.Get(aToken, getter_AddRefs(session));
+ mPendingSessions.Remove(aToken);
+ return session.forget();
+}
+
+const nsCString&
+MediaKeys::GetNodeId() const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mNodeId;
+}
+
+bool
+MediaKeys::IsBoundToMediaElement() const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mElement != nullptr;
+}
+
+nsresult
+MediaKeys::Bind(HTMLMediaElement* aElement)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (IsBoundToMediaElement()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mElement = aElement;
+
+ return NS_OK;
+}
+
+void
+MediaKeys::Unbind()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mElement = nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/MediaKeys.h b/dom/media/eme/MediaKeys.h
new file mode 100644
index 000000000..491963934
--- /dev/null
+++ b/dom/media/eme/MediaKeys.h
@@ -0,0 +1,167 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef mozilla_dom_mediakeys_h__
+#define mozilla_dom_mediakeys_h__
+
+#include "nsWrapperCache.h"
+#include "nsISupports.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsRefPtrHashtable.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/MediaKeysBinding.h"
+#include "mozilla/dom/MediaKeySystemAccessBinding.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mozilla/DetailedPromise.h"
+#include "mozilla/WeakPtr.h"
+
+namespace mozilla {
+
+class CDMProxy;
+
+namespace dom {
+
+class ArrayBufferViewOrArrayBuffer;
+class MediaKeySession;
+class HTMLMediaElement;
+
+typedef nsRefPtrHashtable<nsStringHashKey, MediaKeySession> KeySessionHashMap;
+typedef nsRefPtrHashtable<nsUint32HashKey, dom::DetailedPromise> PromiseHashMap;
+typedef nsRefPtrHashtable<nsUint32HashKey, MediaKeySession> PendingKeySessionsHashMap;
+typedef nsDataHashtable<nsUint32HashKey, uint32_t> PendingPromiseIdTokenHashMap;
+typedef uint32_t PromiseId;
+
+// This class is used on the main thread only.
+// Note: its addref/release is not (and can't be) thread safe!
+class MediaKeys final : public nsISupports,
+ public nsWrapperCache,
+ public SupportsWeakPtr<MediaKeys>
+{
+ ~MediaKeys();
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaKeys)
+ MOZ_DECLARE_WEAKREFERENCE_TYPENAME(MediaKeys)
+
+ MediaKeys(nsPIDOMWindowInner* aParentWindow,
+ const nsAString& aKeySystem,
+ const MediaKeySystemConfiguration& aConfig);
+
+ already_AddRefed<DetailedPromise> Init(ErrorResult& aRv);
+
+ nsPIDOMWindowInner* GetParentObject() const;
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ nsresult Bind(HTMLMediaElement* aElement);
+ void Unbind();
+
+ // Javascript: readonly attribute DOMString keySystem;
+ void GetKeySystem(nsString& retval) const;
+
+ // JavaScript: MediaKeys.createSession()
+ already_AddRefed<MediaKeySession> CreateSession(JSContext* aCx,
+ MediaKeySessionType aSessionType,
+ ErrorResult& aRv);
+
+ // JavaScript: MediaKeys.SetServerCertificate()
+ already_AddRefed<DetailedPromise>
+ SetServerCertificate(const ArrayBufferViewOrArrayBuffer& aServerCertificate,
+ ErrorResult& aRv);
+
+ already_AddRefed<MediaKeySession> GetSession(const nsAString& aSessionId);
+
+ // Removes and returns MediaKeySession from the set of sessions awaiting
+ // their sessionId to be assigned.
+ already_AddRefed<MediaKeySession> GetPendingSession(uint32_t aToken);
+
+ // Called once a Init() operation succeeds.
+ void OnCDMCreated(PromiseId aId,
+ const nsACString& aNodeId, const uint32_t aPluginId);
+
+ // Called once the CDM generates a sessionId while servicing a
+ // MediaKeySession.generateRequest() or MediaKeySession.load() call,
+ // once the sessionId of a MediaKeySession is known.
+ void OnSessionIdReady(MediaKeySession* aSession);
+
+ // Called once a LoadSession succeeds.
+ void OnSessionLoaded(PromiseId aId, bool aSuccess);
+
+ // Called once a session has closed.
+ void OnSessionClosed(MediaKeySession* aSession);
+
+ CDMProxy* GetCDMProxy() { return mProxy; }
+
+ // Makes a new promise, or nullptr on failure.
+ already_AddRefed<DetailedPromise> MakePromise(ErrorResult& aRv,
+ const nsACString& aName);
+ // Stores promise in mPromises, returning an ID that can be used to retrieve
+ // it later. The ID is passed to the CDM, so that it can signal specific
+ // promises to be resolved.
+ PromiseId StorePromise(DetailedPromise* aPromise);
+
+ // Stores a map from promise id to pending session token. Using this
+ // mapping, when a promise is rejected via its ID, we can check if the
+ // promise corresponds to a pending session and retrieve that session
+ // via the mapped-to token, and remove the pending session from the
+ // list of sessions awaiting a session id.
+ void ConnectPendingPromiseIdWithToken(PromiseId aId, uint32_t aToken);
+
+ // Reject promise with DOMException corresponding to aExceptionCode.
+ void RejectPromise(PromiseId aId, nsresult aExceptionCode,
+ const nsCString& aReason);
+ // Resolves promise with "undefined".
+ void ResolvePromise(PromiseId aId);
+
+ const nsCString& GetNodeId() const;
+
+ void Shutdown();
+
+ // Called by CDMProxy when CDM crashes or shuts down. It is different from
+ // Shutdown which is called from the script/dom side.
+ void Terminated();
+
+ // Returns true if this MediaKeys has been bound to a media element.
+ bool IsBoundToMediaElement() const;
+
+private:
+
+ // Instantiate CDMProxy instance.
+ // It could be MediaDrmCDMProxy (Widevine on Fennec) or GMPCDMProxy (the rest).
+ already_AddRefed<CDMProxy> CreateCDMProxy();
+
+ // Removes promise from mPromises, and returns it.
+ already_AddRefed<DetailedPromise> RetrievePromise(PromiseId aId);
+
+ // Owning ref to proxy. The proxy has a weak reference back to the MediaKeys,
+ // and the MediaKeys destructor clears the proxy's reference to the MediaKeys.
+ RefPtr<CDMProxy> mProxy;
+
+ RefPtr<HTMLMediaElement> mElement;
+
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ const nsString mKeySystem;
+ nsCString mNodeId;
+ KeySessionHashMap mKeySessions;
+ PromiseHashMap mPromises;
+ PendingKeySessionsHashMap mPendingSessions;
+ PromiseId mCreatePromiseId;
+
+ RefPtr<nsIPrincipal> mPrincipal;
+ RefPtr<nsIPrincipal> mTopLevelPrincipal;
+
+ const MediaKeySystemConfiguration mConfig;
+
+ PendingPromiseIdTokenHashMap mPromiseIdToken;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_mediakeys_h__
diff --git a/dom/media/eme/moz.build b/dom/media/eme/moz.build
new file mode 100644
index 000000000..7b1ad9d84
--- /dev/null
+++ b/dom/media/eme/moz.build
@@ -0,0 +1,41 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+EXPORTS.mozilla.dom += [
+ 'MediaEncryptedEvent.h',
+ 'MediaKeyError.h',
+ 'MediaKeyMessageEvent.h',
+ 'MediaKeys.h',
+ 'MediaKeySession.h',
+ 'MediaKeyStatusMap.h',
+ 'MediaKeySystemAccess.h',
+ 'MediaKeySystemAccessManager.h',
+]
+
+EXPORTS.mozilla += [
+ 'CDMCaps.h',
+ 'CDMProxy.h',
+ 'DecryptorProxyCallback.h',
+ 'DetailedPromise.h',
+ 'EMEUtils.h',
+]
+
+SOURCES += [
+ 'CDMCaps.cpp',
+ 'DetailedPromise.cpp',
+ 'EMEUtils.cpp',
+ 'MediaEncryptedEvent.cpp',
+ 'MediaKeyError.cpp',
+ 'MediaKeyMessageEvent.cpp',
+ 'MediaKeys.cpp',
+ 'MediaKeySession.cpp',
+ 'MediaKeyStatusMap.cpp',
+ 'MediaKeySystemAccess.cpp',
+ 'MediaKeySystemAccessManager.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/dom/media/fmp4/MP4Decoder.cpp b/dom/media/fmp4/MP4Decoder.cpp
index 5ad690625..7a340d829 100644
--- a/dom/media/fmp4/MP4Decoder.cpp
+++ b/dom/media/fmp4/MP4Decoder.cpp
@@ -1,4 +1,5 @@
/* -*- 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/. */
@@ -9,6 +10,9 @@
#include "MP4Demuxer.h"
#include "mozilla/Preferences.h"
#include "nsCharSeparatedTokenizer.h"
+#ifdef MOZ_EME
+#include "mozilla/CDMProxy.h"
+#endif
#include "mozilla/Logging.h"
#include "mozilla/SharedThreadPool.h"
#include "nsMimeTypes.h"
diff --git a/dom/media/gmp/GMPCDMCallbackProxy.cpp b/dom/media/gmp/GMPCDMCallbackProxy.cpp
new file mode 100644
index 000000000..a0b490849
--- /dev/null
+++ b/dom/media/gmp/GMPCDMCallbackProxy.cpp
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 8; 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 "GMPCDMCallbackProxy.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 "mozilla/EMEUtils.h"
+
+namespace mozilla {
+
+GMPCDMCallbackProxy::GMPCDMCallbackProxy(CDMProxy* aProxy)
+ : mProxy(aProxy)
+{}
+
+void
+GMPCDMCallbackProxy::SetDecryptorId(uint32_t aId)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy, aId] ()
+ {
+ proxy->OnSetDecryptorId(aId);
+ })
+ );}
+
+void
+GMPCDMCallbackProxy::SetSessionId(uint32_t aToken,
+ const nsCString& aSessionId)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy,
+ aToken,
+ sid] ()
+ {
+ proxy->OnSetSessionId(aToken, sid);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy, aPromiseId, aSuccess] ()
+ {
+ proxy->OnResolveLoadSessionPromise(aPromiseId, aSuccess);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::ResolvePromise(uint32_t aPromiseId)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ // Note: CDMProxy proxies this from non-main threads to main thread.
+ mProxy->ResolvePromise(aPromiseId);
+}
+
+void
+GMPCDMCallbackProxy::RejectPromise(uint32_t aPromiseId,
+ nsresult aException,
+ const nsCString& aMessage)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy,
+ aPromiseId,
+ aException,
+ aMessage] ()
+ {
+ proxy->OnRejectPromise(aPromiseId, aException, aMessage);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::SessionMessage(const nsCString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ nsTArray<uint8_t> msg(aMessage);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy,
+ sid,
+ aMessageType,
+ msg] () mutable
+ {
+ proxy->OnSessionMessage(sid, aMessageType, msg);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::ExpirationChange(const nsCString& aSessionId,
+ GMPTimestamp aExpiryTime)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy,
+ sid,
+ aExpiryTime] ()
+ {
+ proxy->OnExpirationChange(sid, aExpiryTime);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::SessionClosed(const nsCString& aSessionId)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ bool keyStatusesChange = false;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ {
+ CDMCaps::AutoLock caps(mProxy->Capabilites());
+ keyStatusesChange = caps.RemoveKeysForSession(NS_ConvertUTF8toUTF16(aSessionId));
+ }
+ if (keyStatusesChange) {
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy, sid] ()
+ {
+ proxy->OnKeyStatusesChange(sid);
+ })
+ );
+ }
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy, sid] ()
+ {
+ proxy->OnSessionClosed(sid);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::SessionError(const nsCString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsCString& aMessage)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ auto msg = NS_ConvertUTF8toUTF16(aMessage);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy,
+ sid,
+ aException,
+ aSystemCode,
+ msg] ()
+ {
+ proxy->OnSessionError(sid,
+ aException,
+ aSystemCode,
+ msg);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::BatchedKeyStatusChanged(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+ BatchedKeyStatusChangedInternal(aSessionId, aKeyInfos);
+}
+
+void
+GMPCDMCallbackProxy::BatchedKeyStatusChangedInternal(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos)
+{
+ bool keyStatusesChange = false;
+ {
+ CDMCaps::AutoLock caps(mProxy->Capabilites());
+ for (size_t i = 0; i < aKeyInfos.Length(); i++) {
+ keyStatusesChange |=
+ caps.SetKeyStatus(aKeyInfos[i].mKeyId,
+ NS_ConvertUTF8toUTF16(aSessionId),
+ aKeyInfos[i].mStatus);
+ }
+ }
+ if (keyStatusesChange) {
+ RefPtr<CDMProxy> proxy = mProxy;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy, sid] ()
+ {
+ proxy->OnKeyStatusesChange(sid);
+ })
+ );
+ }
+}
+
+void
+GMPCDMCallbackProxy::Decrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ mProxy->OnDecrypted(aId, aResult, aDecryptedData);
+}
+
+void
+GMPCDMCallbackProxy::Terminated()
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy] ()
+ {
+ proxy->Terminated();
+ })
+ );
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPCDMCallbackProxy.h b/dom/media/gmp/GMPCDMCallbackProxy.h
new file mode 100644
index 000000000..d2cc80682
--- /dev/null
+++ b/dom/media/gmp/GMPCDMCallbackProxy.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef GMPCDMCallbackProxy_h_
+#define GMPCDMCallbackProxy_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 GMPCDMCallbackProxy : public GMPDecryptorProxyCallback {
+public:
+
+ void SetDecryptorId(uint32_t aId) override;
+
+ void SetSessionId(uint32_t aCreateSessionToken,
+ const nsCString& aSessionId) override;
+
+ void ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess) override;
+
+ void ResolvePromise(uint32_t aPromiseId) override;
+
+ void RejectPromise(uint32_t aPromiseId,
+ nsresult aException,
+ const nsCString& aSessionId) override;
+
+ void SessionMessage(const nsCString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage) override;
+
+ void ExpirationChange(const nsCString& aSessionId,
+ UnixTime aExpiryTime) override;
+
+ void SessionClosed(const nsCString& aSessionId) override;
+
+ void SessionError(const nsCString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsCString& aMessage) override;
+
+ void Decrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData) override;
+
+ void BatchedKeyStatusChanged(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos) override;
+
+ void Terminated() override;
+
+ ~GMPCDMCallbackProxy() {}
+
+private:
+ friend class GMPCDMProxy;
+ explicit GMPCDMCallbackProxy(CDMProxy* aProxy);
+
+ void BatchedKeyStatusChangedInternal(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos);
+ // Warning: Weak ref.
+ CDMProxy* mProxy;
+};
+
+} // namespace mozilla
+
+#endif // GMPCDMCallbackProxy_h_
diff --git a/dom/media/gmp/GMPCDMProxy.cpp b/dom/media/gmp/GMPCDMProxy.cpp
new file mode 100644
index 000000000..0f1958632
--- /dev/null
+++ b/dom/media/gmp/GMPCDMProxy.cpp
@@ -0,0 +1,798 @@
+/* -*- Mode: C++; tab-width: 8; 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 "GMPCDMProxy.h"
+#include "mozilla/EMEUtils.h"
+#include "mozilla/PodOperations.h"
+
+#include "mozilla/dom/MediaKeys.h"
+#include "mozilla/dom/MediaKeySession.h"
+
+#include "mozIGeckoMediaPluginService.h"
+#include "nsContentCID.h"
+#include "nsIConsoleService.h"
+#include "nsPrintfCString.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "prenv.h"
+#include "GMPCDMCallbackProxy.h"
+#include "GMPService.h"
+#include "MainThreadUtils.h"
+#include "MediaData.h"
+
+namespace mozilla {
+
+GMPCDMProxy::GMPCDMProxy(dom::MediaKeys* aKeys,
+ const nsAString& aKeySystem,
+ GMPCrashHelper* aCrashHelper,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired)
+ : CDMProxy(aKeys,
+ aKeySystem,
+ aDistinctiveIdentifierRequired,
+ aPersistentStateRequired)
+ , mCrashHelper(aCrashHelper)
+ , mCDM(nullptr)
+ , mDecryptionJobCount(0)
+ , mShutdownCalled(false)
+ , mDecryptorId(0)
+ , mCreatePromiseId(0)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_COUNT_CTOR(GMPCDMProxy);
+}
+
+GMPCDMProxy::~GMPCDMProxy()
+{
+ MOZ_COUNT_DTOR(GMPCDMProxy);
+}
+
+void
+GMPCDMProxy::Init(PromiseId aPromiseId,
+ const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsing)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ EME_LOG("GMPCDMProxy::Init (%s, %s) %s",
+ NS_ConvertUTF16toUTF8(aOrigin).get(),
+ NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
+ (aInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"));
+
+ nsCString pluginVersion;
+ if (!mOwnerThread) {
+ nsCOMPtr<mozIGeckoMediaPluginService> mps =
+ do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ if (!mps) {
+ RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get MediaPluginService in GMPCDMProxy::Init"));
+ return;
+ }
+ mps->GetThread(getter_AddRefs(mOwnerThread));
+ if (!mOwnerThread) {
+ RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get GMP thread GMPCDMProxy::Init"));
+ return;
+ }
+ }
+
+ if (aGMPName.IsEmpty()) {
+ RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ nsPrintfCString("Unknown GMP for keysystem '%s'", NS_ConvertUTF16toUTF8(mKeySystem).get()));
+ return;
+ }
+
+ nsAutoPtr<InitData> data(new InitData());
+ data->mPromiseId = aPromiseId;
+ data->mOrigin = aOrigin;
+ data->mTopLevelOrigin = aTopLevelOrigin;
+ data->mGMPName = aGMPName;
+ data->mInPrivateBrowsing = aInPrivateBrowsing;
+ data->mCrashHelper = mCrashHelper;
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<InitData>>(this,
+ &GMPCDMProxy::gmp_Init,
+ Move(data)));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+#ifdef DEBUG
+bool
+GMPCDMProxy::IsOnOwnerThread()
+{
+ return NS_GetCurrentThread() == mOwnerThread;
+}
+#endif
+
+void
+GMPCDMProxy::gmp_InitDone(GMPDecryptorProxy* aCDM, nsAutoPtr<InitData>&& aData)
+{
+ EME_LOG("GMPCDMProxy::gmp_InitDone");
+ if (mShutdownCalled) {
+ if (aCDM) {
+ aCDM->Close();
+ }
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("GMPCDMProxy was shut down before init could complete"));
+ return;
+ }
+ if (!aCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("GetGMPDecryptor failed to return a CDM"));
+ return;
+ }
+
+ mCDM = aCDM;
+ mCallback = new GMPCDMCallbackProxy(this);
+ mCDM->Init(mCallback,
+ mDistinctiveIdentifierRequired,
+ mPersistentStateRequired);
+
+ // Await the OnSetDecryptorId callback.
+ mCreatePromiseId = aData->mPromiseId;
+}
+
+void GMPCDMProxy::OnSetDecryptorId(uint32_t aId)
+{
+ MOZ_ASSERT(mCreatePromiseId);
+ mDecryptorId = aId;
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<uint32_t>(this,
+ &GMPCDMProxy::OnCDMCreated,
+ mCreatePromiseId));
+ NS_DispatchToMainThread(task);
+}
+
+class gmp_InitDoneCallback : public GetGMPDecryptorCallback
+{
+public:
+ gmp_InitDoneCallback(GMPCDMProxy* aGMPCDMProxy,
+ nsAutoPtr<GMPCDMProxy::InitData>&& aData)
+ : mGMPCDMProxy(aGMPCDMProxy),
+ mData(Move(aData))
+ {
+ }
+
+ void Done(GMPDecryptorProxy* aCDM)
+ {
+ mGMPCDMProxy->gmp_InitDone(aCDM, Move(mData));
+ }
+
+private:
+ RefPtr<GMPCDMProxy> mGMPCDMProxy;
+ nsAutoPtr<GMPCDMProxy::InitData> mData;
+};
+
+class gmp_InitGetGMPDecryptorCallback : public GetNodeIdCallback
+{
+public:
+ gmp_InitGetGMPDecryptorCallback(GMPCDMProxy* aGMPCDMProxy,
+ nsAutoPtr<GMPCDMProxy::InitData>&& aData)
+ : mGMPCDMProxy(aGMPCDMProxy),
+ mData(aData)
+ {
+ }
+
+ void Done(nsresult aResult, const nsACString& aNodeId)
+ {
+ mGMPCDMProxy->gmp_InitGetGMPDecryptor(aResult, aNodeId, Move(mData));
+ }
+
+private:
+ RefPtr<GMPCDMProxy> mGMPCDMProxy;
+ nsAutoPtr<GMPCDMProxy::InitData> mData;
+};
+
+void
+GMPCDMProxy::gmp_Init(nsAutoPtr<InitData>&& aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ nsCOMPtr<mozIGeckoMediaPluginService> mps =
+ do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ if (!mps) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get MediaPluginService in GMPCDMProxy::gmp_Init"));
+ return;
+ }
+
+ // Make a copy before we transfer ownership of aData to the
+ // gmp_InitGetGMPDecryptorCallback.
+ InitData data(*aData);
+ UniquePtr<GetNodeIdCallback> callback(
+ new gmp_InitGetGMPDecryptorCallback(this, Move(aData)));
+ nsresult rv = mps->GetNodeId(data.mOrigin,
+ data.mTopLevelOrigin,
+ data.mGMPName,
+ data.mInPrivateBrowsing,
+ Move(callback));
+ if (NS_FAILED(rv)) {
+ RejectPromise(data.mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Call to GetNodeId() failed early"));
+ }
+}
+
+void
+GMPCDMProxy::gmp_InitGetGMPDecryptor(nsresult aResult,
+ const nsACString& aNodeId,
+ nsAutoPtr<InitData>&& aData)
+{
+ uint32_t promiseID = aData->mPromiseId;
+ if (NS_FAILED(aResult)) {
+ RejectPromise(promiseID, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("GetNodeId() called back, but with a failure result"));
+ return;
+ }
+
+ mNodeId = aNodeId;
+ MOZ_ASSERT(!GetNodeId().IsEmpty());
+
+ nsCOMPtr<mozIGeckoMediaPluginService> mps =
+ do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ if (!mps) {
+ RejectPromise(promiseID, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get MediaPluginService in GMPCDMProxy::gmp_InitGetGMPDecryptor"));
+ return;
+ }
+
+ EME_LOG("GMPCDMProxy::gmp_Init (%s, %s) %s NodeId=%s",
+ NS_ConvertUTF16toUTF8(aData->mOrigin).get(),
+ NS_ConvertUTF16toUTF8(aData->mTopLevelOrigin).get(),
+ (aData->mInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"),
+ GetNodeId().get());
+
+ nsTArray<nsCString> tags;
+ tags.AppendElement(NS_ConvertUTF16toUTF8(mKeySystem));
+
+ // Note: must capture helper refptr here, before the Move()
+ // when we create the GetGMPDecryptorCallback below.
+ RefPtr<GMPCrashHelper> crashHelper = Move(aData->mCrashHelper);
+ UniquePtr<GetGMPDecryptorCallback> callback(new gmp_InitDoneCallback(this,
+ Move(aData)));
+ nsresult rv = mps->GetGMPDecryptor(crashHelper,
+ &tags,
+ GetNodeId(),
+ Move(callback));
+ if (NS_FAILED(rv)) {
+ RejectPromise(promiseID, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Call to GetGMPDecryptor() failed early"));
+ }
+}
+
+void
+GMPCDMProxy::OnCDMCreated(uint32_t aPromiseId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ MOZ_ASSERT(!GetNodeId().IsEmpty());
+ if (mCDM) {
+ mKeys->OnCDMCreated(aPromiseId, GetNodeId(), mCDM->GetPluginId());
+ } else {
+ // No CDM? Just reject the promise.
+ mKeys->RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in OnCDMCreated()"));
+ }
+}
+
+void
+GMPCDMProxy::CreateSession(uint32_t aCreateSessionToken,
+ dom::MediaKeySessionType aSessionType,
+ PromiseId aPromiseId,
+ const nsAString& aInitDataType,
+ nsTArray<uint8_t>& aInitData)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+
+ nsAutoPtr<CreateSessionData> data(new CreateSessionData());
+ data->mSessionType = aSessionType;
+ data->mCreateSessionToken = aCreateSessionToken;
+ data->mPromiseId = aPromiseId;
+ data->mInitDataType = NS_ConvertUTF16toUTF8(aInitDataType);
+ data->mInitData = Move(aInitData);
+
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<CreateSessionData>>(this, &GMPCDMProxy::gmp_CreateSession, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+GMPSessionType
+ToGMPSessionType(dom::MediaKeySessionType aSessionType) {
+ switch (aSessionType) {
+ case dom::MediaKeySessionType::Temporary: return kGMPTemporySession;
+ case dom::MediaKeySessionType::Persistent_license: return kGMPPersistentSession;
+ default: return kGMPTemporySession;
+ };
+};
+
+void
+GMPCDMProxy::gmp_CreateSession(nsAutoPtr<CreateSessionData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_CreateSession"));
+ return;
+ }
+ mCDM->CreateSession(aData->mCreateSessionToken,
+ aData->mPromiseId,
+ aData->mInitDataType,
+ aData->mInitData,
+ ToGMPSessionType(aData->mSessionType));
+}
+
+void
+GMPCDMProxy::LoadSession(PromiseId aPromiseId,
+ const nsAString& aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+
+ nsAutoPtr<SessionOpData> data(new SessionOpData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<SessionOpData>>(this, &GMPCDMProxy::gmp_LoadSession, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+GMPCDMProxy::gmp_LoadSession(nsAutoPtr<SessionOpData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_LoadSession"));
+ return;
+ }
+ mCDM->LoadSession(aData->mPromiseId, aData->mSessionId);
+}
+
+void
+GMPCDMProxy::SetServerCertificate(PromiseId aPromiseId,
+ nsTArray<uint8_t>& aCert)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+
+ nsAutoPtr<SetServerCertificateData> data(new SetServerCertificateData());
+ data->mPromiseId = aPromiseId;
+ data->mCert = Move(aCert);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<SetServerCertificateData>>(this, &GMPCDMProxy::gmp_SetServerCertificate, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+GMPCDMProxy::gmp_SetServerCertificate(nsAutoPtr<SetServerCertificateData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_SetServerCertificate"));
+ return;
+ }
+ mCDM->SetServerCertificate(aData->mPromiseId, aData->mCert);
+}
+
+void
+GMPCDMProxy::UpdateSession(const nsAString& aSessionId,
+ PromiseId aPromiseId,
+ nsTArray<uint8_t>& aResponse)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ nsAutoPtr<UpdateSessionData> data(new UpdateSessionData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+ data->mResponse = Move(aResponse);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<UpdateSessionData>>(this, &GMPCDMProxy::gmp_UpdateSession, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+GMPCDMProxy::gmp_UpdateSession(nsAutoPtr<UpdateSessionData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_UpdateSession"));
+ return;
+ }
+ mCDM->UpdateSession(aData->mPromiseId,
+ aData->mSessionId,
+ aData->mResponse);
+}
+
+void
+GMPCDMProxy::CloseSession(const nsAString& aSessionId,
+ PromiseId aPromiseId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ nsAutoPtr<SessionOpData> data(new SessionOpData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<SessionOpData>>(this, &GMPCDMProxy::gmp_CloseSession, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+GMPCDMProxy::gmp_CloseSession(nsAutoPtr<SessionOpData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_CloseSession"));
+ return;
+ }
+ mCDM->CloseSession(aData->mPromiseId, aData->mSessionId);
+}
+
+void
+GMPCDMProxy::RemoveSession(const nsAString& aSessionId,
+ PromiseId aPromiseId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ nsAutoPtr<SessionOpData> data(new SessionOpData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<SessionOpData>>(this, &GMPCDMProxy::gmp_RemoveSession, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+GMPCDMProxy::gmp_RemoveSession(nsAutoPtr<SessionOpData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_RemoveSession"));
+ return;
+ }
+ mCDM->RemoveSession(aData->mPromiseId, aData->mSessionId);
+}
+
+void
+GMPCDMProxy::Shutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mKeys.Clear();
+ // Note: This may end up being the last owning reference to the GMPCDMProxy.
+ nsCOMPtr<nsIRunnable> task(NewRunnableMethod(this, &GMPCDMProxy::gmp_Shutdown));
+ if (mOwnerThread) {
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+ }
+}
+
+void
+GMPCDMProxy::gmp_Shutdown()
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ mShutdownCalled = true;
+
+ // Abort any pending decrypt jobs, to awaken any clients waiting on a job.
+ for (size_t i = 0; i < mDecryptionJobs.Length(); i++) {
+ DecryptJob* job = mDecryptionJobs[i];
+ job->PostResult(AbortedErr);
+ }
+ mDecryptionJobs.Clear();
+
+ if (mCDM) {
+ mCDM->Close();
+ mCDM = nullptr;
+ }
+}
+
+void
+GMPCDMProxy::RejectPromise(PromiseId aId, nsresult aCode,
+ const nsCString& aReason)
+{
+ if (NS_IsMainThread()) {
+ if (!mKeys.IsNull()) {
+ mKeys->RejectPromise(aId, aCode, aReason);
+ }
+ } else {
+ nsCOMPtr<nsIRunnable> task(new RejectPromiseTask(this, aId, aCode,
+ aReason));
+ NS_DispatchToMainThread(task);
+ }
+}
+
+void
+GMPCDMProxy::ResolvePromise(PromiseId aId)
+{
+ if (NS_IsMainThread()) {
+ if (!mKeys.IsNull()) {
+ mKeys->ResolvePromise(aId);
+ } else {
+ NS_WARNING("GMPCDMProxy unable to resolve promise!");
+ }
+ } else {
+ nsCOMPtr<nsIRunnable> task;
+ task = NewRunnableMethod<PromiseId>(this,
+ &GMPCDMProxy::ResolvePromise,
+ aId);
+ NS_DispatchToMainThread(task);
+ }
+}
+
+const nsCString&
+GMPCDMProxy::GetNodeId() const
+{
+ return mNodeId;
+}
+
+void
+GMPCDMProxy::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
+GMPCDMProxy::OnResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccess)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ mKeys->OnSessionLoaded(aPromiseId, aSuccess);
+}
+
+void
+GMPCDMProxy::OnSessionMessage(const nsAString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ 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
+GMPCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyStatusesChange();
+ }
+}
+
+void
+GMPCDMProxy::OnExpirationChange(const nsAString& aSessionId,
+ GMPTimestamp 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
+GMPCDMProxy::OnSessionClosed(const nsAString& aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->OnClosed();
+ }
+}
+
+void
+GMPCDMProxy::OnDecrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ gmp_Decrypted(aId, aResult, aDecryptedData);
+}
+
+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
+GMPCDMProxy::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);
+ }
+ LogToConsole(aMsg);
+}
+
+void
+GMPCDMProxy::OnRejectPromise(uint32_t aPromiseId,
+ nsresult aDOMException,
+ const nsCString& aMsg)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ RejectPromise(aPromiseId, aDOMException, aMsg);
+}
+
+const nsString&
+GMPCDMProxy::KeySystem() const
+{
+ return mKeySystem;
+}
+
+CDMCaps&
+GMPCDMProxy::Capabilites() {
+ return mCapabilites;
+}
+
+RefPtr<GMPCDMProxy::DecryptPromise>
+GMPCDMProxy::Decrypt(MediaRawData* aSample)
+{
+ RefPtr<DecryptJob> job(new DecryptJob(aSample));
+ RefPtr<DecryptPromise> promise(job->Ensure());
+
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<RefPtr<DecryptJob>>(this, &GMPCDMProxy::gmp_Decrypt, job));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+ return promise;
+}
+
+void
+GMPCDMProxy::gmp_Decrypt(RefPtr<DecryptJob> aJob)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ if (!mCDM) {
+ aJob->PostResult(AbortedErr);
+ return;
+ }
+
+ aJob->mId = ++mDecryptionJobCount;
+ nsTArray<uint8_t> data;
+ data.AppendElements(aJob->mSample->Data(), aJob->mSample->Size());
+ mCDM->Decrypt(aJob->mId, aJob->mSample->mCrypto, data);
+ mDecryptionJobs.AppendElement(aJob.forget());
+}
+
+void
+GMPCDMProxy::gmp_Decrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+#ifdef DEBUG
+ bool jobIdFound = false;
+#endif
+ for (size_t i = 0; i < mDecryptionJobs.Length(); i++) {
+ DecryptJob* job = mDecryptionJobs[i];
+ if (job->mId == aId) {
+#ifdef DEBUG
+ jobIdFound = true;
+#endif
+ job->PostResult(aResult, aDecryptedData);
+ mDecryptionJobs.RemoveElementAt(i);
+ }
+ }
+#ifdef DEBUG
+ if (!jobIdFound) {
+ NS_WARNING("GMPDecryptorChild returned incorrect job ID");
+ }
+#endif
+}
+
+void
+GMPCDMProxy::DecryptJob::PostResult(DecryptStatus aResult)
+{
+ nsTArray<uint8_t> empty;
+ PostResult(aResult, empty);
+}
+
+void
+GMPCDMProxy::DecryptJob::PostResult(DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData)
+{
+ if (aDecryptedData.Length() != mSample->Size()) {
+ NS_WARNING("CDM returned incorrect number of decrypted bytes");
+ }
+ if (aResult == Ok) {
+ nsAutoPtr<MediaRawDataWriter> writer(mSample->CreateWriter());
+ PodCopy(writer->Data(),
+ aDecryptedData.Elements(),
+ std::min<size_t>(aDecryptedData.Length(), mSample->Size()));
+ } else if (aResult == NoKeyErr) {
+ NS_WARNING("CDM returned NoKeyErr");
+ // We still have the encrypted sample, so we can re-enqueue it to be
+ // decrypted again once the key is usable again.
+ } else {
+ nsAutoCString str("CDM returned decode failure DecryptStatus=");
+ str.AppendInt(aResult);
+ NS_WARNING(str.get());
+ }
+ mPromise.Resolve(DecryptResult(aResult, mSample), __func__);
+}
+
+void
+GMPCDMProxy::GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
+ nsTArray<nsCString>& aSessionIds)
+{
+ CDMCaps::AutoLock caps(Capabilites());
+ caps.GetSessionIdsForKeyId(aKeyId, aSessionIds);
+}
+
+void
+GMPCDMProxy::Terminated()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_WARNING("CDM terminated");
+ if (mCreatePromiseId) {
+ RejectPromise(mCreatePromiseId,
+ NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ NS_LITERAL_CSTRING("Crashed waiting for CDM to initialize"));
+ mCreatePromiseId = 0;
+ }
+ if (!mKeys.IsNull()) {
+ mKeys->Terminated();
+ }
+}
+
+uint32_t
+GMPCDMProxy::GetDecryptorId()
+{
+ return mDecryptorId;
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPCDMProxy.h b/dom/media/gmp/GMPCDMProxy.h
new file mode 100644
index 000000000..a7fae235b
--- /dev/null
+++ b/dom/media/gmp/GMPCDMProxy.h
@@ -0,0 +1,265 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef GMPCDMProxy_h_
+#define GMPCDMProxy_h_
+
+#include "mozilla/CDMProxy.h"
+#include "GMPCDMCallbackProxy.h"
+#include "GMPDecryptorProxy.h"
+
+namespace mozilla {
+class MediaRawData;
+
+// Implementation of CDMProxy which is based on GMP architecture.
+class GMPCDMProxy : public CDMProxy {
+public:
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPCDMProxy, override)
+
+ typedef MozPromise<DecryptResult, DecryptResult, /* IsExclusive = */ true> DecryptPromise;
+
+ GMPCDMProxy(dom::MediaKeys* aKeys,
+ const nsAString& aKeySystem,
+ GMPCrashHelper* aCrashHelper,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired);
+
+ void Init(PromiseId aPromiseId,
+ const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsing) override;
+
+ void OnSetDecryptorId(uint32_t aId) override;
+
+ void CreateSession(uint32_t aCreateSessionToken,
+ dom::MediaKeySessionType aSessionType,
+ PromiseId aPromiseId,
+ const nsAString& aInitDataType,
+ nsTArray<uint8_t>& aInitData) override;
+
+ void LoadSession(PromiseId aPromiseId,
+ const nsAString& aSessionId) override;
+
+ void SetServerCertificate(PromiseId aPromiseId,
+ nsTArray<uint8_t>& aCert) override;
+
+ void UpdateSession(const nsAString& aSessionId,
+ PromiseId aPromiseId,
+ nsTArray<uint8_t>& aResponse) override;
+
+ void CloseSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) override;
+
+ void RemoveSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) override;
+
+ void Shutdown() override;
+
+ void Terminated() override;
+
+ const nsCString& GetNodeId() const override;
+
+ void OnSetSessionId(uint32_t aCreateSessionToken,
+ const nsAString& aSessionId) override;
+
+ void OnResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccess) override;
+
+ void OnSessionMessage(const nsAString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ nsTArray<uint8_t>& aMessage) override;
+
+ void OnExpirationChange(const nsAString& aSessionId,
+ GMPTimestamp aExpiryTime) override;
+
+ void OnSessionClosed(const nsAString& aSessionId) override;
+
+ void OnSessionError(const nsAString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsAString& aMsg) override;
+
+ void OnRejectPromise(uint32_t aPromiseId,
+ nsresult aDOMException,
+ const nsCString& aMsg) override;
+
+ RefPtr<DecryptPromise> Decrypt(MediaRawData* aSample) override;
+
+ void OnDecrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData) override;
+
+ void RejectPromise(PromiseId aId, nsresult aExceptionCode,
+ const nsCString& aReason) override;
+
+ void ResolvePromise(PromiseId aId) override;
+
+ const nsString& KeySystem() const override;
+
+ CDMCaps& Capabilites() override;
+
+ void OnKeyStatusesChange(const nsAString& aSessionId) override;
+
+ void GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
+ nsTArray<nsCString>& aSessionIds) override;
+
+#ifdef DEBUG
+ bool IsOnOwnerThread() override;
+#endif
+
+ uint32_t GetDecryptorId() override;
+
+private:
+ friend class gmp_InitDoneCallback;
+ friend class gmp_InitGetGMPDecryptorCallback;
+
+ struct InitData {
+ uint32_t mPromiseId;
+ nsString mOrigin;
+ nsString mTopLevelOrigin;
+ nsString mGMPName;
+ RefPtr<GMPCrashHelper> mCrashHelper;
+ bool mInPrivateBrowsing;
+ };
+
+ // GMP thread only.
+ void gmp_Init(nsAutoPtr<InitData>&& aData);
+ void gmp_InitDone(GMPDecryptorProxy* aCDM, nsAutoPtr<InitData>&& aData);
+ void gmp_InitGetGMPDecryptor(nsresult aResult,
+ const nsACString& aNodeId,
+ nsAutoPtr<InitData>&& aData);
+
+ // GMP thread only.
+ void gmp_Shutdown();
+
+ // Main thread only.
+ void OnCDMCreated(uint32_t aPromiseId);
+
+ struct CreateSessionData {
+ dom::MediaKeySessionType mSessionType;
+ uint32_t mCreateSessionToken;
+ PromiseId mPromiseId;
+ nsCString mInitDataType;
+ nsTArray<uint8_t> mInitData;
+ };
+ // GMP thread only.
+ void gmp_CreateSession(nsAutoPtr<CreateSessionData> aData);
+
+ struct SessionOpData {
+ PromiseId mPromiseId;
+ nsCString 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;
+ nsCString 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);
+
+ class DecryptJob {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecryptJob)
+
+ explicit DecryptJob(MediaRawData* aSample)
+ : mId(0)
+ , mSample(aSample)
+ {
+ }
+
+ void PostResult(DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData);
+ void PostResult(DecryptStatus aResult);
+
+ RefPtr<DecryptPromise> Ensure() {
+ return mPromise.Ensure(__func__);
+ }
+
+ uint32_t mId;
+ RefPtr<MediaRawData> mSample;
+ private:
+ ~DecryptJob() {}
+ MozPromiseHolder<DecryptPromise> mPromise;
+ };
+ // GMP thread only.
+ void gmp_Decrypt(RefPtr<DecryptJob> aJob);
+
+ // GMP thread only.
+ void gmp_Decrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData);
+
+ class RejectPromiseTask : public Runnable {
+ public:
+ RejectPromiseTask(GMPCDMProxy* aProxy,
+ PromiseId aId,
+ nsresult aCode,
+ const nsCString& aReason)
+ : mProxy(aProxy)
+ , mId(aId)
+ , mCode(aCode)
+ , mReason(aReason)
+ {
+ }
+ NS_IMETHOD Run() override {
+ mProxy->RejectPromise(mId, mCode, mReason);
+ return NS_OK;
+ }
+ private:
+ RefPtr<GMPCDMProxy> mProxy;
+ PromiseId mId;
+ nsresult mCode;
+ nsCString mReason;
+ };
+
+ ~GMPCDMProxy();
+
+ GMPCrashHelper* mCrashHelper;
+
+ GMPDecryptorProxy* mCDM;
+
+ nsAutoPtr<GMPCDMCallbackProxy> mCallback;
+
+ // Decryption jobs sent to CDM, awaiting result.
+ // GMP thread only.
+ nsTArray<RefPtr<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;
+
+ // True if GMPCDMProxy::gmp_Shutdown was called.
+ // GMP thread only.
+ bool mShutdownCalled;
+
+ uint32_t mDecryptorId;
+
+ PromiseId mCreatePromiseId;
+};
+
+
+} // namespace mozilla
+
+#endif // GMPCDMProxy_h_
diff --git a/dom/media/gmp/GMPChild.cpp b/dom/media/gmp/GMPChild.cpp
index 7941183b5..eb1803736 100644
--- a/dom/media/gmp/GMPChild.cpp
+++ b/dom/media/gmp/GMPChild.cpp
@@ -22,6 +22,9 @@
#include "GMPUtils.h"
#include "prio.h"
#include "base/task.h"
+#ifdef MOZ_EME
+#include "widevine-adapter/WidevineAdapter.h"
+#endif
using namespace mozilla::ipc;
@@ -251,7 +254,13 @@ GMPChild::AnswerStartPlugin(const nsString& aAdapter)
return false;
}
+#ifdef MOZ_EME
+ bool isWidevine = aAdapter.EqualsLiteral("widevine");
+
+ GMPAdapter* adapter = (isWidevine) ? new WidevineAdapter() : nullptr;
+#else
GMPAdapter* adapter = nullptr;
+#endif
if (!mGMPLoader->Load(libPath.get(),
libPath.Length(),
mNodeId.BeginWriting(),
diff --git a/dom/media/gmp/GMPDecryptorParent.cpp b/dom/media/gmp/GMPDecryptorParent.cpp
index f2a5d2431..56474e973 100644
--- a/dom/media/gmp/GMPDecryptorParent.cpp
+++ b/dom/media/gmp/GMPDecryptorParent.cpp
@@ -45,7 +45,13 @@ GMPDecryptorParent::~GMPDecryptorParent()
bool
GMPDecryptorParent::RecvSetDecryptorId(const uint32_t& aId)
{
- /* STUB */
+#ifdef MOZ_EME
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->SetDecryptorId(aId);
+#endif
return true;
}
@@ -200,7 +206,16 @@ bool
GMPDecryptorParent::RecvSetSessionId(const uint32_t& aCreateSessionId,
const nsCString& aSessionId)
{
- /* STUB */
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvSetSessionId(token=%u, sessionId='%s')",
+ this, aCreateSessionId, aSessionId.get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->SetSessionId(aCreateSessionId, aSessionId);
+#endif
return true;
}
@@ -208,14 +223,32 @@ bool
GMPDecryptorParent::RecvResolveLoadSessionPromise(const uint32_t& aPromiseId,
const bool& aSuccess)
{
- /* STUB */
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvResolveLoadSessionPromise(promiseId=%u)",
+ this, aPromiseId));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->ResolveLoadSessionPromise(aPromiseId, aSuccess);
+#endif
return true;
}
bool
GMPDecryptorParent::RecvResolvePromise(const uint32_t& aPromiseId)
{
- /* STUB */
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvResolvePromise(promiseId=%u)",
+ this, aPromiseId));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->ResolvePromise(aPromiseId);
+#endif
return true;
}
@@ -243,16 +276,47 @@ GMPDecryptorParent::RecvRejectPromise(const uint32_t& aPromiseId,
const GMPDOMException& aException,
const nsCString& aMessage)
{
- /* STUB */
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvRejectPromise(promiseId=%u, exception=%d, msg='%s')",
+ this, aPromiseId, aException, aMessage.get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->RejectPromise(aPromiseId, GMPExToNsresult(aException), aMessage);
+#endif
return true;
}
+#ifdef MOZ_EME
+static dom::MediaKeyMessageType
+ToMediaKeyMessageType(GMPSessionMessageType aMessageType) {
+ switch (aMessageType) {
+ case kGMPLicenseRequest: return dom::MediaKeyMessageType::License_request;
+ case kGMPLicenseRenewal: return dom::MediaKeyMessageType::License_renewal;
+ case kGMPLicenseRelease: return dom::MediaKeyMessageType::License_release;
+ case kGMPIndividualizationRequest: return dom::MediaKeyMessageType::Individualization_request;
+ default: return dom::MediaKeyMessageType::License_request;
+ };
+};
+#endif
+
bool
GMPDecryptorParent::RecvSessionMessage(const nsCString& aSessionId,
const GMPSessionMessageType& aMessageType,
nsTArray<uint8_t>&& aMessage)
{
- /* STUB */
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvSessionMessage(sessionId='%s', type=%d, msg='%s')",
+ this, aSessionId.get(), aMessageType, ToBase64(aMessage).get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->SessionMessage(aSessionId, ToMediaKeyMessageType(aMessageType), aMessage);
+#endif
return true;
}
@@ -260,14 +324,32 @@ bool
GMPDecryptorParent::RecvExpirationChange(const nsCString& aSessionId,
const double& aExpiryTime)
{
- /* STUB */
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvExpirationChange(sessionId='%s', expiry=%lf)",
+ this, aSessionId.get(), aExpiryTime));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->ExpirationChange(aSessionId, aExpiryTime);
+#endif
return true;
}
bool
GMPDecryptorParent::RecvSessionClosed(const nsCString& aSessionId)
{
- /* STUB */
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvSessionClosed(sessionId='%s')",
+ this, aSessionId.get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->SessionClosed(aSessionId);
+#endif
return true;
}
@@ -277,24 +359,95 @@ GMPDecryptorParent::RecvSessionError(const nsCString& aSessionId,
const uint32_t& aSystemCode,
const nsCString& aMessage)
{
- /* STUB */
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvSessionError(sessionId='%s', exception=%d, sysCode=%d, msg='%s')",
+ this, aSessionId.get(),
+ aException, aSystemCode, aMessage.get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->SessionError(aSessionId,
+ GMPExToNsresult(aException),
+ aSystemCode,
+ aMessage);
+#endif
return true;
}
+#ifdef MOZ_EME
+static dom::MediaKeyStatus
+ToMediaKeyStatus(GMPMediaKeyStatus aStatus) {
+ switch (aStatus) {
+ case kGMPUsable: return dom::MediaKeyStatus::Usable;
+ case kGMPExpired: return dom::MediaKeyStatus::Expired;
+ case kGMPOutputDownscaled: return dom::MediaKeyStatus::Output_downscaled;
+ case kGMPOutputRestricted: return dom::MediaKeyStatus::Output_restricted;
+ case kGMPInternalError: return dom::MediaKeyStatus::Internal_error;
+ case kGMPReleased: return dom::MediaKeyStatus::Released;
+ case kGMPStatusPending: return dom::MediaKeyStatus::Status_pending;
+ default: return dom::MediaKeyStatus::Internal_error;
+ }
+}
+#endif
+
bool
GMPDecryptorParent::RecvBatchedKeyStatusChanged(const nsCString& aSessionId,
InfallibleTArray<GMPKeyInformation>&& aKeyInfos)
{
- /* STUB */
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvBatchedKeyStatusChanged(sessionId='%s', KeyInfos len='%d')",
+ this, aSessionId.get(), aKeyInfos.Length()));
+
+ if (mIsOpen) {
+ nsTArray<CDMKeyInfo> cdmKeyInfos(aKeyInfos.Length());
+ for (uint32_t i = 0; i < aKeyInfos.Length(); i++) {
+ LOGD(("GMPDecryptorParent[%p]::RecvBatchedKeyStatusChanged(keyId=%s, gmp-status=%d)",
+ this, ToBase64(aKeyInfos[i].keyId()).get(), aKeyInfos[i].status()));
+ // If the status is kGMPUnknown, we're going to forget(remove) that key info.
+ if (aKeyInfos[i].status() != kGMPUnknown) {
+ auto status = ToMediaKeyStatus(aKeyInfos[i].status());
+ cdmKeyInfos.AppendElement(CDMKeyInfo(aKeyInfos[i].keyId(),
+ dom::Optional<dom::MediaKeyStatus>(status)));
+ } else {
+ cdmKeyInfos.AppendElement(CDMKeyInfo(aKeyInfos[i].keyId()));
+ }
+ }
+ mCallback->BatchedKeyStatusChanged(aSessionId, cdmKeyInfos);
+ }
+#endif
return true;
}
+#ifdef MOZ_EME
+DecryptStatus
+ToDecryptStatus(GMPErr aError)
+{
+ switch (aError) {
+ case GMPNoErr: return Ok;
+ case GMPNoKeyErr: return NoKeyErr;
+ case GMPAbortedErr: return AbortedErr;
+ default: return GenericErr;
+ }
+}
+#endif
+
bool
GMPDecryptorParent::RecvDecrypted(const uint32_t& aId,
const GMPErr& aErr,
InfallibleTArray<uint8_t>&& aBuffer)
{
- /* STUB */
+#ifdef MOZ_EME
+ LOGV(("GMPDecryptorParent[%p]::RecvDecrypted(id=%d, err=%d)",
+ this, aId, aErr));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->Decrypted(aId, ToDecryptStatus(aErr), aBuffer);
+#endif
return true;
}
diff --git a/dom/media/gmp/GMPDecryptorProxy.h b/dom/media/gmp/GMPDecryptorProxy.h
index f9e34a45f..ed16755f8 100644
--- a/dom/media/gmp/GMPDecryptorProxy.h
+++ b/dom/media/gmp/GMPDecryptorProxy.h
@@ -6,6 +6,9 @@
#ifndef GMPDecryptorProxy_h_
#define GMPDecryptorProxy_h_
+#ifdef MOZ_EME
+#include "mozilla/DecryptorProxyCallback.h"
+#endif
#include "GMPCallbackBase.h"
#include "gmp-decryption.h"
#include "nsString.h"
@@ -14,7 +17,12 @@ namespace mozilla {
class CryptoSample;
} // namespace mozilla
+#ifdef MOZ_EME
+class GMPDecryptorProxyCallback : public DecryptorProxyCallback,
+ public GMPCallbackBase {
+#else
class GMPDecryptorProxyCallback : public GMPCallbackBase {
+#endif
public:
virtual ~GMPDecryptorProxyCallback() {}
diff --git a/dom/media/gmp/GMPParent.cpp b/dom/media/gmp/GMPParent.cpp
index 11dbc6b96..e6f797264 100644
--- a/dom/media/gmp/GMPParent.cpp
+++ b/dom/media/gmp/GMPParent.cpp
@@ -28,6 +28,11 @@ using mozilla::ipc::GeckoChildProcessHost;
#include "WMFDecoderModule.h"
#endif
+#ifdef MOZ_EME
+#include "mozilla/dom/WidevineCDMManifestBinding.h"
+#include "widevine-adapter/WidevineAdapter.h"
+#endif
+
namespace mozilla {
#undef LOG
@@ -649,7 +654,18 @@ GMPParent::ReadGMPMetaData()
return ReadGMPInfoFile(infoFile);
}
+#ifdef MOZ_EME
+ // Maybe this is the Widevine adapted plugin?
+ nsCOMPtr<nsIFile> manifestFile;
+ rv = mDirectory->Clone(getter_AddRefs(manifestFile));
+ if (NS_FAILED(rv)) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+ manifestFile->AppendRelativePath(NS_LITERAL_STRING("manifest.json"));
+ return ReadChromiumManifestFile(manifestFile);
+#else
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+#endif
}
RefPtr<GenericPromise>
@@ -742,9 +758,48 @@ GMPParent::ReadChromiumManifestFile(nsIFile* aFile)
RefPtr<GenericPromise>
GMPParent::ParseChromiumManifest(nsString aJSON)
{
- // TODO: Remove This.
+#ifdef MOZ_EME
+ LOGD("%s: for '%s'", __FUNCTION__, NS_LossyConvertUTF16toASCII(aJSON).get());
+
+ MOZ_ASSERT(NS_IsMainThread());
+ mozilla::dom::WidevineCDMManifest m;
+ if (!m.Init(aJSON)) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ nsresult ignored; // Note: ToInteger returns 0 on failure.
+ if (!WidevineAdapter::Supports(m.mX_cdm_module_versions.ToInteger(&ignored),
+ m.mX_cdm_interface_versions.ToInteger(&ignored),
+ m.mX_cdm_host_versions.ToInteger(&ignored))) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ mDisplayName = NS_ConvertUTF16toUTF8(m.mName);
+ mDescription = NS_ConvertUTF16toUTF8(m.mDescription);
+ mVersion = NS_ConvertUTF16toUTF8(m.mVersion);
+
+ GMPCapability video(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER));
+ video.mAPITags.AppendElement(NS_LITERAL_CSTRING("h264"));
+ video.mAPITags.AppendElement(NS_LITERAL_CSTRING("vp8"));
+ video.mAPITags.AppendElement(NS_LITERAL_CSTRING("vp9"));
+ video.mAPITags.AppendElement(kEMEKeySystemWidevine);
+ mCapabilities.AppendElement(Move(video));
+
+ GMPCapability decrypt(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR));
+ decrypt.mAPITags.AppendElement(kEMEKeySystemWidevine);
+ mCapabilities.AppendElement(Move(decrypt));
+
+ MOZ_ASSERT(mName.EqualsLiteral("widevinecdm"));
+ mAdapter = NS_LITERAL_STRING("widevine");
+#ifdef XP_WIN
+ mLibs = NS_LITERAL_CSTRING("dxva2.dll");
+#endif
+
+ return GenericPromise::CreateAndResolve(true, __func__);
+#else // !MOZ_EME
MOZ_ASSERT_UNREACHABLE("don't call me if EME isn't enabled");
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+#endif // !MOZ_EME
}
bool
diff --git a/dom/media/gmp/moz.build b/dom/media/gmp/moz.build
index 3ff90b2c8..52dc673cd 100644
--- a/dom/media/gmp/moz.build
+++ b/dom/media/gmp/moz.build
@@ -70,6 +70,12 @@ EXPORTS += [
'GMPVideoPlaneImpl.h',
]
+if CONFIG['MOZ_EME']:
+ EXPORTS += [
+ 'GMPCDMCallbackProxy.h',
+ 'GMPCDMProxy.h',
+ ]
+
SOURCES += [
'GMPAudioDecoderChild.cpp',
'GMPAudioDecoderParent.cpp',
@@ -105,8 +111,17 @@ SOURCES += [
'GMPVideoPlaneImpl.cpp',
]
+if CONFIG['MOZ_EME']:
+ SOURCES += [
+ 'GMPCDMCallbackProxy.cpp',
+ 'GMPCDMProxy.cpp',
+ ]
+
DIRS += ['rlz']
+if CONFIG['MOZ_EME']:
+ DIRS += ['widevine-adapter']
+
IPDL_SOURCES += [
'GMPTypes.ipdlh',
'PGMP.ipdl',
diff --git a/dom/media/gmp/widevine-adapter/WidevineAdapter.cpp b/dom/media/gmp/widevine-adapter/WidevineAdapter.cpp
new file mode 100644
index 000000000..57d4ecec2
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineAdapter.cpp
@@ -0,0 +1,168 @@
+/* -*- 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 "WidevineAdapter.h"
+#include "content_decryption_module.h"
+#include "VideoUtils.h"
+#include "WidevineDecryptor.h"
+#include "WidevineUtils.h"
+#include "WidevineVideoDecoder.h"
+#include "gmp-api/gmp-entrypoints.h"
+#include "gmp-api/gmp-decryption.h"
+#include "gmp-api/gmp-video-codec.h"
+#include "gmp-api/gmp-platform.h"
+
+static const GMPPlatformAPI* sPlatform = nullptr;
+
+namespace mozilla {
+
+GMPErr GMPGetCurrentTime(GMPTimestamp* aOutTime) {
+ return sPlatform->getcurrenttime(aOutTime);
+}
+
+// Call on main thread only.
+GMPErr GMPSetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS) {
+ return sPlatform->settimer(aTask, aTimeoutMS);
+}
+
+GMPErr GMPCreateRecord(const char* aRecordName,
+ uint32_t aRecordNameSize,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient)
+{
+ return sPlatform->createrecord(aRecordName, aRecordNameSize, aOutRecord, aClient);
+}
+
+void
+WidevineAdapter::SetAdaptee(PRLibrary* aLib)
+{
+ mLib = aLib;
+}
+
+void* GetCdmHost(int aHostInterfaceVersion, void* aUserData)
+{
+ Log("GetCdmHostFunc(%d, %p)", aHostInterfaceVersion, aUserData);
+ WidevineDecryptor* decryptor = reinterpret_cast<WidevineDecryptor*>(aUserData);
+ MOZ_ASSERT(decryptor);
+ return static_cast<cdm::Host_9*>(decryptor);
+}
+
+#define STRINGIFY(s) _STRINGIFY(s)
+#define _STRINGIFY(s) #s
+
+GMPErr
+WidevineAdapter::GMPInit(const GMPPlatformAPI* aPlatformAPI)
+{
+#ifdef ENABLE_WIDEVINE_LOG
+ if (getenv("GMP_LOG_FILE")) {
+ // Clear log file.
+ FILE* f = fopen(getenv("GMP_LOG_FILE"), "w");
+ if (f) {
+ fclose(f);
+ }
+ }
+#endif
+
+ sPlatform = aPlatformAPI;
+ if (!mLib) {
+ return GMPGenericErr;
+ }
+
+ auto init = reinterpret_cast<decltype(::INITIALIZE_CDM_MODULE)*>(
+ PR_FindFunctionSymbol(mLib, STRINGIFY(INITIALIZE_CDM_MODULE)));
+ if (!init) {
+ return GMPGenericErr;
+ }
+
+ Log(STRINGIFY(INITIALIZE_CDM_MODULE)"()");
+ init();
+
+ return GMPNoErr;
+}
+
+GMPErr
+WidevineAdapter::GMPGetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId)
+{
+ Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p",
+ aAPIName, aHostAPI, aPluginAPI, this, aDecryptorId);
+ if (!strcmp(aAPIName, GMP_API_DECRYPTOR)) {
+ if (WidevineDecryptor::GetInstance(aDecryptorId)) {
+ // We only support one CDM instance per PGMPDecryptor. Fail!
+ Log("WidevineAdapter::GMPGetAPI() Tried to create more than once CDM per IPDL actor! FAIL!");
+ return GMPQuotaExceededErr;
+ }
+ auto create = reinterpret_cast<decltype(::CreateCdmInstance)*>(
+ PR_FindFunctionSymbol(mLib, "CreateCdmInstance"));
+ if (!create) {
+ Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p FAILED to find CreateCdmInstance",
+ aAPIName, aHostAPI, aPluginAPI, this, aDecryptorId);
+ return GMPGenericErr;
+ }
+
+ WidevineDecryptor* decryptor = new WidevineDecryptor();
+
+ auto cdm = reinterpret_cast<cdm::ContentDecryptionModule_9*>(
+ create(cdm::ContentDecryptionModule_9::kVersion,
+ kEMEKeySystemWidevine.get(),
+ kEMEKeySystemWidevine.Length(),
+ &GetCdmHost,
+ decryptor));
+ if (!cdm) {
+ Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p FAILED to create cdm",
+ aAPIName, aHostAPI, aPluginAPI, this, aDecryptorId);
+ return GMPGenericErr;
+ }
+ Log("cdm: 0x%x", cdm);
+ RefPtr<CDMWrapper> wrapper(new CDMWrapper(cdm, decryptor));
+ decryptor->SetCDM(wrapper, aDecryptorId);
+ *aPluginAPI = decryptor;
+
+ } else if (!strcmp(aAPIName, GMP_API_VIDEO_DECODER)) {
+ RefPtr<CDMWrapper> wrapper = WidevineDecryptor::GetInstance(aDecryptorId);
+ if (!wrapper) {
+ Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p No cdm for video decoder",
+ aAPIName, aHostAPI, aPluginAPI, thiss, aDecryptorId);
+ return GMPGenericErr;
+ }
+ *aPluginAPI = new WidevineVideoDecoder(static_cast<GMPVideoHost*>(aHostAPI),
+ wrapper);
+ }
+ return *aPluginAPI ? GMPNoErr : GMPNotImplementedErr;
+}
+
+void
+WidevineAdapter::GMPShutdown()
+{
+ Log("WidevineAdapter::GMPShutdown()");
+
+ decltype(::DeinitializeCdmModule)* deinit;
+ deinit = (decltype(deinit))(PR_FindFunctionSymbol(mLib, "DeinitializeCdmModule"));
+ if (deinit) {
+ Log("DeinitializeCdmModule()");
+ deinit();
+ }
+}
+
+void
+WidevineAdapter::GMPSetNodeId(const char* aNodeId, uint32_t aLength)
+{
+
+}
+
+/* static */
+bool
+WidevineAdapter::Supports(int32_t aModuleVersion,
+ int32_t aInterfaceVersion,
+ int32_t aHostVersion)
+{
+ return aModuleVersion == CDM_MODULE_VERSION &&
+ aInterfaceVersion == cdm::ContentDecryptionModule_9::kVersion &&
+ aHostVersion == cdm::Host_9::kVersion;
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineAdapter.h b/dom/media/gmp/widevine-adapter/WidevineAdapter.h
new file mode 100644
index 000000000..714e041be
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineAdapter.h
@@ -0,0 +1,59 @@
+/* -*- 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/. */
+
+#ifndef WidevineAdapter_h_
+#define WidevineAdapter_h_
+
+#include "GMPLoader.h"
+#include "prlink.h"
+#include "GMPUtils.h"
+
+struct GMPPlatformAPI;
+
+namespace mozilla {
+
+class WidevineAdapter : public gmp::GMPAdapter {
+public:
+
+ void SetAdaptee(PRLibrary* aLib) override;
+
+ // These are called in place of the corresponding GMP API functions.
+ GMPErr GMPInit(const GMPPlatformAPI* aPlatformAPI) override;
+ GMPErr GMPGetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId) override;
+ void GMPShutdown() override;
+ void GMPSetNodeId(const char* aNodeId, uint32_t aLength) override;
+
+ static bool Supports(int32_t aModuleVersion,
+ int32_t aInterfaceVersion,
+ int32_t aHostVersion);
+
+private:
+ PRLibrary* mLib = nullptr;
+};
+
+GMPErr GMPCreateThread(GMPThread** aThread);
+GMPErr GMPRunOnMainThread(GMPTask* aTask);
+GMPErr GMPCreateMutex(GMPMutex** aMutex);
+
+// Call on main thread only.
+GMPErr GMPCreateRecord(const char* aRecordName,
+ uint32_t aRecordNameSize,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient);
+
+// Call on main thread only.
+GMPErr GMPSetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS);
+
+GMPErr GMPGetCurrentTime(GMPTimestamp* aOutTime);
+
+GMPErr GMPCreateRecordIterator(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+ void* aUserArg);
+
+} // namespace mozilla
+
+#endif // WidevineAdapter_h_
diff --git a/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp b/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
new file mode 100644
index 000000000..4d3408804
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
@@ -0,0 +1,554 @@
+/* -*- 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 "WidevineDecryptor.h"
+
+#include "WidevineAdapter.h"
+#include "WidevineUtils.h"
+#include "WidevineFileIO.h"
+#include <mozilla/SizePrintfMacros.h>
+#include <stdarg.h>
+#include "base/time.h"
+
+using namespace cdm;
+using namespace std;
+
+namespace mozilla {
+
+static map<uint32_t, RefPtr<CDMWrapper>> sDecryptors;
+
+/* static */
+RefPtr<CDMWrapper>
+WidevineDecryptor::GetInstance(uint32_t aInstanceId)
+{
+ auto itr = sDecryptors.find(aInstanceId);
+ if (itr != sDecryptors.end()) {
+ return itr->second;
+ }
+ return nullptr;
+}
+
+
+WidevineDecryptor::WidevineDecryptor()
+ : mCallback(nullptr)
+{
+ Log("WidevineDecryptor created this=%p", this);
+ AddRef(); // Released in DecryptingComplete().
+}
+
+WidevineDecryptor::~WidevineDecryptor()
+{
+ Log("WidevineDecryptor destroyed this=%p", this);
+}
+
+void
+WidevineDecryptor::SetCDM(RefPtr<CDMWrapper> aCDM, uint32_t aInstanceId)
+{
+ mCDM = aCDM;
+ mInstanceId = aInstanceId;
+ sDecryptors[mInstanceId] = aCDM;
+}
+
+void
+WidevineDecryptor::Init(GMPDecryptorCallback* aCallback,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired)
+{
+ Log("WidevineDecryptor::Init() this=%p distinctiveId=%d persistentState=%d",
+ this, aDistinctiveIdentifierRequired, aPersistentStateRequired);
+ MOZ_ASSERT(aCallback);
+ mCallback = aCallback;
+ MOZ_ASSERT(mCDM);
+ mDistinctiveIdentifierRequired = aDistinctiveIdentifierRequired;
+ mPersistentStateRequired = aPersistentStateRequired;
+ if (CDM()) {
+ CDM()->Initialize(aDistinctiveIdentifierRequired,
+ aPersistentStateRequired);
+ }
+}
+
+static SessionType
+ToCDMSessionType(GMPSessionType aSessionType)
+{
+ switch (aSessionType) {
+ case kGMPTemporySession: return kTemporary;
+ case kGMPPersistentSession: return kPersistentLicense;
+ case kGMPSessionInvalid: return kTemporary;
+ // TODO: kPersistentKeyRelease
+ }
+ MOZ_ASSERT(false); // Not supposed to get here.
+ return kTemporary;
+}
+
+void
+WidevineDecryptor::CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const char* aInitDataType,
+ uint32_t aInitDataTypeSize,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ GMPSessionType aSessionType)
+{
+ Log("Decryptor::CreateSession(token=%d, pid=%d)", aCreateSessionToken, aPromiseId);
+ InitDataType initDataType;
+ if (!strcmp(aInitDataType, "cenc")) {
+ initDataType = kCenc;
+ } else if (!strcmp(aInitDataType, "webm")) {
+ initDataType = kWebM;
+ } else if (!strcmp(aInitDataType, "keyids")) {
+ initDataType = kKeyIds;
+ } else {
+ // Invalid init data type
+ const char* errorMsg = "Invalid init data type when creating session.";
+ OnRejectPromise(aPromiseId, kExceptionNotSupportedError, 0, errorMsg, sizeof(errorMsg));
+ return;
+ }
+ mPromiseIdToNewSessionTokens[aPromiseId] = aCreateSessionToken;
+ CDM()->CreateSessionAndGenerateRequest(aPromiseId,
+ ToCDMSessionType(aSessionType),
+ initDataType,
+ aInitData, aInitDataSize);
+}
+
+void
+WidevineDecryptor::LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ Log("Decryptor::LoadSession(pid=%d, %s)", aPromiseId, aSessionId);
+ // TODO: session type??
+ CDM()->LoadSession(aPromiseId, kPersistentLicense, aSessionId, aSessionIdLength);
+}
+
+void
+WidevineDecryptor::UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize)
+{
+ Log("Decryptor::UpdateSession(pid=%d, session=%s)", aPromiseId, aSessionId);
+ CDM()->UpdateSession(aPromiseId, aSessionId, aSessionIdLength, aResponse, aResponseSize);
+}
+
+void
+WidevineDecryptor::CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ Log("Decryptor::CloseSession(pid=%d, session=%s)", aPromiseId, aSessionId);
+ CDM()->CloseSession(aPromiseId, aSessionId, aSessionIdLength);
+}
+
+void
+WidevineDecryptor::RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ Log("Decryptor::RemoveSession(%s)", aSessionId);
+ CDM()->RemoveSession(aPromiseId, aSessionId, aSessionIdLength);
+}
+
+void
+WidevineDecryptor::SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize)
+{
+ Log("Decryptor::SetServerCertificate()");
+ CDM()->SetServerCertificate(aPromiseId, aServerCert, aServerCertSize);
+}
+
+class WidevineDecryptedBlock : public cdm::DecryptedBlock {
+public:
+
+ WidevineDecryptedBlock()
+ : mBuffer(nullptr)
+ , mTimestamp(0)
+ {
+ }
+
+ ~WidevineDecryptedBlock() {
+ if (mBuffer) {
+ mBuffer->Destroy();
+ mBuffer = nullptr;
+ }
+ }
+
+ void SetDecryptedBuffer(cdm::Buffer* aBuffer) override {
+ mBuffer = aBuffer;
+ }
+
+ cdm::Buffer* DecryptedBuffer() override {
+ return mBuffer;
+ }
+
+ void SetTimestamp(int64_t aTimestamp) override {
+ mTimestamp = aTimestamp;
+ }
+
+ int64_t Timestamp() const override {
+ return mTimestamp;
+ }
+
+private:
+ cdm::Buffer* mBuffer;
+ int64_t mTimestamp;
+};
+
+void
+WidevineDecryptor::Decrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata)
+{
+ if (!mCallback) {
+ Log("WidevineDecryptor::Decrypt() this=%p FAIL; !mCallback", this);
+ return;
+ }
+ const GMPEncryptedBufferMetadata* crypto = aMetadata;
+ InputBuffer sample;
+ nsTArray<SubsampleEntry> subsamples;
+ InitInputBuffer(crypto, aBuffer->Id(), aBuffer->Data(), aBuffer->Size(), sample, subsamples);
+ WidevineDecryptedBlock decrypted;
+ Status rv = CDM()->Decrypt(sample, &decrypted);
+ Log("Decryptor::Decrypt(timestamp=%lld) rv=%d sz=%d",
+ sample.timestamp, rv, decrypted.DecryptedBuffer()->Size());
+ if (rv == kSuccess) {
+ aBuffer->Resize(decrypted.DecryptedBuffer()->Size());
+ memcpy(aBuffer->Data(),
+ decrypted.DecryptedBuffer()->Data(),
+ decrypted.DecryptedBuffer()->Size());
+ }
+ mCallback->Decrypted(aBuffer, ToGMPErr(rv));
+}
+
+void
+WidevineDecryptor::DecryptingComplete()
+{
+ Log("WidevineDecryptor::DecryptingComplete() this=%p", this);
+ // Drop our references to the CDMWrapper. When any other references
+ // held elsewhere are dropped (for example references held by a
+ // WidevineVideoDecoder, or a runnable), the CDMWrapper destroys
+ // the CDM.
+ mCDM = nullptr;
+ sDecryptors.erase(mInstanceId);
+ mCallback = nullptr;
+ Release();
+}
+
+class WidevineBuffer : public cdm::Buffer {
+public:
+ explicit WidevineBuffer(size_t aSize) {
+ Log("WidevineBuffer(size=" PRIuSIZE ") created", aSize);
+ mBuffer.SetLength(aSize);
+ }
+ ~WidevineBuffer() {
+ Log("WidevineBuffer(size=" PRIuSIZE ") destroyed", Size());
+ }
+ void Destroy() override { delete this; }
+ uint32_t Capacity() const override { return mBuffer.Length(); };
+ uint8_t* Data() override { return mBuffer.Elements(); }
+ void SetSize(uint32_t aSize) override { mBuffer.SetLength(aSize); }
+ uint32_t Size() const override { return mBuffer.Length(); }
+
+private:
+ WidevineBuffer(const WidevineBuffer&);
+ void operator=(const WidevineBuffer&);
+
+ nsTArray<uint8_t> mBuffer;
+};
+
+Buffer*
+WidevineDecryptor::Allocate(uint32_t aCapacity)
+{
+ Log("Decryptor::Allocate(capacity=%u)", aCapacity);
+ return new WidevineBuffer(aCapacity);
+}
+
+class TimerTask : public GMPTask {
+public:
+ TimerTask(WidevineDecryptor* aDecryptor,
+ RefPtr<CDMWrapper> aCDM,
+ void* aContext)
+ : mDecryptor(aDecryptor)
+ , mCDM(aCDM)
+ , mContext(aContext)
+ {
+ }
+ ~TimerTask() override {}
+ void Run() override {
+ mCDM->GetCDM()->TimerExpired(mContext);
+ }
+ void Destroy() override { delete this; }
+private:
+ RefPtr<WidevineDecryptor> mDecryptor;
+ RefPtr<CDMWrapper> mCDM;
+ void* mContext;
+};
+
+void
+WidevineDecryptor::SetTimer(int64_t aDelayMs, void* aContext)
+{
+ Log("Decryptor::SetTimer(delay_ms=%lld, context=0x%x)", aDelayMs, aContext);
+ if (mCDM) {
+ GMPSetTimerOnMainThread(new TimerTask(this, mCDM, aContext), aDelayMs);
+ }
+}
+
+Time
+WidevineDecryptor::GetCurrentWallTime()
+{
+ return base::Time::Now().ToDoubleT();
+}
+
+void
+WidevineDecryptor::OnResolveKeyStatusPromise(uint32_t aPromiseId,
+ cdm::KeyStatus aKeyStatus) {
+ //TODO: The callback of GetStatusForPolicy. See Mozilla bug 1404230.
+}
+
+void
+WidevineDecryptor::OnResolveNewSessionPromise(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdSize)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d) FAIL; !mCallback", aPromiseId);
+ return;
+ }
+ Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d)", aPromiseId);
+ auto iter = mPromiseIdToNewSessionTokens.find(aPromiseId);
+ if (iter == mPromiseIdToNewSessionTokens.end()) {
+ Log("FAIL: Decryptor::OnResolveNewSessionPromise(aPromiseId=%d) unknown aPromiseId", aPromiseId);
+ return;
+ }
+ mCallback->SetSessionId(iter->second, aSessionId, aSessionIdSize);
+ mCallback->ResolvePromise(aPromiseId);
+ mPromiseIdToNewSessionTokens.erase(iter);
+}
+
+void
+WidevineDecryptor::OnResolvePromise(uint32_t aPromiseId)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnResolvePromise(aPromiseId=0x%d) FAIL; !mCallback", aPromiseId);
+ return;
+ }
+ Log("Decryptor::OnResolvePromise(aPromiseId=%d)", aPromiseId);
+ mCallback->ResolvePromise(aPromiseId);
+}
+
+static GMPDOMException
+ConvertCDMExceptionToGMPDOMException(cdm::Exception aException)
+{
+ switch (aException) {
+ case kExceptionNotSupportedError: return kGMPNotSupportedError;
+ case kExceptionInvalidStateError: return kGMPInvalidStateError;
+ case kExceptionTypeError: return kGMPTypeError;
+ case kExceptionQuotaExceededError: return kGMPQuotaExceededError;
+ case kUnknownError: return kGMPInvalidModificationError; // Note: Unique placeholder.
+ case kClientError: return kGMPAbortError; // Note: Unique placeholder.
+ case kOutputError: return kGMPSecurityError; // Note: Unique placeholder.
+ };
+ return kGMPInvalidStateError; // Note: Unique placeholder.
+}
+
+// Align with spec, the Exceptions used by CDM to reject promises .
+// https://w3c.github.io/encrypted-media/#exceptions
+cdm::Exception
+ConvertCDMErrorToCDMException(cdm::Error error) {
+ switch (error) {
+ case cdm::kNotSupportedError:
+ return cdm::Exception::kExceptionNotSupportedError;
+ case cdm::kInvalidStateError:
+ return cdm::Exception::kExceptionInvalidStateError;
+ case cdm::kInvalidAccessError:
+ return cdm::Exception::kExceptionTypeError;
+ case cdm::kQuotaExceededError:
+ return cdm::Exception::kExceptionQuotaExceededError;
+
+ case cdm::kUnknownError:
+ case cdm::kClientError:
+ case cdm::kOutputError:
+ break;
+ }
+
+ return cdm::Exception::kExceptionInvalidStateError;
+}
+
+void
+WidevineDecryptor::OnRejectPromise(uint32_t aPromiseId,
+ cdm::Exception aException,
+ uint32_t aSystemCode,
+ const char* aErrorMessage,
+ uint32_t aErrorMessageSize)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnRejectPromise(aPromiseId=%d, err=%d, sysCode=%u, msg=%s) FAIL; !mCallback",
+ aPromiseId, (int)aException, aSystemCode, aErrorMessage);
+ return;
+ }
+ Log("Decryptor::OnRejectPromise(aPromiseId=%d, err=%d, sysCode=%u, msg=%s)",
+ aPromiseId, (int)aException, aSystemCode, aErrorMessage);
+ mCallback->RejectPromise(aPromiseId,
+ ConvertCDMExceptionToGMPDOMException(aException),
+ !aErrorMessageSize ? "" : aErrorMessage,
+ aErrorMessageSize);
+}
+
+static GMPSessionMessageType
+ToGMPMessageType(MessageType message_type)
+{
+ switch (message_type) {
+ case kLicenseRequest: return kGMPLicenseRequest;
+ case kLicenseRenewal: return kGMPLicenseRenewal;
+ case kLicenseRelease: return kGMPLicenseRelease;
+ }
+ return kGMPMessageInvalid;
+}
+
+void
+WidevineDecryptor::OnSessionMessage(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ cdm::MessageType aMessageType,
+ const char* aMessage,
+ uint32_t aMessageSize)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnSessionMessage() FAIL; !mCallback");
+ return;
+ }
+ Log("Decryptor::OnSessionMessage()");
+ mCallback->SessionMessage(aSessionId,
+ aSessionIdSize,
+ ToGMPMessageType(aMessageType),
+ reinterpret_cast<const uint8_t*>(aMessage),
+ aMessageSize);
+}
+
+static GMPMediaKeyStatus
+ToGMPKeyStatus(KeyStatus aStatus)
+{
+ switch (aStatus) {
+ case kUsable: return kGMPUsable;
+ case kInternalError: return kGMPInternalError;
+ case kExpired: return kGMPExpired;
+ case kOutputRestricted: return kGMPOutputRestricted;
+ case kOutputDownscaled: return kGMPOutputDownscaled;
+ case kStatusPending: return kGMPStatusPending;
+ case kReleased: return kGMPReleased;
+ }
+ return kGMPUnknown;
+}
+
+void
+WidevineDecryptor::OnSessionKeysChange(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ bool aHasAdditionalUsableKey,
+ const KeyInformation* aKeysInfo,
+ uint32_t aKeysInfoCount)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnSessionKeysChange() FAIL; !mCallback");
+ return;
+ }
+ Log("Decryptor::OnSessionKeysChange()");
+
+ nsTArray<GMPMediaKeyInfo> key_infos;
+ for (uint32_t i = 0; i < aKeysInfoCount; i++) {
+ key_infos.AppendElement(GMPMediaKeyInfo(aKeysInfo[i].key_id,
+ aKeysInfo[i].key_id_size,
+ ToGMPKeyStatus(aKeysInfo[i].status)));
+ }
+ mCallback->BatchedKeyStatusChanged(aSessionId, aSessionIdSize,
+ key_infos.Elements(), key_infos.Length());
+}
+
+static GMPTimestamp
+ToGMPTime(Time aCDMTime)
+{
+ return static_cast<GMPTimestamp>(aCDMTime * 1000);
+}
+
+void
+WidevineDecryptor::OnExpirationChange(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ Time aNewExpiryTime)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnExpirationChange(sid=%s) t=%lf FAIL; !mCallback",
+ aSessionId, aNewExpiryTime);
+ return;
+ }
+ Log("Decryptor::OnExpirationChange(sid=%s) t=%lf", aSessionId, aNewExpiryTime);
+ GMPTimestamp expiry = ToGMPTime(aNewExpiryTime);
+ if (aNewExpiryTime == 0) {
+ return;
+ }
+ mCallback->ExpirationChange(aSessionId, aSessionIdSize, expiry);
+}
+
+void
+WidevineDecryptor::OnSessionClosed(const char* aSessionId,
+ uint32_t aSessionIdSize)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnSessionClosed(sid=%s) FAIL; !mCallback", aSessionId);
+ return;
+ }
+ Log("Decryptor::OnSessionClosed(sid=%s)", aSessionId);
+ mCallback->SessionClosed(aSessionId, aSessionIdSize);
+}
+
+void
+WidevineDecryptor::SendPlatformChallenge(const char* aServiceId,
+ uint32_t aServiceIdSize,
+ const char* aChallenge,
+ uint32_t aChallengeSize)
+{
+ Log("Decryptor::SendPlatformChallenge(service_id=%s)", aServiceId);
+}
+
+void
+WidevineDecryptor::EnableOutputProtection(uint32_t aDesiredProtectionMask)
+{
+ Log("Decryptor::EnableOutputProtection(mask=0x%x)", aDesiredProtectionMask);
+}
+
+void
+WidevineDecryptor::QueryOutputProtectionStatus()
+{
+ Log("Decryptor::QueryOutputProtectionStatus()");
+}
+
+void
+WidevineDecryptor::OnDeferredInitializationDone(StreamType aStreamType,
+ Status aDecoderStatus)
+{
+ Log("Decryptor::OnDeferredInitializationDone()");
+}
+
+FileIO*
+WidevineDecryptor::CreateFileIO(FileIOClient* aClient)
+{
+ Log("Decryptor::CreateFileIO()");
+ if (!mPersistentStateRequired) {
+ return nullptr;
+ }
+ return new WidevineFileIO(aClient);
+}
+
+void
+WidevineDecryptor::RequestStorageId(uint32_t aVersion)
+{
+ Log("Decryptor::RequestStorageId() aVersion = %u", aVersion);
+ if (aVersion >= 0x80000000) {
+ mCDM->OnStorageId(aVersion, nullptr, 0);
+ return;
+ }
+
+ //TODO: Need to provide a menaingful buffer instead of a dummy one.
+ mCDM->OnStorageId(aVersion, new uint8_t[1024*1024], 1024 * 1024);
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineDecryptor.h b/dom/media/gmp/widevine-adapter/WidevineDecryptor.h
new file mode 100644
index 000000000..f291c321d
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineDecryptor.h
@@ -0,0 +1,132 @@
+/* -*- 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/. */
+
+#ifndef WidevineDecryptor_h_
+#define WidevineDecryptor_h_
+
+#include "stddef.h"
+#include "content_decryption_module.h"
+#include "gmp-api/gmp-decryption.h"
+#include "mozilla/RefPtr.h"
+#include "WidevineUtils.h"
+#include <map>
+
+namespace mozilla {
+
+class WidevineDecryptor : public GMPDecryptor
+ , public cdm::Host_9
+{
+public:
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WidevineDecryptor)
+
+ WidevineDecryptor();
+
+ void SetCDM(RefPtr<CDMWrapper> aCDM, uint32_t aDecryptorId);
+
+ static RefPtr<CDMWrapper> GetInstance(uint32_t aDecryptorId);
+
+ // GMPDecryptor
+ void Init(GMPDecryptorCallback* aCallback,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired) override;
+
+ void CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const char* aInitDataType,
+ uint32_t aInitDataTypeSize,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ GMPSessionType aSessionType) override;
+
+ void LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override;
+
+ void UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize) override;
+
+ void CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override;
+
+ void RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override;
+
+ void SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize) override;
+
+ void Decrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata) override;
+
+ void DecryptingComplete() override;
+
+
+ // cdm::Host_9 implementation
+ cdm::Buffer* Allocate(uint32_t aCapacity) override;
+ void SetTimer(int64_t aDelayMs, void* aContext) override;
+ cdm::Time GetCurrentWallTime() override;
+ // cdm::Host_9 interface
+ void OnResolveKeyStatusPromise(uint32_t aPromiseId,
+ cdm::KeyStatus aKeyStatus) override;
+ void OnResolveNewSessionPromise(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdSize) override;
+ void OnResolvePromise(uint32_t aPromiseId) override;
+ void OnRejectPromise(uint32_t aPromiseId,
+ cdm::Exception aException,
+ uint32_t aSystemCode,
+ const char* aErrorMessage,
+ uint32_t aErrorMessageSize) override;
+ void OnSessionMessage(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ cdm::MessageType aMessageType,
+ const char* aMessage,
+ uint32_t aMessageSize) override;
+ void OnSessionKeysChange(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ bool aHasAdditionalUsableKey,
+ const cdm::KeyInformation* aKeysInfo,
+ uint32_t aKeysInfoCount) override;
+ void OnExpirationChange(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ cdm::Time aNewExpiryTime) override;
+ void OnSessionClosed(const char* aSessionId,
+ uint32_t aSessionIdSize) override;
+ void SendPlatformChallenge(const char* aServiceId,
+ uint32_t aServiceIdSize,
+ const char* aChallenge,
+ uint32_t aChallengeSize) override;
+ void EnableOutputProtection(uint32_t aDesiredProtectionMask) override;
+ void QueryOutputProtectionStatus() override;
+ void OnDeferredInitializationDone(cdm::StreamType aStreamType,
+ cdm::Status aDecoderStatus) override;
+ // cdm::Host_9 interface
+ // NOTE: the interface has changed upstream.
+ void RequestStorageId(uint32_t aVersion) override;
+ cdm::FileIO* CreateFileIO(cdm::FileIOClient* aClient) override;
+
+ GMPDecryptorCallback* Callback() const { return mCallback; }
+ RefPtr<CDMWrapper> GetCDMWrapper() const { return mCDM; }
+private:
+ ~WidevineDecryptor();
+ RefPtr<CDMWrapper> mCDM;
+ cdm::ContentDecryptionModule_9* CDM() { return mCDM->GetCDM(); }
+
+ GMPDecryptorCallback* mCallback;
+ std::map<uint32_t, uint32_t> mPromiseIdToNewSessionTokens;
+ bool mDistinctiveIdentifierRequired = false;
+ bool mPersistentStateRequired = false;
+ uint32_t mInstanceId = 0;
+};
+
+} // namespace mozilla
+
+#endif // WidevineDecryptor_h_
diff --git a/dom/media/gmp/widevine-adapter/WidevineFileIO.cpp b/dom/media/gmp/widevine-adapter/WidevineFileIO.cpp
new file mode 100644
index 000000000..b5fb1d705
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineFileIO.cpp
@@ -0,0 +1,97 @@
+#include "WidevineFileIO.h"
+#include "WidevineUtils.h"
+#include "WidevineAdapter.h"
+
+using namespace cdm;
+
+namespace mozilla {
+
+void
+WidevineFileIO::Open(const char* aFilename, uint32_t aFilenameLength)
+{
+ mName = std::string(aFilename, aFilename + aFilenameLength);
+ GMPRecord* record = nullptr;
+ GMPErr err = GMPCreateRecord(aFilename, aFilenameLength, &record, static_cast<GMPRecordClient*>(this));
+ if (GMP_FAILED(err)) {
+ Log("WidevineFileIO::Open() '%s' GMPCreateRecord failed", mName.c_str());
+ mClient->OnOpenComplete(FileIOClient::kError);
+ return;
+ }
+ if (GMP_FAILED(record->Open())) {
+ Log("WidevineFileIO::Open() '%s' record open failed", mName.c_str());
+ mClient->OnOpenComplete(FileIOClient::kError);
+ return;
+ }
+
+ Log("WidevineFileIO::Open() '%s'", mName.c_str());
+ mRecord = record;
+}
+
+void
+WidevineFileIO::Read()
+{
+ if (!mRecord) {
+ Log("WidevineFileIO::Read() '%s' used uninitialized!", mName.c_str());
+ mClient->OnReadComplete(FileIOClient::kError, nullptr, 0);
+ return;
+ }
+ Log("WidevineFileIO::Read() '%s'", mName.c_str());
+ mRecord->Read();
+}
+
+void
+WidevineFileIO::Write(const uint8_t* aData, uint32_t aDataSize)
+{
+ if (!mRecord) {
+ Log("WidevineFileIO::Write() '%s' used uninitialized!", mName.c_str());
+ mClient->OnWriteComplete(FileIOClient::kError);
+ return;
+ }
+ mRecord->Write(aData, aDataSize);
+}
+
+void
+WidevineFileIO::Close()
+{
+ Log("WidevineFileIO::Close() '%s'", mName.c_str());
+ if (mRecord) {
+ mRecord->Close();
+ mRecord = nullptr;
+ }
+ delete this;
+}
+
+static FileIOClient::Status
+GMPToWidevineFileStatus(GMPErr aStatus)
+{
+ switch (aStatus) {
+ case GMPRecordInUse: return FileIOClient::kInUse;
+ case GMPNoErr: return FileIOClient::kSuccess;
+ default: return FileIOClient::kError;
+ }
+}
+
+void
+WidevineFileIO::OpenComplete(GMPErr aStatus)
+{
+ Log("WidevineFileIO::OpenComplete() '%s' status=%d", mName.c_str(), aStatus);
+ mClient->OnOpenComplete(GMPToWidevineFileStatus(aStatus));
+}
+
+void
+WidevineFileIO::ReadComplete(GMPErr aStatus,
+ const uint8_t* aData,
+ uint32_t aDataSize)
+{
+ Log("WidevineFileIO::OnReadComplete() '%s' status=%d", mName.c_str(), aStatus);
+ mClient->OnReadComplete(GMPToWidevineFileStatus(aStatus), aData, aDataSize);
+}
+
+void
+WidevineFileIO::WriteComplete(GMPErr aStatus)
+{
+ Log("WidevineFileIO::WriteComplete() '%s' status=%d", mName.c_str(), aStatus);
+ mClient->OnWriteComplete(GMPToWidevineFileStatus(aStatus));
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineFileIO.h b/dom/media/gmp/widevine-adapter/WidevineFileIO.h
new file mode 100644
index 000000000..63003d9b6
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineFileIO.h
@@ -0,0 +1,46 @@
+/* -*- 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/. */
+
+#ifndef WidevineFileIO_h_
+#define WidevineFileIO_h_
+
+#include <stddef.h>
+#include "content_decryption_module.h"
+#include "gmp-api/gmp-storage.h"
+#include <string>
+
+namespace mozilla {
+
+class WidevineFileIO : public cdm::FileIO
+ , public GMPRecordClient
+{
+public:
+ explicit WidevineFileIO(cdm::FileIOClient* aClient)
+ : mClient(aClient)
+ , mRecord(nullptr)
+ {}
+
+ // cdm::FileIO
+ void Open(const char* aFilename, uint32_t aFilenameLength) override;
+ void Read() override;
+ void Write(const uint8_t* aData, uint32_t aDataSize) override;
+ void Close() override;
+
+ // GMPRecordClient
+ void OpenComplete(GMPErr aStatus) override;
+ void ReadComplete(GMPErr aStatus,
+ const uint8_t* aData,
+ uint32_t aDataSize) override;
+ void WriteComplete(GMPErr aStatus) override;
+
+private:
+ cdm::FileIOClient* mClient;
+ GMPRecord* mRecord;
+ std::string mName;
+};
+
+} // namespace mozilla
+
+#endif // WidevineFileIO_h_ \ No newline at end of file
diff --git a/dom/media/gmp/widevine-adapter/WidevineUtils.cpp b/dom/media/gmp/widevine-adapter/WidevineUtils.cpp
new file mode 100644
index 000000000..10c6c2e18
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineUtils.cpp
@@ -0,0 +1,95 @@
+/* -*- 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 "WidevineUtils.h"
+#include "WidevineDecryptor.h"
+
+#include "gmp-api/gmp-errors.h"
+#include <stdarg.h>
+#include <stdio.h>
+
+namespace mozilla {
+
+#ifdef ENABLE_WIDEVINE_LOG
+void
+Log(const char* aFormat, ...)
+{
+ va_list ap;
+ va_start(ap, aFormat);
+ const size_t len = 1024;
+ char buf[len];
+ vsnprintf(buf, len, aFormat, ap);
+ va_end(ap);
+ if (getenv("GMP_LOG_FILE")) {
+ FILE* f = fopen(getenv("GMP_LOG_FILE"), "a");
+ if (f) {
+ fprintf(f, "%s\n", buf);
+ fflush(f);
+ fclose(f);
+ f = nullptr;
+ }
+ } else {
+ printf("LOG: %s\n", buf);
+ }
+}
+#endif // ENABLE_WIDEVINE_LOG
+
+GMPErr
+ToGMPErr(cdm::Status aStatus)
+{
+ switch (aStatus) {
+ case cdm::kSuccess: return GMPNoErr;
+ case cdm::kNeedMoreData: return GMPGenericErr;
+ case cdm::kNoKey: return GMPNoKeyErr;
+ case cdm::kInitializationError: return GMPGenericErr;
+ case cdm::kDecryptError: return GMPCryptoErr;
+ case cdm::kDecodeError: return GMPDecodeErr;
+ case cdm::kDeferredInitialization: return GMPGenericErr;
+ default: return GMPGenericErr;
+ }
+}
+
+void InitInputBuffer(const GMPEncryptedBufferMetadata* aCrypto,
+ int64_t aTimestamp,
+ const uint8_t* aData,
+ size_t aDataSize,
+ cdm::InputBuffer &aInputBuffer,
+ nsTArray<cdm::SubsampleEntry> &aSubsamples)
+{
+ if (aCrypto) {
+ aInputBuffer.key_id = aCrypto->KeyId();
+ aInputBuffer.key_id_size = aCrypto->KeyIdSize();
+ aInputBuffer.iv = aCrypto->IV();
+ aInputBuffer.iv_size = aCrypto->IVSize();
+ aInputBuffer.num_subsamples = aCrypto->NumSubsamples();
+ aSubsamples.SetCapacity(aInputBuffer.num_subsamples);
+ const uint16_t* clear = aCrypto->ClearBytes();
+ const uint32_t* cipher = aCrypto->CipherBytes();
+ for (size_t i = 0; i < aCrypto->NumSubsamples(); i++) {
+ aSubsamples.AppendElement(cdm::SubsampleEntry(clear[i], cipher[i]));
+ }
+ }
+ aInputBuffer.data = aData;
+ aInputBuffer.data_size = aDataSize;
+ aInputBuffer.subsamples = aSubsamples.Elements();
+ aInputBuffer.timestamp = aTimestamp;
+}
+
+CDMWrapper::CDMWrapper(cdm::ContentDecryptionModule_9* aCDM,
+ WidevineDecryptor* aDecryptor)
+ : mCDM(aCDM)
+ , mDecryptor(aDecryptor)
+{
+ MOZ_ASSERT(mCDM);
+}
+
+CDMWrapper::~CDMWrapper()
+{
+ Log("CDMWrapper destroying CDM=%p", mCDM);
+ mCDM->Destroy();
+ mCDM = nullptr;
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineUtils.h b/dom/media/gmp/widevine-adapter/WidevineUtils.h
new file mode 100644
index 000000000..2f6137fe3
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineUtils.h
@@ -0,0 +1,73 @@
+/* -*- 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/. */
+
+#ifndef WidevineUtils_h_
+#define WidevineUtils_h_
+
+#include "stddef.h"
+#include "content_decryption_module.h"
+#include "gmp-api/gmp-decryption.h"
+#include "gmp-api/gmp-platform.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+// Uncomment for logging...
+//#define ENABLE_WIDEVINE_LOG 1
+#ifdef ENABLE_WIDEVINE_LOG
+void
+Log(const char* aFormat, ...);
+#else
+#define Log(...)
+#endif // ENABLE_WIDEVINE_LOG
+
+
+#define ENSURE_TRUE(condition, rv) { \
+ if (!(condition)) {\
+ Log("ENSURE_TRUE FAILED %s:%d", __FILE__, __LINE__); \
+ return rv; \
+ } \
+} \
+
+#define ENSURE_GMP_SUCCESS(err, rv) { \
+ if (GMP_FAILED(err)) {\
+ Log("ENSURE_GMP_SUCCESS FAILED %s:%d", __FILE__, __LINE__); \
+ return rv; \
+ } \
+} \
+
+GMPErr
+ToGMPErr(cdm::Status aStatus);
+
+class WidevineDecryptor;
+
+class CDMWrapper {
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CDMWrapper)
+
+ explicit CDMWrapper(cdm::ContentDecryptionModule_9* aCDM,
+ WidevineDecryptor* aDecryptor);
+ cdm::ContentDecryptionModule_9* GetCDM() const { return mCDM; }
+ void OnStorageId(uint32_t aVersion, const uint8_t* aStorageId,
+ uint32_t aStorageIdSize) {
+ mCDM->OnStorageId(aVersion, aStorageId, aStorageIdSize);
+ }
+private:
+ ~CDMWrapper();
+ cdm::ContentDecryptionModule_9* mCDM;
+ RefPtr<WidevineDecryptor> mDecryptor;
+};
+
+void InitInputBuffer(const GMPEncryptedBufferMetadata* aCrypto,
+ int64_t aTimestamp,
+ const uint8_t* aData,
+ size_t aDataSize,
+ cdm::InputBuffer &aInputBuffer,
+ nsTArray<cdm::SubsampleEntry> &aSubsamples);
+
+} // namespace mozilla
+
+#endif // WidevineUtils_h_
diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp
new file mode 100644
index 000000000..70d2fd8e0
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp
@@ -0,0 +1,400 @@
+/* -*- 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 "WidevineVideoDecoder.h"
+
+#include "mp4_demuxer/AnnexB.h"
+#include "WidevineUtils.h"
+#include "WidevineVideoFrame.h"
+#include "mozilla/Move.h"
+
+using namespace cdm;
+
+namespace mozilla {
+
+WidevineVideoDecoder::WidevineVideoDecoder(GMPVideoHost* aVideoHost,
+ RefPtr<CDMWrapper> aCDMWrapper)
+ : mVideoHost(aVideoHost)
+ , mCDMWrapper(Move(aCDMWrapper))
+ , mExtraData(new MediaByteBuffer())
+ , mSentInput(false)
+ , mCodecType(kGMPVideoCodecInvalid)
+ , mReturnOutputCallDepth(0)
+ , mDrainPending(false)
+ , mResetInProgress(false)
+{
+ // Expect to start with a CDM wrapper, will release it in DecodingComplete().
+ MOZ_ASSERT(mCDMWrapper);
+ Log("WidevineVideoDecoder created this=%p", this);
+
+ // Corresponding Release is in DecodingComplete().
+ AddRef();
+}
+
+WidevineVideoDecoder::~WidevineVideoDecoder()
+{
+ Log("WidevineVideoDecoder destroyed this=%p", this);
+}
+
+static
+VideoDecoderConfig::VideoCodecProfile
+ToCDMH264Profile(uint8_t aProfile)
+{
+ switch (aProfile) {
+ case 66: return VideoDecoderConfig::kH264ProfileBaseline;
+ case 77: return VideoDecoderConfig::kH264ProfileMain;
+ case 88: return VideoDecoderConfig::kH264ProfileExtended;
+ case 100: return VideoDecoderConfig::kH264ProfileHigh;
+ case 110: return VideoDecoderConfig::kH264ProfileHigh10;
+ case 122: return VideoDecoderConfig::kH264ProfileHigh422;
+ case 144: return VideoDecoderConfig::kH264ProfileHigh444Predictive;
+ }
+ return VideoDecoderConfig::kUnknownVideoCodecProfile;
+}
+
+void
+WidevineVideoDecoder::InitDecode(const GMPVideoCodec& aCodecSettings,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificLength,
+ GMPVideoDecoderCallback* aCallback,
+ int32_t aCoreCount)
+{
+ mCallback = aCallback;
+ VideoDecoderConfig config;
+ mCodecType = aCodecSettings.mCodecType;
+ if (mCodecType == kGMPVideoCodecH264) {
+ config.codec = VideoDecoderConfig::kCodecH264;
+ const GMPVideoCodecH264* h264 = (const GMPVideoCodecH264*)(aCodecSpecific);
+ config.profile = ToCDMH264Profile(h264->mAVCC.mProfile);
+ } else if (mCodecType == kGMPVideoCodecVP8) {
+ config.codec = VideoDecoderConfig::kCodecVp8;
+ config.profile = VideoDecoderConfig::kProfileNotNeeded;
+ } else if (mCodecType == kGMPVideoCodecVP9) {
+ config.codec = VideoDecoderConfig::kCodecVp9;
+ config.profile = VideoDecoderConfig::kProfileNotNeeded;
+ } else {
+ mCallback->Error(GMPInvalidArgErr);
+ return;
+ }
+ config.format = kYv12;
+ config.coded_size = Size(aCodecSettings.mWidth, aCodecSettings.mHeight);
+ mExtraData->AppendElements(aCodecSpecific + 1, aCodecSpecificLength);
+ config.extra_data = mExtraData->Elements();
+ config.extra_data_size = mExtraData->Length();
+ Status rv = CDM()->InitializeVideoDecoder(config);
+ if (rv != kSuccess) {
+ mCallback->Error(ToGMPErr(rv));
+ return;
+ }
+ Log("WidevineVideoDecoder::InitDecode() rv=%d", rv);
+ mAnnexB = mp4_demuxer::AnnexB::ConvertExtraDataToAnnexB(mExtraData);
+}
+
+void
+WidevineVideoDecoder::Decode(GMPVideoEncodedFrame* aInputFrame,
+ bool aMissingFrames,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength,
+ int64_t aRenderTimeMs)
+{
+ // We should not be given new input if a drain has been initiated
+ MOZ_ASSERT(!mDrainPending);
+ // We may not get the same out of the CDM decoder as we put in, and there
+ // may be some latency, i.e. we may need to input (say) 30 frames before
+ // we receive output. So we need to store the durations of the frames input,
+ // and retrieve them on output.
+ mFrameDurations[aInputFrame->TimeStamp()] = aInputFrame->Duration();
+
+ mSentInput = true;
+ InputBuffer sample;
+
+ RefPtr<MediaRawData> raw(
+ new MediaRawData(aInputFrame->Buffer(), aInputFrame->Size()));
+ if (aInputFrame->Size() && !raw->Data()) {
+ // OOM.
+ mCallback->Error(GMPAllocErr);
+ return;
+ }
+ raw->mExtraData = mExtraData;
+ raw->mKeyframe = (aInputFrame->FrameType() == kGMPKeyFrame);
+ if (mCodecType == kGMPVideoCodecH264) {
+ // Convert input from AVCC, which GMPAPI passes in, to AnnexB, which
+ // Chromium uses internally.
+ mp4_demuxer::AnnexB::ConvertSampleToAnnexB(raw);
+ }
+
+ const GMPEncryptedBufferMetadata* crypto = aInputFrame->GetDecryptionData();
+ nsTArray<SubsampleEntry> subsamples;
+ InitInputBuffer(crypto, aInputFrame->TimeStamp(), raw->Data(), raw->Size(), sample, subsamples);
+
+ // For keyframes, ConvertSampleToAnnexB will stick the AnnexB extra data
+ // at the start of the input. So we need to account for that as clear data
+ // in the subsamples.
+ if (raw->mKeyframe && !subsamples.IsEmpty() && mCodecType == kGMPVideoCodecH264) {
+ subsamples[0].clear_bytes += mAnnexB->Length();
+ }
+
+ WidevineVideoFrame frame;
+ Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame);
+ Log("WidevineVideoDecoder::Decode(timestamp=%lld) rv=%d", sample.timestamp, rv);
+
+ // Destroy frame, so that the shmem is now free to be used to return
+ // output to the Gecko process.
+ aInputFrame->Destroy();
+ aInputFrame = nullptr;
+
+ if (rv == kSuccess) {
+ if (!ReturnOutput(frame)) {
+ Log("WidevineVideoDecoder::Decode() Failed in ReturnOutput()");
+ mCallback->Error(GMPDecodeErr);
+ return;
+ }
+ // A reset should only be started at most at level mReturnOutputCallDepth 1,
+ // and if it's started it should be finished by that call by the time
+ // the it returns, so it should always be false by this point.
+ MOZ_ASSERT(!mResetInProgress);
+ // Only request more data if we don't have pending samples.
+ if (mFrameAllocationQueue.empty()) {
+ MOZ_ASSERT(mCDMWrapper);
+ mCallback->InputDataExhausted();
+ }
+ } else if (rv == kNeedMoreData) {
+ MOZ_ASSERT(mCDMWrapper);
+ mCallback->InputDataExhausted();
+ } else {
+ mCallback->Error(ToGMPErr(rv));
+ }
+ // Finish a drain if pending and we have no pending ReturnOutput calls on the stack.
+ if (mDrainPending && mReturnOutputCallDepth == 0) {
+ Drain();
+ }
+}
+
+// Util class to assist with counting mReturnOutputCallDepth.
+class CounterHelper {
+public:
+ // RAII, increment counter
+ explicit CounterHelper(int32_t& counter)
+ : mCounter(counter)
+ {
+ mCounter++;
+ }
+
+ // RAII, decrement counter
+ ~CounterHelper()
+ {
+ mCounter--;
+ }
+
+private:
+ int32_t& mCounter;
+};
+
+// Util class to make sure GMP frames are freed. Holds a GMPVideoi420Frame*
+// and will destroy it when the helper is destroyed unless the held frame
+// if forgotten with ForgetFrame.
+class FrameDestroyerHelper {
+public:
+ explicit FrameDestroyerHelper(GMPVideoi420Frame*& frame)
+ : frame(frame)
+ {
+ }
+
+ // RAII, destroy frame if held.
+ ~FrameDestroyerHelper()
+ {
+ if (frame) {
+ frame->Destroy();
+ }
+ frame = nullptr;
+ }
+
+ // Forget the frame without destroying it.
+ void ForgetFrame()
+ {
+ frame = nullptr;
+ }
+
+private:
+ GMPVideoi420Frame* frame;
+};
+
+
+// Special handing is needed around ReturnOutput as it spins the IPC message
+// queue when creating an empty frame and can end up with reentrant calls into
+// the class methods.
+bool
+WidevineVideoDecoder::ReturnOutput(WidevineVideoFrame& aCDMFrame)
+{
+ MOZ_ASSERT(mReturnOutputCallDepth >= 0);
+ CounterHelper counterHelper(mReturnOutputCallDepth);
+ mFrameAllocationQueue.push_back(Move(aCDMFrame));
+ if (mReturnOutputCallDepth > 1) {
+ // In a reentrant call.
+ return true;
+ }
+ while (!mFrameAllocationQueue.empty()) {
+ MOZ_ASSERT(mReturnOutputCallDepth == 1);
+ // If we're at call level 1 a reset should not have been started. A
+ // reset may be received during CreateEmptyFrame below, but we should not
+ // be in a reset at this stage -- this would indicate receiving decode
+ // messages before completing our reset, which we should not.
+ MOZ_ASSERT(!mResetInProgress);
+ WidevineVideoFrame currentCDMFrame = Move(mFrameAllocationQueue.front());
+ mFrameAllocationQueue.pop_front();
+ GMPVideoFrame* f = nullptr;
+ auto err = mVideoHost->CreateFrame(kGMPI420VideoFrame, &f);
+ if (GMP_FAILED(err) || !f) {
+ Log("Failed to create i420 frame!\n");
+ return false;
+ }
+ auto gmpFrame = static_cast<GMPVideoi420Frame*>(f);
+ FrameDestroyerHelper frameDestroyerHelper(gmpFrame);
+ Size size = currentCDMFrame.Size();
+ const int32_t yStride = currentCDMFrame.Stride(VideoFrame::kYPlane);
+ const int32_t uStride = currentCDMFrame.Stride(VideoFrame::kUPlane);
+ const int32_t vStride = currentCDMFrame.Stride(VideoFrame::kVPlane);
+ const int32_t halfHeight = size.height / 2;
+ // This call can cause a shmem alloc, during this alloc other calls
+ // may be made to this class and placed on the stack. ***WARNING***:
+ // other IPC calls can happen during this call, resulting in calls
+ // being made to the CDM. After this call state can have changed,
+ // and should be reevaluated.
+ err = gmpFrame->CreateEmptyFrame(size.width,
+ size.height,
+ yStride,
+ uStride,
+ vStride);
+ // Assert possible reentrant calls or resets haven't altered level unexpectedly.
+ MOZ_ASSERT(mReturnOutputCallDepth == 1);
+ ENSURE_GMP_SUCCESS(err, false);
+
+ // If a reset started we need to dump the current frame and complete the reset.
+ if (mResetInProgress) {
+ MOZ_ASSERT(mCDMWrapper);
+ MOZ_ASSERT(mFrameAllocationQueue.empty());
+ CompleteReset();
+ return true;
+ }
+
+ err = gmpFrame->SetWidth(size.width);
+ ENSURE_GMP_SUCCESS(err, false);
+
+ err = gmpFrame->SetHeight(size.height);
+ ENSURE_GMP_SUCCESS(err, false);
+
+ Buffer* buffer = currentCDMFrame.FrameBuffer();
+ uint8_t* outBuffer = gmpFrame->Buffer(kGMPYPlane);
+ ENSURE_TRUE(outBuffer != nullptr, false);
+ MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPYPlane) >= yStride*size.height);
+ memcpy(outBuffer,
+ buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kYPlane),
+ yStride * size.height);
+
+ outBuffer = gmpFrame->Buffer(kGMPUPlane);
+ ENSURE_TRUE(outBuffer != nullptr, false);
+ MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPUPlane) >= uStride * halfHeight);
+ memcpy(outBuffer,
+ buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kUPlane),
+ uStride * halfHeight);
+
+ outBuffer = gmpFrame->Buffer(kGMPVPlane);
+ ENSURE_TRUE(outBuffer != nullptr, false);
+ MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPVPlane) >= vStride * halfHeight);
+ memcpy(outBuffer,
+ buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kVPlane),
+ vStride * halfHeight);
+
+ gmpFrame->SetTimestamp(currentCDMFrame.Timestamp());
+
+ auto d = mFrameDurations.find(currentCDMFrame.Timestamp());
+ if (d != mFrameDurations.end()) {
+ gmpFrame->SetDuration(d->second);
+ mFrameDurations.erase(d);
+ }
+
+ // Forget frame so it's not deleted, call back taking ownership.
+ frameDestroyerHelper.ForgetFrame();
+ mCallback->Decoded(gmpFrame);
+ }
+
+ return true;
+}
+
+void
+WidevineVideoDecoder::Reset()
+{
+ Log("WidevineVideoDecoder::Reset() mSentInput=%d", mSentInput);
+ // We shouldn't reset if a drain is pending.
+ MOZ_ASSERT(!mDrainPending);
+ mResetInProgress = true;
+ if (mSentInput) {
+ CDM()->ResetDecoder(kStreamTypeVideo);
+ }
+ // Remove queued frames, but do not reset mReturnOutputCallDepth, let the
+ // ReturnOutput calls unwind and decrement the counter as needed.
+ mFrameAllocationQueue.clear();
+ mFrameDurations.clear();
+ // Only if no ReturnOutput calls are in progress can we complete, otherwise
+ // ReturnOutput needs to finalize the reset.
+ if (mReturnOutputCallDepth == 0) {
+ CompleteReset();
+ }
+}
+
+void
+WidevineVideoDecoder::CompleteReset()
+{
+ mCallback->ResetComplete();
+ mSentInput = false;
+ mResetInProgress = false;
+}
+
+void
+WidevineVideoDecoder::Drain()
+{
+ Log("WidevineVideoDecoder::Drain()");
+ if (mReturnOutputCallDepth > 0) {
+ Log("Drain call is reentrant, postponing drain");
+ mDrainPending = true;
+ return;
+ }
+
+ Status rv = kSuccess;
+ while (rv == kSuccess) {
+ WidevineVideoFrame frame;
+ InputBuffer sample;
+ Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame);
+ Log("WidevineVideoDecoder::Drain(); DecryptAndDecodeFrame() rv=%d", rv);
+ if (frame.Format() == kUnknownVideoFormat) {
+ break;
+ }
+ if (rv == kSuccess) {
+ if (!ReturnOutput(frame)) {
+ Log("WidevineVideoDecoder::Decode() Failed in ReturnOutput()");
+ }
+ }
+ }
+ // Shouldn't be reset while draining.
+ MOZ_ASSERT(!mResetInProgress);
+
+ CDM()->ResetDecoder(kStreamTypeVideo);
+ mDrainPending = false;
+ mCallback->DrainComplete();
+}
+
+void
+WidevineVideoDecoder::DecodingComplete()
+{
+ Log("WidevineVideoDecoder::DecodingComplete()");
+ if (mCDMWrapper) {
+ CDM()->DeinitializeDecoder(kStreamTypeVideo);
+ mCDMWrapper = nullptr;
+ }
+ // Release that corresponds to AddRef() in constructor.
+ Release();
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h
new file mode 100644
index 000000000..f5e63519b
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h
@@ -0,0 +1,80 @@
+/* -*- 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/. */
+
+#ifndef WidevineVideoDecoder_h_
+#define WidevineVideoDecoder_h_
+
+#include "stddef.h"
+#include "content_decryption_module.h"
+#include "gmp-api/gmp-video-decode.h"
+#include "gmp-api/gmp-video-host.h"
+#include "MediaData.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+#include "WidevineDecryptor.h"
+#include "WidevineVideoFrame.h"
+#include <map>
+#include <deque>
+
+namespace mozilla {
+
+class WidevineVideoDecoder : public GMPVideoDecoder {
+public:
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WidevineVideoDecoder)
+
+ WidevineVideoDecoder(GMPVideoHost* aVideoHost,
+ RefPtr<CDMWrapper> aCDMWrapper);
+ void InitDecode(const GMPVideoCodec& aCodecSettings,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificLength,
+ GMPVideoDecoderCallback* aCallback,
+ int32_t aCoreCount) override;
+ void Decode(GMPVideoEncodedFrame* aInputFrame,
+ bool aMissingFrames,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength,
+ int64_t aRenderTimeMs = -1) override;
+ void Reset() override;
+ void Drain() override;
+ void DecodingComplete() override;
+
+private:
+
+ ~WidevineVideoDecoder();
+
+ cdm::ContentDecryptionModule_9* CDM() const {
+ // CDM should only be accessed before 'DecodingComplete'.
+ MOZ_ASSERT(mCDMWrapper);
+ // CDMWrapper ensure the CDM is non-null, no need to check again.
+ return mCDMWrapper->GetCDM();
+ }
+
+ bool ReturnOutput(WidevineVideoFrame& aFrame);
+ void CompleteReset();
+
+ GMPVideoHost* mVideoHost;
+ RefPtr<CDMWrapper> mCDMWrapper;
+ RefPtr<MediaByteBuffer> mExtraData;
+ RefPtr<MediaByteBuffer> mAnnexB;
+ GMPVideoDecoderCallback* mCallback;
+ std::map<uint64_t, uint64_t> mFrameDurations;
+ bool mSentInput;
+ GMPVideoCodecType mCodecType;
+ // Frames waiting on allocation
+ std::deque<WidevineVideoFrame> mFrameAllocationQueue;
+ // Number of calls of ReturnOutput currently in progress.
+ int32_t mReturnOutputCallDepth;
+ // If we're waiting to drain. Used to prevent drain completing while
+ // ReturnOutput calls are still on the stack.
+ bool mDrainPending;
+ // If a reset is being performed. Used to track if ReturnOutput should
+ // dump current frame.
+ bool mResetInProgress;
+};
+
+} // namespace mozilla
+
+#endif // WidevineVideoDecoder_h_
diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp
new file mode 100644
index 000000000..4221bf15b
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp
@@ -0,0 +1,126 @@
+/* -*- 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 "WidevineVideoFrame.h"
+
+#include "WidevineUtils.h"
+
+using namespace cdm;
+
+namespace mozilla {
+
+WidevineVideoFrame::WidevineVideoFrame()
+ : mFormat(kUnknownVideoFormat)
+ , mSize(0,0)
+ , mBuffer(nullptr)
+ , mTimestamp(0)
+{
+ Log("WidevineVideoFrame::WidevineVideoFrame() this=%p", this);
+ memset(mPlaneOffsets, 0, sizeof(mPlaneOffsets));
+ memset(mPlaneStrides, 0, sizeof(mPlaneStrides));
+}
+
+WidevineVideoFrame::WidevineVideoFrame(WidevineVideoFrame&& aOther)
+ : mFormat(aOther.mFormat)
+ , mSize(aOther.mSize)
+ , mBuffer(aOther.mBuffer)
+ , mTimestamp(aOther.mTimestamp)
+{
+ Log("WidevineVideoFrame::WidevineVideoFrame(WidevineVideoFrame&&) this=%p, other=%p",
+ this, &aOther);
+ memcpy(mPlaneOffsets, aOther.mPlaneOffsets, sizeof(mPlaneOffsets));
+ memcpy(mPlaneStrides, aOther.mPlaneStrides, sizeof(mPlaneStrides));
+ aOther.mBuffer = nullptr;
+}
+
+WidevineVideoFrame::~WidevineVideoFrame()
+{
+ if (mBuffer) {
+ mBuffer->Destroy();
+ mBuffer = nullptr;
+ }
+}
+
+void
+WidevineVideoFrame::SetFormat(cdm::VideoFormat aFormat)
+{
+ Log("WidevineVideoFrame::SetFormat(%d) this=%p", aFormat, this);
+ mFormat = aFormat;
+}
+
+cdm::VideoFormat
+WidevineVideoFrame::Format() const
+{
+ return mFormat;
+}
+
+void
+WidevineVideoFrame::SetSize(cdm::Size aSize)
+{
+ Log("WidevineVideoFrame::SetSize(%d,%d) this=%p", aSize.width, aSize.height, this);
+ mSize.width = aSize.width;
+ mSize.height = aSize.height;
+}
+
+cdm::Size
+WidevineVideoFrame::Size() const
+{
+ return mSize;
+}
+
+void
+WidevineVideoFrame::SetFrameBuffer(cdm::Buffer* aFrameBuffer)
+{
+ Log("WidevineVideoFrame::SetFrameBuffer(%p) this=%p", aFrameBuffer, this);
+ MOZ_ASSERT(!mBuffer);
+ mBuffer = aFrameBuffer;
+}
+
+cdm::Buffer*
+WidevineVideoFrame::FrameBuffer()
+{
+ return mBuffer;
+}
+
+void
+WidevineVideoFrame::SetPlaneOffset(cdm::VideoFrame::VideoPlane aPlane, uint32_t aOffset)
+{
+ Log("WidevineVideoFrame::SetPlaneOffset(%d, %d) this=%p", aPlane, aOffset, this);
+ mPlaneOffsets[aPlane] = aOffset;
+}
+
+uint32_t
+WidevineVideoFrame::PlaneOffset(cdm::VideoFrame::VideoPlane aPlane)
+{
+ return mPlaneOffsets[aPlane];
+}
+
+void
+WidevineVideoFrame::SetStride(cdm::VideoFrame::VideoPlane aPlane, uint32_t aStride)
+{
+ Log("WidevineVideoFrame::SetStride(%d, %d) this=%p", aPlane, aStride, this);
+ mPlaneStrides[aPlane] = aStride;
+}
+
+uint32_t
+WidevineVideoFrame::Stride(cdm::VideoFrame::VideoPlane aPlane)
+{
+ return mPlaneStrides[aPlane];
+}
+
+void
+WidevineVideoFrame::SetTimestamp(int64_t timestamp)
+{
+ Log("WidevineVideoFrame::SetTimestamp(%lld) this=%p", timestamp, this);
+ mTimestamp = timestamp;
+}
+
+int64_t
+WidevineVideoFrame::Timestamp() const
+{
+ return mTimestamp;
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoFrame.h b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.h
new file mode 100644
index 000000000..96d4f20f8
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.h
@@ -0,0 +1,50 @@
+/* -*- 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/. */
+
+#ifndef WidevineVideoFrame_h_
+#define WidevineVideoFrame_h_
+
+#include "stddef.h"
+#include "content_decryption_module.h"
+#include <vector>
+
+namespace mozilla {
+
+class WidevineVideoFrame : public cdm::VideoFrame {
+public:
+ WidevineVideoFrame();
+ WidevineVideoFrame(WidevineVideoFrame&& other);
+ ~WidevineVideoFrame();
+
+ void SetFormat(cdm::VideoFormat aFormat) override;
+ cdm::VideoFormat Format() const override;
+
+ void SetSize(cdm::Size aSize) override;
+ cdm::Size Size() const override;
+
+ void SetFrameBuffer(cdm::Buffer* aFrameBuffer) override;
+ cdm::Buffer* FrameBuffer() override;
+
+ void SetPlaneOffset(cdm::VideoFrame::VideoPlane aPlane, uint32_t aOffset) override;
+ uint32_t PlaneOffset(cdm::VideoFrame::VideoPlane aPlane) override;
+
+ void SetStride(cdm::VideoFrame::VideoPlane aPlane, uint32_t aStride) override;
+ uint32_t Stride(cdm::VideoFrame::VideoPlane aPlane) override;
+
+ void SetTimestamp(int64_t aTimestamp) override;
+ int64_t Timestamp() const override;
+
+protected:
+ cdm::VideoFormat mFormat;
+ cdm::Size mSize;
+ cdm::Buffer* mBuffer;
+ uint32_t mPlaneOffsets[kMaxPlanes];
+ uint32_t mPlaneStrides[kMaxPlanes];
+ int64_t mTimestamp;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/gmp/widevine-adapter/content_decryption_module.h b/dom/media/gmp/widevine-adapter/content_decryption_module.h
new file mode 100644
index 000000000..0539135fb
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/content_decryption_module.h
@@ -0,0 +1,1278 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CDM_CONTENT_DECRYPTION_MODULE_H_
+#define CDM_CONTENT_DECRYPTION_MODULE_H_
+
+#include "content_decryption_module_export.h"
+
+#if defined(_MSC_VER)
+typedef unsigned char uint8_t;
+typedef unsigned int uint32_t;
+typedef int int32_t;
+typedef __int64 int64_t;
+#else
+#include <stdint.h>
+#endif
+
+// Define CDM_CLASS_API to export class types. We have to add visibility
+// attributes to make sure virtual tables in CDM consumer and CDM implementation
+// are the same. Generally, it was always a good idea, as there're no guarantees
+// about that for the internal symbols, but it has only become a practical issue
+// after introduction of LTO devirtualization. See more details on
+// https://crbug.com/609564#c35
+#if defined(_WIN32)
+#if defined(__clang__)
+#define CDM_CLASS_API [[clang::lto_visibility_public]]
+#else
+#define CDM_CLASS_API
+#endif
+#else // defined(_WIN32)
+#define CDM_CLASS_API __attribute__((visibility("default")))
+#endif // defined(_WIN32)
+
+// The version number must be rolled when the exported functions are updated!
+// If the CDM and the adapter use different versions of these functions, the
+// adapter will fail to load or crash!
+#define CDM_MODULE_VERSION 4
+
+// Build the versioned entrypoint name.
+// The extra macros are necessary to expand version to an actual value.
+#define INITIALIZE_CDM_MODULE \
+ BUILD_ENTRYPOINT(InitializeCdmModule, CDM_MODULE_VERSION)
+#define BUILD_ENTRYPOINT(name, version) \
+ BUILD_ENTRYPOINT_NO_EXPANSION(name, version)
+#define BUILD_ENTRYPOINT_NO_EXPANSION(name, version) name##_##version
+
+extern "C" {
+CDM_API void INITIALIZE_CDM_MODULE();
+
+CDM_API void DeinitializeCdmModule();
+
+// Returns a pointer to the requested CDM Host interface upon success.
+// Returns NULL if the requested CDM Host interface is not supported.
+// The caller should cast the returned pointer to the type matching
+// |host_interface_version|.
+typedef void* (*GetCdmHostFunc)(int host_interface_version, void* user_data);
+
+// Returns a pointer to the requested CDM upon success.
+// Returns NULL if an error occurs or the requested |cdm_interface_version| or
+// |key_system| is not supported or another error occurs.
+// The caller should cast the returned pointer to the type matching
+// |cdm_interface_version|.
+// Caller retains ownership of arguments and must call Destroy() on the returned
+// object.
+CDM_API void* CreateCdmInstance(
+ int cdm_interface_version,
+ const char* key_system, uint32_t key_system_size,
+ GetCdmHostFunc get_cdm_host_func, void* user_data);
+
+CDM_API const char* GetCdmVersion();
+}
+
+namespace cdm {
+
+class CDM_CLASS_API AudioFrames;
+class CDM_CLASS_API DecryptedBlock;
+class CDM_CLASS_API VideoFrame;
+
+class CDM_CLASS_API Host_8;
+class CDM_CLASS_API Host_9;
+
+enum Status {
+ kSuccess = 0,
+ kNeedMoreData, // Decoder needs more data to produce a decoded frame/sample.
+ kNoKey, // The required decryption key is not available.
+ kInitializationError, // Initialization error.
+ kDecryptError, // Decryption failed.
+ kDecodeError, // Error decoding audio or video.
+ kDeferredInitialization // Decoder is not ready for initialization.
+};
+
+// This must at least contain the exceptions defined in the spec:
+// https://w3c.github.io/encrypted-media/#exceptions
+// The following starts with the list of DOM4 exceptions from:
+// http://www.w3.org/TR/dom/#domexception
+// Some DOM4 exceptions are not included as they are not expected to be used.
+// Should only be used on Host_8 and before.
+enum Error {
+ kNotSupportedError = 9,
+ kInvalidStateError = 11,
+ kInvalidAccessError = 15,
+ kQuotaExceededError = 22,
+
+ // Additional exceptions that do not have assigned codes.
+ // There are other non-EME-specific values, not included in this list.
+ kUnknownError = 30,
+
+ // Additional values from previous EME versions. They currently have no
+ // matching DOMException.
+ kClientError = 100,
+ kOutputError = 101
+};
+
+// Exceptions used by the CDM to reject promises.
+// https://w3c.github.io/encrypted-media/#exceptions
+enum Exception {
+ kExceptionTypeError,
+ kExceptionNotSupportedError,
+ kExceptionInvalidStateError,
+ kExceptionQuotaExceededError
+};
+
+// Time is defined as the number of seconds since the Epoch
+// (00:00:00 UTC, January 1, 1970), not including any added leap second.
+// Also see Time definition in spec: https://w3c.github.io/encrypted-media/#time
+// Note that Time is defined in millisecond accuracy in the spec but in second
+// accuracy here.
+typedef double Time;
+
+// An input buffer can be split into several continuous subsamples.
+// A SubsampleEntry specifies the number of clear and cipher bytes in each
+// subsample. For example, the following buffer has three subsamples:
+//
+// |<----- subsample1 ----->|<----- subsample2 ----->|<----- subsample3 ----->|
+// | clear1 | cipher1 | clear2 | cipher2 | clear3 | cipher3 |
+//
+// For decryption, all of the cipher bytes in a buffer should be concatenated
+// (in the subsample order) into a single logical stream. The clear bytes should
+// not be considered as part of decryption.
+//
+// Stream to decrypt: | cipher1 | cipher2 | cipher3 |
+// Decrypted stream: | decrypted1| decrypted2 | decrypted3 |
+//
+// After decryption, the decrypted bytes should be copied over the position
+// of the corresponding cipher bytes in the original buffer to form the output
+// buffer. Following the above example, the decrypted buffer should be:
+//
+// |<----- subsample1 ----->|<----- subsample2 ----->|<----- subsample3 ----->|
+// | clear1 | decrypted1| clear2 | decrypted2 | clear3 | decrypted3 |
+//
+struct SubsampleEntry {
+ SubsampleEntry(uint32_t clear_bytes, uint32_t cipher_bytes)
+ : clear_bytes(clear_bytes), cipher_bytes(cipher_bytes) {}
+
+ uint32_t clear_bytes;
+ uint32_t cipher_bytes;
+};
+
+// Represents an input buffer to be decrypted (and possibly decoded). It does
+// not own any pointers in this struct. If |iv_size| = 0, the data is
+// unencrypted.
+struct InputBuffer {
+ InputBuffer()
+ : data(nullptr),
+ data_size(0),
+ key_id(nullptr),
+ key_id_size(0),
+ iv(nullptr),
+ iv_size(0),
+ subsamples(nullptr),
+ num_subsamples(0),
+ timestamp(0) {}
+
+ const uint8_t* data; // Pointer to the beginning of the input data.
+ uint32_t data_size; // Size (in bytes) of |data|.
+
+ const uint8_t* key_id; // Key ID to identify the decryption key.
+ uint32_t key_id_size; // Size (in bytes) of |key_id|.
+
+ const uint8_t* iv; // Initialization vector.
+ uint32_t iv_size; // Size (in bytes) of |iv|.
+
+ const struct SubsampleEntry* subsamples;
+ uint32_t num_subsamples; // Number of subsamples in |subsamples|.
+
+ int64_t timestamp; // Presentation timestamp in microseconds.
+};
+
+struct AudioDecoderConfig {
+ enum AudioCodec {
+ kUnknownAudioCodec = 0,
+ kCodecVorbis,
+ kCodecAac
+ };
+
+ AudioDecoderConfig()
+ : codec(kUnknownAudioCodec),
+ channel_count(0),
+ bits_per_channel(0),
+ samples_per_second(0),
+ extra_data(nullptr),
+ extra_data_size(0) {}
+
+ AudioCodec codec;
+ int32_t channel_count;
+ int32_t bits_per_channel;
+ int32_t samples_per_second;
+
+ // Optional byte data required to initialize audio decoders, such as the
+ // vorbis setup header.
+ uint8_t* extra_data;
+ uint32_t extra_data_size;
+};
+
+// Supported sample formats for AudioFrames.
+enum AudioFormat {
+ kUnknownAudioFormat = 0, // Unknown format value. Used for error reporting.
+ kAudioFormatU8, // Interleaved unsigned 8-bit w/ bias of 128.
+ kAudioFormatS16, // Interleaved signed 16-bit.
+ kAudioFormatS32, // Interleaved signed 32-bit.
+ kAudioFormatF32, // Interleaved float 32-bit.
+ kAudioFormatPlanarS16, // Signed 16-bit planar.
+ kAudioFormatPlanarF32, // Float 32-bit planar.
+};
+
+// Surface formats based on FOURCC labels, see: http://www.fourcc.org/yuv.php
+// Values are chosen to be consistent with Chromium's VideoPixelFormat values.
+enum VideoFormat {
+ kUnknownVideoFormat = 0, // Unknown format value. Used for error reporting.
+ kYv12 = 1, // 12bpp YVU planar 1x1 Y, 2x2 VU samples.
+ kI420 = 2, // 12bpp YUV planar 1x1 Y, 2x2 UV samples.
+
+ // In the following formats, each sample uses 16-bit in storage, while the
+ // sample value is stored in the least significant N bits where N is
+ // specified by the number after "P". For example, for YUV420P9, each Y, U,
+ // and V sample is stored in the least significant 9 bits in a 2-byte block.
+ kYUV420P9 = 16,
+ kYUV420P10 = 17,
+ kYUV422P9 = 18,
+ kYUV422P10 = 19,
+ kYUV444P9 = 20,
+ kYUV444P10 = 21,
+ kYUV420P12 = 22,
+ kYUV422P12 = 23,
+ kYUV444P12 = 24,
+};
+
+struct Size {
+ Size() : width(0), height(0) {}
+ Size(int32_t width, int32_t height) : width(width), height(height) {}
+
+ int32_t width;
+ int32_t height;
+};
+
+struct VideoDecoderConfig {
+ enum VideoCodec {
+ kUnknownVideoCodec = 0,
+ kCodecVp8,
+ kCodecH264,
+ kCodecVp9
+ };
+
+ enum VideoCodecProfile {
+ kUnknownVideoCodecProfile = 0,
+ kProfileNotNeeded,
+ kH264ProfileBaseline,
+ kH264ProfileMain,
+ kH264ProfileExtended,
+ kH264ProfileHigh,
+ kH264ProfileHigh10,
+ kH264ProfileHigh422,
+ kH264ProfileHigh444Predictive,
+ // VP9 Profiles are only passed in starting from CDM_9.
+ kVP9Profile0,
+ kVP9Profile1,
+ kVP9Profile2,
+ kVP9Profile3
+ };
+
+ VideoDecoderConfig()
+ : codec(kUnknownVideoCodec),
+ profile(kUnknownVideoCodecProfile),
+ format(kUnknownVideoFormat),
+ extra_data(nullptr),
+ extra_data_size(0) {}
+
+ VideoCodec codec;
+ VideoCodecProfile profile;
+ VideoFormat format;
+
+ // Width and height of video frame immediately post-decode. Not all pixels
+ // in this region are valid.
+ Size coded_size;
+
+ // Optional byte data required to initialize video decoders, such as H.264
+ // AAVC data.
+ uint8_t* extra_data;
+ uint32_t extra_data_size;
+};
+
+enum StreamType {
+ kStreamTypeAudio = 0,
+ kStreamTypeVideo = 1
+};
+
+// Structure provided to ContentDecryptionModule::OnPlatformChallengeResponse()
+// after a platform challenge was initiated via Host::SendPlatformChallenge().
+// All values will be NULL / zero in the event of a challenge failure.
+struct PlatformChallengeResponse {
+ // |challenge| provided during Host::SendPlatformChallenge() combined with
+ // nonce data and signed with the platform's private key.
+ const uint8_t* signed_data;
+ uint32_t signed_data_length;
+
+ // RSASSA-PKCS1-v1_5-SHA256 signature of the |signed_data| block.
+ const uint8_t* signed_data_signature;
+ uint32_t signed_data_signature_length;
+
+ // X.509 device specific certificate for the |service_id| requested.
+ const uint8_t* platform_key_certificate;
+ uint32_t platform_key_certificate_length;
+};
+
+// Used when passing arrays of binary data. Does not own the referenced data.
+struct BinaryData {
+ BinaryData() : data(nullptr), length(0) {}
+ const uint8_t* data;
+ uint32_t length;
+};
+
+// The current status of the associated key. The valid types are defined in the
+// spec: https://w3c.github.io/encrypted-media/#idl-def-MediaKeyStatus
+enum KeyStatus {
+ kUsable = 0,
+ kInternalError = 1,
+ kExpired = 2,
+ kOutputRestricted = 3,
+ kOutputDownscaled = 4,
+ kStatusPending = 5,
+ kReleased = 6
+};
+
+// Used when passing arrays of key information. Does not own the referenced
+// data. |system_code| is an additional error code for unusable keys and
+// should be 0 when |status| == kUsable.
+struct KeyInformation {
+ KeyInformation()
+ : key_id(nullptr),
+ key_id_size(0),
+ status(kInternalError),
+ system_code(0) {}
+ const uint8_t* key_id;
+ uint32_t key_id_size;
+ KeyStatus status;
+ uint32_t system_code;
+};
+
+// Supported output protection methods for use with EnableOutputProtection() and
+// returned by OnQueryOutputProtectionStatus().
+enum OutputProtectionMethods {
+ kProtectionNone = 0,
+ kProtectionHDCP = 1 << 0
+};
+
+// Connected output link types returned by OnQueryOutputProtectionStatus().
+enum OutputLinkTypes {
+ kLinkTypeNone = 0,
+ kLinkTypeUnknown = 1 << 0,
+ kLinkTypeInternal = 1 << 1,
+ kLinkTypeVGA = 1 << 2,
+ kLinkTypeHDMI = 1 << 3,
+ kLinkTypeDVI = 1 << 4,
+ kLinkTypeDisplayPort = 1 << 5,
+ kLinkTypeNetwork = 1 << 6
+};
+
+// Result of the QueryOutputProtectionStatus() call.
+enum QueryResult {
+ kQuerySucceeded = 0,
+ kQueryFailed
+};
+
+// The Initialization Data Type. The valid types are defined in the spec:
+// http://w3c.github.io/encrypted-media/initdata-format-registry.html#registry
+enum InitDataType {
+ kCenc = 0,
+ kKeyIds = 1,
+ kWebM = 2
+};
+
+// The type of session to create. The valid types are defined in the spec:
+// https://w3c.github.io/encrypted-media/#idl-def-SessionType
+enum SessionType {
+ kTemporary = 0,
+ kPersistentLicense = 1,
+ kPersistentKeyRelease = 2
+};
+
+// The type of the message event. The valid types are defined in the spec:
+// https://w3c.github.io/encrypted-media/#idl-def-MediaKeyMessageType
+enum MessageType {
+ kLicenseRequest = 0,
+ kLicenseRenewal = 1,
+ kLicenseRelease = 2
+};
+
+enum HdcpVersion {
+ kHdcpVersionNone,
+ kHdcpVersion1_0,
+ kHdcpVersion1_1,
+ kHdcpVersion1_2,
+ kHdcpVersion1_3,
+ kHdcpVersion1_4,
+ kHdcpVersion2_0,
+ kHdcpVersion2_1,
+ kHdcpVersion2_2
+};
+
+struct Policy {
+ Policy() : min_hdcp_version(kHdcpVersionNone) {}
+
+ HdcpVersion min_hdcp_version;
+};
+
+// FileIO interface provides a way for the CDM to store data in a file in
+// persistent storage. This interface aims only at providing basic read/write
+// capabilities and should not be used as a full fledged file IO API.
+// Each CDM and origin (e.g. HTTPS, "foo.example.com", 443) combination has
+// its own persistent storage. All instances of a given CDM associated with a
+// given origin share the same persistent storage.
+// Note to implementors of this interface:
+// Per-origin storage and the ability for users to clear it are important.
+// See http://www.w3.org/TR/encrypted-media/#privacy-storedinfo.
+class CDM_CLASS_API FileIO {
+ public:
+ // Opens the file with |file_name| for read and write.
+ // FileIOClient::OnOpenComplete() will be called after the opening
+ // operation finishes.
+ // - When the file is opened by a CDM instance, it will be classified as "in
+ // use". In this case other CDM instances in the same domain may receive
+ // kInUse status when trying to open it.
+ // - |file_name| must only contain letters (A-Za-z), digits(0-9), or "._-".
+ // It must not start with an underscore ('_'), and must be at least 1
+ // character and no more than 256 characters long.
+ virtual void Open(const char* file_name, uint32_t file_name_size) = 0;
+
+ // Reads the contents of the file. FileIOClient::OnReadComplete() will be
+ // called with the read status. Read() should not be called if a previous
+ // Read() or Write() call is still pending; otherwise OnReadComplete() will
+ // be called with kInUse.
+ virtual void Read() = 0;
+
+ // Writes |data_size| bytes of |data| into the file.
+ // FileIOClient::OnWriteComplete() will be called with the write status.
+ // All existing contents in the file will be overwritten. Calling Write() with
+ // NULL |data| will clear all contents in the file. Write() should not be
+ // called if a previous Write() or Read() call is still pending; otherwise
+ // OnWriteComplete() will be called with kInUse.
+ virtual void Write(const uint8_t* data, uint32_t data_size) = 0;
+
+ // Closes the file if opened, destroys this FileIO object and releases any
+ // resources allocated. The CDM must call this method when it finished using
+ // this object. A FileIO object must not be used after Close() is called.
+ virtual void Close() = 0;
+
+ protected:
+ FileIO() {}
+ virtual ~FileIO() {}
+};
+
+// Responses to FileIO calls. All responses will be called asynchronously.
+// When kError is returned, the FileIO object could be in an error state. All
+// following calls (other than Close()) could return kError. The CDM should
+// still call Close() to destroy the FileIO object.
+class CDM_CLASS_API FileIOClient {
+ public:
+ enum Status {
+ kSuccess = 0,
+ kInUse,
+ kError
+ };
+
+ // Response to a FileIO::Open() call with the open |status|.
+ virtual void OnOpenComplete(Status status) = 0;
+
+ // Response to a FileIO::Read() call to provide |data_size| bytes of |data|
+ // read from the file.
+ // - kSuccess indicates that all contents of the file has been successfully
+ // read. In this case, 0 |data_size| means that the file is empty.
+ // - kInUse indicates that there are other read/write operations pending.
+ // - kError indicates read failure, e.g. the storage is not open or cannot be
+ // fully read.
+ virtual void OnReadComplete(Status status,
+ const uint8_t* data, uint32_t data_size) = 0;
+
+ // Response to a FileIO::Write() call.
+ // - kSuccess indicates that all the data has been written into the file
+ // successfully.
+ // - kInUse indicates that there are other read/write operations pending.
+ // - kError indicates write failure, e.g. the storage is not open or cannot be
+ // fully written. Upon write failure, the contents of the file should be
+ // regarded as corrupt and should not used.
+ virtual void OnWriteComplete(Status status) = 0;
+
+ protected:
+ FileIOClient() {}
+ virtual ~FileIOClient() {}
+};
+
+// ContentDecryptionModule interface that all CDMs need to implement.
+// The interface is versioned for backward compatibility.
+// Note: ContentDecryptionModule implementations must use the allocator
+// provided in CreateCdmInstance() to allocate any Buffer that needs to
+// be passed back to the caller. Implementations must call Buffer::Destroy()
+// when a Buffer is created that will never be returned to the caller.
+class CDM_CLASS_API ContentDecryptionModule_8 {
+ public:
+ static const int kVersion = 8;
+ typedef Host_8 Host;
+
+ // Initializes the CDM instance, providing information about permitted
+ // functionalities.
+ // If |allow_distinctive_identifier| is false, messages from the CDM,
+ // such as message events, must not contain a Distinctive Identifier,
+ // even in an encrypted form.
+ // If |allow_persistent_state| is false, the CDM must not attempt to
+ // persist state. Calls to CreateFileIO() will fail.
+ virtual void Initialize(bool allow_distinctive_identifier,
+ bool allow_persistent_state) = 0;
+
+ // SetServerCertificate(), CreateSessionAndGenerateRequest(), LoadSession(),
+ // UpdateSession(), CloseSession(), and RemoveSession() all accept a
+ // |promise_id|, which must be passed to the completion Host method
+ // (e.g. Host::OnResolveNewSessionPromise()).
+
+ // Provides a server certificate to be used to encrypt messages to the
+ // license server. The CDM must respond by calling either
+ // Host::OnResolvePromise() or Host::OnRejectPromise().
+ virtual void SetServerCertificate(uint32_t promise_id,
+ const uint8_t* server_certificate_data,
+ uint32_t server_certificate_data_size) = 0;
+
+ // Creates a session given |session_type|, |init_data_type|, and |init_data|.
+ // The CDM must respond by calling either Host::OnResolveNewSessionPromise()
+ // or Host::OnRejectPromise().
+ virtual void CreateSessionAndGenerateRequest(uint32_t promise_id,
+ SessionType session_type,
+ InitDataType init_data_type,
+ const uint8_t* init_data,
+ uint32_t init_data_size) = 0;
+
+ // Loads the session of type |session_type| specified by |session_id|.
+ // The CDM must respond by calling either Host::OnResolveNewSessionPromise()
+ // or Host::OnRejectPromise(). If the session is not found, call
+ // Host::OnResolveNewSessionPromise() with session_id = NULL.
+ virtual void LoadSession(uint32_t promise_id,
+ SessionType session_type,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Updates the session with |response|. The CDM must respond by calling
+ // either Host::OnResolvePromise() or Host::OnRejectPromise().
+ virtual void UpdateSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size,
+ const uint8_t* response,
+ uint32_t response_size) = 0;
+
+ // Requests that the CDM close the session. The CDM must respond by calling
+ // either Host::OnResolvePromise() or Host::OnRejectPromise() when the request
+ // has been processed. This may be before the session is closed. Once the
+ // session is closed, Host::OnSessionClosed() must also be called.
+ virtual void CloseSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Removes any stored session data associated with this session. Will only be
+ // called for persistent sessions. The CDM must respond by calling either
+ // Host::OnResolvePromise() or Host::OnRejectPromise() when the request has
+ // been processed.
+ virtual void RemoveSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Performs scheduled operation with |context| when the timer fires.
+ virtual void TimerExpired(void* context) = 0;
+
+ // Decrypts the |encrypted_buffer|.
+ //
+ // Returns kSuccess if decryption succeeded, in which case the callee
+ // should have filled the |decrypted_buffer| and passed the ownership of
+ // |data| in |decrypted_buffer| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kDecryptError if any other error happened.
+ // If the return value is not kSuccess, |decrypted_buffer| should be ignored
+ // by the caller.
+ virtual Status Decrypt(const InputBuffer& encrypted_buffer,
+ DecryptedBlock* decrypted_buffer) = 0;
+
+ // Initializes the CDM audio decoder with |audio_decoder_config|. This
+ // function must be called before DecryptAndDecodeSamples() is called.
+ //
+ // Returns kSuccess if the |audio_decoder_config| is supported and the CDM
+ // audio decoder is successfully initialized.
+ // Returns kSessionError if |audio_decoder_config| is not supported. The CDM
+ // may still be able to do Decrypt().
+ // Returns kDeferredInitialization if the CDM is not ready to initialize the
+ // decoder at this time. Must call Host::OnDeferredInitializationDone() once
+ // initialization is complete.
+ virtual Status InitializeAudioDecoder(
+ const AudioDecoderConfig& audio_decoder_config) = 0;
+
+ // Initializes the CDM video decoder with |video_decoder_config|. This
+ // function must be called before DecryptAndDecodeFrame() is called.
+ //
+ // Returns kSuccess if the |video_decoder_config| is supported and the CDM
+ // video decoder is successfully initialized.
+ // Returns kSessionError if |video_decoder_config| is not supported. The CDM
+ // may still be able to do Decrypt().
+ // Returns kDeferredInitialization if the CDM is not ready to initialize the
+ // decoder at this time. Must call Host::OnDeferredInitializationDone() once
+ // initialization is complete.
+ virtual Status InitializeVideoDecoder(
+ const VideoDecoderConfig& video_decoder_config) = 0;
+
+ // De-initializes the CDM decoder and sets it to an uninitialized state. The
+ // caller can initialize the decoder again after this call to re-initialize
+ // it. This can be used to reconfigure the decoder if the configuration
+ // changes.
+ virtual void DeinitializeDecoder(StreamType decoder_type) = 0;
+
+ // Resets the CDM decoder to an initialized clean state. All internal buffers
+ // MUST be flushed.
+ virtual void ResetDecoder(StreamType decoder_type) = 0;
+
+ // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into a
+ // |video_frame|. Upon end-of-stream, the caller should call this function
+ // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty
+ // |video_frame| (|format| == kEmptyVideoFrame) is produced.
+ //
+ // Returns kSuccess if decryption and decoding both succeeded, in which case
+ // the callee will have filled the |video_frame| and passed the ownership of
+ // |frame_buffer| in |video_frame| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kNeedMoreData if more data was needed by the decoder to generate
+ // a decoded frame (e.g. during initialization and end-of-stream).
+ // Returns kDecryptError if any decryption error happened.
+ // Returns kDecodeError if any decoding error happened.
+ // If the return value is not kSuccess, |video_frame| should be ignored by
+ // the caller.
+ virtual Status DecryptAndDecodeFrame(const InputBuffer& encrypted_buffer,
+ VideoFrame* video_frame) = 0;
+
+ // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into
+ // |audio_frames|. Upon end-of-stream, the caller should call this function
+ // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty
+ // |audio_frames| is produced.
+ //
+ // Returns kSuccess if decryption and decoding both succeeded, in which case
+ // the callee will have filled |audio_frames| and passed the ownership of
+ // |data| in |audio_frames| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kNeedMoreData if more data was needed by the decoder to generate
+ // audio samples (e.g. during initialization and end-of-stream).
+ // Returns kDecryptError if any decryption error happened.
+ // Returns kDecodeError if any decoding error happened.
+ // If the return value is not kSuccess, |audio_frames| should be ignored by
+ // the caller.
+ virtual Status DecryptAndDecodeSamples(const InputBuffer& encrypted_buffer,
+ AudioFrames* audio_frames) = 0;
+
+ // Called by the host after a platform challenge was initiated via
+ // Host::SendPlatformChallenge().
+ virtual void OnPlatformChallengeResponse(
+ const PlatformChallengeResponse& response) = 0;
+
+ // Called by the host after a call to Host::QueryOutputProtectionStatus(). The
+ // |link_mask| is a bit mask of OutputLinkTypes and |output_protection_mask|
+ // is a bit mask of OutputProtectionMethods. If |result| is kQueryFailed,
+ // then |link_mask| and |output_protection_mask| are undefined and should
+ // be ignored.
+ virtual void OnQueryOutputProtectionStatus(
+ QueryResult result,
+ uint32_t link_mask,
+ uint32_t output_protection_mask) = 0;
+
+ // Destroys the object in the same context as it was created.
+ virtual void Destroy() = 0;
+
+ protected:
+ ContentDecryptionModule_8() {}
+ virtual ~ContentDecryptionModule_8() {}
+};
+
+// ContentDecryptionModule interface that all CDMs need to implement.
+// The interface is versioned for backward compatibility.
+// Note: ContentDecryptionModule implementations must use the allocator
+// provided in CreateCdmInstance() to allocate any Buffer that needs to
+// be passed back to the caller. Implementations must call Buffer::Destroy()
+// when a Buffer is created that will never be returned to the caller.
+class CDM_CLASS_API ContentDecryptionModule_9 {
+ public:
+ static const int kVersion = 9;
+ typedef Host_9 Host;
+
+ // Initializes the CDM instance, providing information about permitted
+ // functionalities.
+ // If |allow_distinctive_identifier| is false, messages from the CDM,
+ // such as message events, must not contain a Distinctive Identifier,
+ // even in an encrypted form.
+ // If |allow_persistent_state| is false, the CDM must not attempt to
+ // persist state. Calls to CreateFileIO() will fail.
+ virtual void Initialize(bool allow_distinctive_identifier,
+ bool allow_persistent_state) = 0;
+
+ // Gets the key status if the CDM has a hypothetical key with the |policy|.
+ // The CDM must respond by calling either Host::OnResolveKeyStatusPromise()
+ // with the result key status or Host::OnRejectPromise() if an unexpected
+ // error happened or this method is not supported.
+ virtual void GetStatusForPolicy(uint32_t promise_id,
+ const Policy& policy) = 0;
+
+ // SetServerCertificate(), CreateSessionAndGenerateRequest(), LoadSession(),
+ // UpdateSession(), CloseSession(), and RemoveSession() all accept a
+ // |promise_id|, which must be passed to the completion Host method
+ // (e.g. Host::OnResolveNewSessionPromise()).
+
+ // Provides a server certificate to be used to encrypt messages to the
+ // license server. The CDM must respond by calling either
+ // Host::OnResolvePromise() or Host::OnRejectPromise().
+ virtual void SetServerCertificate(uint32_t promise_id,
+ const uint8_t* server_certificate_data,
+ uint32_t server_certificate_data_size) = 0;
+
+ // Creates a session given |session_type|, |init_data_type|, and |init_data|.
+ // The CDM must respond by calling either Host::OnResolveNewSessionPromise()
+ // or Host::OnRejectPromise().
+ virtual void CreateSessionAndGenerateRequest(uint32_t promise_id,
+ SessionType session_type,
+ InitDataType init_data_type,
+ const uint8_t* init_data,
+ uint32_t init_data_size) = 0;
+
+ // Loads the session of type |session_type| specified by |session_id|.
+ // The CDM must respond by calling either Host::OnResolveNewSessionPromise()
+ // or Host::OnRejectPromise(). If the session is not found, call
+ // Host::OnResolveNewSessionPromise() with session_id = NULL.
+ virtual void LoadSession(uint32_t promise_id,
+ SessionType session_type,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Updates the session with |response|. The CDM must respond by calling
+ // either Host::OnResolvePromise() or Host::OnRejectPromise().
+ virtual void UpdateSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size,
+ const uint8_t* response,
+ uint32_t response_size) = 0;
+
+ // Requests that the CDM close the session. The CDM must respond by calling
+ // either Host::OnResolvePromise() or Host::OnRejectPromise() when the request
+ // has been processed. This may be before the session is closed. Once the
+ // session is closed, Host::OnSessionClosed() must also be called.
+ virtual void CloseSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Removes any stored session data associated with this session. Will only be
+ // called for persistent sessions. The CDM must respond by calling either
+ // Host::OnResolvePromise() or Host::OnRejectPromise() when the request has
+ // been processed.
+ virtual void RemoveSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Performs scheduled operation with |context| when the timer fires.
+ virtual void TimerExpired(void* context) = 0;
+
+ // Decrypts the |encrypted_buffer|.
+ //
+ // Returns kSuccess if decryption succeeded, in which case the callee
+ // should have filled the |decrypted_buffer| and passed the ownership of
+ // |data| in |decrypted_buffer| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kDecryptError if any other error happened.
+ // If the return value is not kSuccess, |decrypted_buffer| should be ignored
+ // by the caller.
+ virtual Status Decrypt(const InputBuffer& encrypted_buffer,
+ DecryptedBlock* decrypted_buffer) = 0;
+
+ // Initializes the CDM audio decoder with |audio_decoder_config|. This
+ // function must be called before DecryptAndDecodeSamples() is called.
+ //
+ // Returns kSuccess if the |audio_decoder_config| is supported and the CDM
+ // audio decoder is successfully initialized.
+ // Returns kInitializationError if |audio_decoder_config| is not supported.
+ // The CDM may still be able to do Decrypt().
+ // Returns kDeferredInitialization if the CDM is not ready to initialize the
+ // decoder at this time. Must call Host::OnDeferredInitializationDone() once
+ // initialization is complete.
+ virtual Status InitializeAudioDecoder(
+ const AudioDecoderConfig& audio_decoder_config) = 0;
+
+ // Initializes the CDM video decoder with |video_decoder_config|. This
+ // function must be called before DecryptAndDecodeFrame() is called.
+ //
+ // Returns kSuccess if the |video_decoder_config| is supported and the CDM
+ // video decoder is successfully initialized.
+ // Returns kInitializationError if |video_decoder_config| is not supported.
+ // The CDM may still be able to do Decrypt().
+ // Returns kDeferredInitialization if the CDM is not ready to initialize the
+ // decoder at this time. Must call Host::OnDeferredInitializationDone() once
+ // initialization is complete.
+ virtual Status InitializeVideoDecoder(
+ const VideoDecoderConfig& video_decoder_config) = 0;
+
+ // De-initializes the CDM decoder and sets it to an uninitialized state. The
+ // caller can initialize the decoder again after this call to re-initialize
+ // it. This can be used to reconfigure the decoder if the configuration
+ // changes.
+ virtual void DeinitializeDecoder(StreamType decoder_type) = 0;
+
+ // Resets the CDM decoder to an initialized clean state. All internal buffers
+ // MUST be flushed.
+ virtual void ResetDecoder(StreamType decoder_type) = 0;
+
+ // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into a
+ // |video_frame|. Upon end-of-stream, the caller should call this function
+ // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty
+ // |video_frame| (|format| == kEmptyVideoFrame) is produced.
+ //
+ // Returns kSuccess if decryption and decoding both succeeded, in which case
+ // the callee will have filled the |video_frame| and passed the ownership of
+ // |frame_buffer| in |video_frame| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kNeedMoreData if more data was needed by the decoder to generate
+ // a decoded frame (e.g. during initialization and end-of-stream).
+ // Returns kDecryptError if any decryption error happened.
+ // Returns kDecodeError if any decoding error happened.
+ // If the return value is not kSuccess, |video_frame| should be ignored by
+ // the caller.
+ virtual Status DecryptAndDecodeFrame(const InputBuffer& encrypted_buffer,
+ VideoFrame* video_frame) = 0;
+
+ // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into
+ // |audio_frames|. Upon end-of-stream, the caller should call this function
+ // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty
+ // |audio_frames| is produced.
+ //
+ // Returns kSuccess if decryption and decoding both succeeded, in which case
+ // the callee will have filled |audio_frames| and passed the ownership of
+ // |data| in |audio_frames| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kNeedMoreData if more data was needed by the decoder to generate
+ // audio samples (e.g. during initialization and end-of-stream).
+ // Returns kDecryptError if any decryption error happened.
+ // Returns kDecodeError if any decoding error happened.
+ // If the return value is not kSuccess, |audio_frames| should be ignored by
+ // the caller.
+ virtual Status DecryptAndDecodeSamples(const InputBuffer& encrypted_buffer,
+ AudioFrames* audio_frames) = 0;
+
+ // Called by the host after a platform challenge was initiated via
+ // Host::SendPlatformChallenge().
+ virtual void OnPlatformChallengeResponse(
+ const PlatformChallengeResponse& response) = 0;
+
+ // Called by the host after a call to Host::QueryOutputProtectionStatus(). The
+ // |link_mask| is a bit mask of OutputLinkTypes and |output_protection_mask|
+ // is a bit mask of OutputProtectionMethods. If |result| is kQueryFailed,
+ // then |link_mask| and |output_protection_mask| are undefined and should
+ // be ignored.
+ virtual void OnQueryOutputProtectionStatus(
+ QueryResult result,
+ uint32_t link_mask,
+ uint32_t output_protection_mask) = 0;
+
+ // Called by the host after a call to Host::RequestStorageId(). If the
+ // version of the storage ID requested is available, |storage_id| and
+ // |storage_id_size| are set appropriately. |version| will be the same as
+ // what was requested, unless 0 (latest) was requested, in which case
+ // |version| will be the actual version number for the |storage_id| returned.
+ // If the requested version is not available, null/zero will be provided as
+ // |storage_id| and |storage_id_size|, respectively, and |version| should be
+ // ignored.
+ virtual void OnStorageId(uint32_t version,
+ const uint8_t* storage_id,
+ uint32_t storage_id_size) = 0;
+
+ // Destroys the object in the same context as it was created.
+ virtual void Destroy() = 0;
+
+ protected:
+ ContentDecryptionModule_9() {}
+ virtual ~ContentDecryptionModule_9() {}
+};
+
+typedef ContentDecryptionModule_9 ContentDecryptionModule;
+
+// Represents a buffer created by Allocator implementations.
+class CDM_CLASS_API Buffer {
+ public:
+ // Destroys the buffer in the same context as it was created.
+ virtual void Destroy() = 0;
+
+ virtual uint32_t Capacity() const = 0;
+ virtual uint8_t* Data() = 0;
+ virtual void SetSize(uint32_t size) = 0;
+ virtual uint32_t Size() const = 0;
+
+ protected:
+ Buffer() {}
+ virtual ~Buffer() {}
+
+ private:
+ Buffer(const Buffer&);
+ void operator=(const Buffer&);
+};
+
+class CDM_CLASS_API Host_8 {
+ public:
+ static const int kVersion = 8;
+
+ // Returns a Buffer* containing non-zero members upon success, or NULL on
+ // failure. The caller owns the Buffer* after this call. The buffer is not
+ // guaranteed to be zero initialized. The capacity of the allocated Buffer
+ // is guaranteed to be not less than |capacity|.
+ virtual Buffer* Allocate(uint32_t capacity) = 0;
+
+ // Requests the host to call ContentDecryptionModule::TimerFired() |delay_ms|
+ // from now with |context|.
+ virtual void SetTimer(int64_t delay_ms, void* context) = 0;
+
+ // Returns the current wall time.
+ virtual Time GetCurrentWallTime() = 0;
+
+ // Called by the CDM when a session is created or loaded and the value for the
+ // MediaKeySession's sessionId attribute is available (|session_id|).
+ // This must be called before OnSessionMessage() or
+ // OnSessionKeysChange() is called for the same session. |session_id_size|
+ // should not include null termination.
+ // When called in response to LoadSession(), the |session_id| must be the
+ // same as the |session_id| passed in LoadSession(), or NULL if the
+ // session could not be loaded.
+ virtual void OnResolveNewSessionPromise(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Called by the CDM when a session is updated or released.
+ virtual void OnResolvePromise(uint32_t promise_id) = 0;
+
+ // Called by the CDM when an error occurs as a result of one of the
+ // ContentDecryptionModule calls that accept a |promise_id|.
+ // |error| must be specified, |error_message| and |system_code|
+ // are optional. |error_message_size| should not include null termination.
+ virtual void OnRejectPromise(uint32_t promise_id,
+ Error error,
+ uint32_t system_code,
+ const char* error_message,
+ uint32_t error_message_size) = 0;
+
+ // Called by the CDM when it has a message for session |session_id|.
+ // Size parameters should not include null termination.
+ // |legacy_destination_url| is only for supporting the prefixed EME API and
+ // is ignored by unprefixed EME. It should only be non-null if |message_type|
+ // is kLicenseRenewal.
+ virtual void OnSessionMessage(const char* session_id,
+ uint32_t session_id_size,
+ MessageType message_type,
+ const char* message,
+ uint32_t message_size,
+ const char* legacy_destination_url,
+ uint32_t legacy_destination_url_length) = 0;
+
+ // Called by the CDM when there has been a change in keys or their status for
+ // session |session_id|. |has_additional_usable_key| should be set if a
+ // key is newly usable (e.g. new key available, previously expired key has
+ // been renewed, etc.) and the browser should attempt to resume playback.
+ // |key_ids| is the list of key ids for this session along with their
+ // current status. |key_ids_count| is the number of entries in |key_ids|.
+ // Size parameter for |session_id| should not include null termination.
+ virtual void OnSessionKeysChange(const char* session_id,
+ uint32_t session_id_size,
+ bool has_additional_usable_key,
+ const KeyInformation* keys_info,
+ uint32_t keys_info_count) = 0;
+
+ // Called by the CDM when there has been a change in the expiration time for
+ // session |session_id|. This can happen as the result of an Update() call
+ // or some other event. If this happens as a result of a call to Update(),
+ // it must be called before resolving the Update() promise. |new_expiry_time|
+ // represents the time after which the key(s) in the session will no longer
+ // be usable for decryption. It can be 0 if no such time exists or if the
+ // license explicitly never expires. Size parameter should not include null
+ // termination.
+ virtual void OnExpirationChange(const char* session_id,
+ uint32_t session_id_size,
+ Time new_expiry_time) = 0;
+
+ // Called by the CDM when session |session_id| is closed. Size
+ // parameter should not include null termination.
+ virtual void OnSessionClosed(const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Called by the CDM when an error occurs in session |session_id|
+ // unrelated to one of the ContentDecryptionModule calls that accept a
+ // |promise_id|. |error| must be specified, |error_message| and
+ // |system_code| are optional. Length parameters should not include null
+ // termination.
+ // Note:
+ // - This method is only for supporting prefixed EME API.
+ // - This method will be ignored by unprefixed EME. All errors reported
+ // in this method should probably also be reported by one of other methods.
+ virtual void OnLegacySessionError(
+ const char* session_id, uint32_t session_id_length,
+ Error error,
+ uint32_t system_code,
+ const char* error_message, uint32_t error_message_length) = 0;
+
+ // The following are optional methods that may not be implemented on all
+ // platforms.
+
+ // Sends a platform challenge for the given |service_id|. |challenge| is at
+ // most 256 bits of data to be signed. Once the challenge has been completed,
+ // the host will call ContentDecryptionModule::OnPlatformChallengeResponse()
+ // with the signed challenge response and platform certificate. Size
+ // parameters should not include null termination.
+ virtual void SendPlatformChallenge(const char* service_id,
+ uint32_t service_id_size,
+ const char* challenge,
+ uint32_t challenge_size) = 0;
+
+ // Attempts to enable output protection (e.g. HDCP) on the display link. The
+ // |desired_protection_mask| is a bit mask of OutputProtectionMethods. No
+ // status callback is issued, the CDM must call QueryOutputProtectionStatus()
+ // periodically to ensure the desired protections are applied.
+ virtual void EnableOutputProtection(uint32_t desired_protection_mask) = 0;
+
+ // Requests the current output protection status. Once the host has the status
+ // it will call ContentDecryptionModule::OnQueryOutputProtectionStatus().
+ virtual void QueryOutputProtectionStatus() = 0;
+
+ // Must be called by the CDM if it returned kDeferredInitialization during
+ // InitializeAudioDecoder() or InitializeVideoDecoder().
+ virtual void OnDeferredInitializationDone(StreamType stream_type,
+ Status decoder_status) = 0;
+
+ // Creates a FileIO object from the host to do file IO operation. Returns NULL
+ // if a FileIO object cannot be obtained. Once a valid FileIO object is
+ // returned, |client| must be valid until FileIO::Close() is called. The
+ // CDM can call this method multiple times to operate on different files.
+ virtual FileIO* CreateFileIO(FileIOClient* client) = 0;
+
+ protected:
+ Host_8() {}
+ virtual ~Host_8() {}
+};
+
+class CDM_CLASS_API Host_9 {
+ public:
+ static const int kVersion = 9;
+
+ // Returns a Buffer* containing non-zero members upon success, or NULL on
+ // failure. The caller owns the Buffer* after this call. The buffer is not
+ // guaranteed to be zero initialized. The capacity of the allocated Buffer
+ // is guaranteed to be not less than |capacity|.
+ virtual Buffer* Allocate(uint32_t capacity) = 0;
+
+ // Requests the host to call ContentDecryptionModule::TimerFired() |delay_ms|
+ // from now with |context|.
+ virtual void SetTimer(int64_t delay_ms, void* context) = 0;
+
+ // Returns the current wall time.
+ virtual Time GetCurrentWallTime() = 0;
+
+ // Called by the CDM when a key status is available in response to
+ // GetStatusForPolicy().
+ virtual void OnResolveKeyStatusPromise(uint32_t promise_id,
+ KeyStatus key_status) = 0;
+
+ // Called by the CDM when a session is created or loaded and the value for the
+ // MediaKeySession's sessionId attribute is available (|session_id|).
+ // This must be called before OnSessionMessage() or
+ // OnSessionKeysChange() is called for the same session. |session_id_size|
+ // should not include null termination.
+ // When called in response to LoadSession(), the |session_id| must be the
+ // same as the |session_id| passed in LoadSession(), or NULL if the
+ // session could not be loaded.
+ virtual void OnResolveNewSessionPromise(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Called by the CDM when a session is updated or released.
+ virtual void OnResolvePromise(uint32_t promise_id) = 0;
+
+ // Called by the CDM when an error occurs as a result of one of the
+ // ContentDecryptionModule calls that accept a |promise_id|.
+ // |exception| must be specified. |error_message| and |system_code|
+ // are optional. |error_message_size| should not include null termination.
+ virtual void OnRejectPromise(uint32_t promise_id,
+ Exception exception,
+ uint32_t system_code,
+ const char* error_message,
+ uint32_t error_message_size) = 0;
+
+ // Called by the CDM when it has a message for session |session_id|.
+ // Size parameters should not include null termination.
+ virtual void OnSessionMessage(const char* session_id,
+ uint32_t session_id_size,
+ MessageType message_type,
+ const char* message,
+ uint32_t message_size) = 0;
+
+ // Called by the CDM when there has been a change in keys or their status for
+ // session |session_id|. |has_additional_usable_key| should be set if a
+ // key is newly usable (e.g. new key available, previously expired key has
+ // been renewed, etc.) and the browser should attempt to resume playback.
+ // |key_ids| is the list of key ids for this session along with their
+ // current status. |key_ids_count| is the number of entries in |key_ids|.
+ // Size parameter for |session_id| should not include null termination.
+ virtual void OnSessionKeysChange(const char* session_id,
+ uint32_t session_id_size,
+ bool has_additional_usable_key,
+ const KeyInformation* keys_info,
+ uint32_t keys_info_count) = 0;
+
+ // Called by the CDM when there has been a change in the expiration time for
+ // session |session_id|. This can happen as the result of an Update() call
+ // or some other event. If this happens as a result of a call to Update(),
+ // it must be called before resolving the Update() promise. |new_expiry_time|
+ // represents the time after which the key(s) in the session will no longer
+ // be usable for decryption. It can be 0 if no such time exists or if the
+ // license explicitly never expires. Size parameter should not include null
+ // termination.
+ virtual void OnExpirationChange(const char* session_id,
+ uint32_t session_id_size,
+ Time new_expiry_time) = 0;
+
+ // Called by the CDM when session |session_id| is closed. Size
+ // parameter should not include null termination.
+ virtual void OnSessionClosed(const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // The following are optional methods that may not be implemented on all
+ // platforms.
+
+ // Sends a platform challenge for the given |service_id|. |challenge| is at
+ // most 256 bits of data to be signed. Once the challenge has been completed,
+ // the host will call ContentDecryptionModule::OnPlatformChallengeResponse()
+ // with the signed challenge response and platform certificate. Size
+ // parameters should not include null termination.
+ virtual void SendPlatformChallenge(const char* service_id,
+ uint32_t service_id_size,
+ const char* challenge,
+ uint32_t challenge_size) = 0;
+
+ // Attempts to enable output protection (e.g. HDCP) on the display link. The
+ // |desired_protection_mask| is a bit mask of OutputProtectionMethods. No
+ // status callback is issued, the CDM must call QueryOutputProtectionStatus()
+ // periodically to ensure the desired protections are applied.
+ virtual void EnableOutputProtection(uint32_t desired_protection_mask) = 0;
+
+ // Requests the current output protection status. Once the host has the status
+ // it will call ContentDecryptionModule::OnQueryOutputProtectionStatus().
+ virtual void QueryOutputProtectionStatus() = 0;
+
+ // Must be called by the CDM if it returned kDeferredInitialization during
+ // InitializeAudioDecoder() or InitializeVideoDecoder().
+ virtual void OnDeferredInitializationDone(StreamType stream_type,
+ Status decoder_status) = 0;
+
+ // Creates a FileIO object from the host to do file IO operation. Returns NULL
+ // if a FileIO object cannot be obtained. Once a valid FileIO object is
+ // returned, |client| must be valid until FileIO::Close() is called. The
+ // CDM can call this method multiple times to operate on different files.
+ virtual FileIO* CreateFileIO(FileIOClient* client) = 0;
+
+ // Requests a specific version of the storage ID. A storage ID is a stable,
+ // device specific ID used by the CDM to securely store persistent data. The
+ // ID will be returned by the host via ContentDecryptionModule::OnStorageId().
+ // If |version| is 0, the latest version will be returned. All |version|s
+ // that are greater than or equal to 0x80000000 are reserved for the CDM and
+ // should not be supported or returned by the host. The CDM must not expose
+ // the ID outside the client device, even in encrypted form.
+ virtual void RequestStorageId(uint32_t version) = 0;
+
+ protected:
+ Host_9() {}
+ virtual ~Host_9() {}
+};
+
+// Represents a decrypted block that has not been decoded.
+class CDM_CLASS_API DecryptedBlock {
+ public:
+ virtual void SetDecryptedBuffer(Buffer* buffer) = 0;
+ virtual Buffer* DecryptedBuffer() = 0;
+
+ // TODO(tomfinegan): Figure out if timestamp is really needed. If it is not,
+ // we can just pass Buffer pointers around.
+ virtual void SetTimestamp(int64_t timestamp) = 0;
+ virtual int64_t Timestamp() const = 0;
+
+ protected:
+ DecryptedBlock() {}
+ virtual ~DecryptedBlock() {}
+};
+
+class CDM_CLASS_API VideoFrame {
+ public:
+ enum VideoPlane {
+ kYPlane = 0,
+ kUPlane = 1,
+ kVPlane = 2,
+ kMaxPlanes = 3,
+ };
+
+ virtual void SetFormat(VideoFormat format) = 0;
+ virtual VideoFormat Format() const = 0;
+
+ virtual void SetSize(cdm::Size size) = 0;
+ virtual cdm::Size Size() const = 0;
+
+ virtual void SetFrameBuffer(Buffer* frame_buffer) = 0;
+ virtual Buffer* FrameBuffer() = 0;
+
+ virtual void SetPlaneOffset(VideoPlane plane, uint32_t offset) = 0;
+ virtual uint32_t PlaneOffset(VideoPlane plane) = 0;
+
+ virtual void SetStride(VideoPlane plane, uint32_t stride) = 0;
+ virtual uint32_t Stride(VideoPlane plane) = 0;
+
+ virtual void SetTimestamp(int64_t timestamp) = 0;
+ virtual int64_t Timestamp() const = 0;
+
+ protected:
+ VideoFrame() {}
+ virtual ~VideoFrame() {}
+};
+
+// Represents decrypted and decoded audio frames. AudioFrames can contain
+// multiple audio output buffers, which are serialized into this format:
+//
+// |<------------------- serialized audio buffer ------------------->|
+// | int64_t timestamp | int64_t length | length bytes of audio data |
+//
+// For example, with three audio output buffers, the AudioFrames will look
+// like this:
+//
+// |<----------------- AudioFrames ------------------>|
+// | audio buffer 0 | audio buffer 1 | audio buffer 2 |
+class CDM_CLASS_API AudioFrames {
+ public:
+ virtual void SetFrameBuffer(Buffer* buffer) = 0;
+ virtual Buffer* FrameBuffer() = 0;
+
+ // The CDM must call this method, providing a valid format, when providing
+ // frame buffers. Planar data should be stored end to end; e.g.,
+ // |ch1 sample1||ch1 sample2|....|ch1 sample_last||ch2 sample1|...
+ virtual void SetFormat(AudioFormat format) = 0;
+ virtual AudioFormat Format() const = 0;
+
+ protected:
+ AudioFrames() {}
+ virtual ~AudioFrames() {}
+};
+
+} // namespace cdm
+
+#endif // CDM_CONTENT_DECRYPTION_MODULE_H_
diff --git a/dom/media/gmp/widevine-adapter/content_decryption_module_export.h b/dom/media/gmp/widevine-adapter/content_decryption_module_export.h
new file mode 100644
index 000000000..51d485892
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/content_decryption_module_export.h
@@ -0,0 +1,22 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CDM_CONTENT_DECRYPTION_MODULE_EXPORT_H_
+#define CDM_CONTENT_DECRYPTION_MODULE_EXPORT_H_
+
+// Define CDM_API so that functionality implemented by the CDM module
+// can be exported to consumers.
+#if defined(_WIN32)
+
+#if defined(CDM_IMPLEMENTATION)
+#define CDM_API __declspec(dllexport)
+#else
+#define CDM_API __declspec(dllimport)
+#endif // defined(CDM_IMPLEMENTATION)
+
+#else // defined(_WIN32)
+#define CDM_API __attribute__((visibility("default")))
+#endif // defined(_WIN32)
+
+#endif // CDM_CONTENT_DECRYPTION_MODULE_EXPORT_H_
diff --git a/dom/media/gmp/widevine-adapter/content_decryption_module_ext.h b/dom/media/gmp/widevine-adapter/content_decryption_module_ext.h
new file mode 100644
index 000000000..5df8344e6
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/content_decryption_module_ext.h
@@ -0,0 +1,64 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CDM_CONTENT_DECRYPTION_MODULE_EXT_H_
+#define CDM_CONTENT_DECRYPTION_MODULE_EXT_H_
+
+#if defined(_WIN32)
+#include <windows.h>
+#endif
+
+#include "content_decryption_module_export.h"
+
+#if defined(_MSC_VER)
+typedef unsigned int uint32_t;
+#else
+#include <stdint.h>
+#endif
+
+namespace cdm {
+
+#if defined(_WIN32)
+typedef wchar_t FilePathCharType;
+typedef HANDLE PlatformFile;
+const PlatformFile kInvalidPlatformFile = INVALID_HANDLE_VALUE;
+#else
+typedef char FilePathCharType;
+typedef int PlatformFile;
+const PlatformFile kInvalidPlatformFile = -1;
+#endif // defined(_WIN32)
+
+struct HostFile {
+ HostFile(const FilePathCharType* file_path,
+ PlatformFile file,
+ PlatformFile sig_file)
+ : file_path(file_path), file(file), sig_file(sig_file) {}
+
+ // File that is part of the host of the CDM.
+ const FilePathCharType* file_path = nullptr;
+ PlatformFile file = kInvalidPlatformFile;
+
+ // Signature file for |file|.
+ PlatformFile sig_file = kInvalidPlatformFile;
+};
+
+} // namespace cdm
+
+extern "C" {
+
+// Functions in this file are dynamically retrieved by their versioned function
+// names. Increment the version number for any backward incompatible API
+// changes.
+
+// Verifies CDM host. All files in |host_files| are opened in read-only mode.
+//
+// Returns false and closes all files if there is an immediate failure.
+// Otherwise returns true as soon as possible and processes the files
+// asynchronously. All files MUST be closed by the CDM after this one-time
+// processing is finished.
+CDM_API bool VerifyCdmHost_0(const cdm::HostFile* host_files,
+ uint32_t num_files);
+}
+
+#endif // CDM_CONTENT_DECRYPTION_MODULE_EXT_H_
diff --git a/dom/media/gmp/widevine-adapter/moz.build b/dom/media/gmp/widevine-adapter/moz.build
new file mode 100644
index 000000000..4a3a079ce
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+SOURCES += [
+ 'WidevineAdapter.cpp',
+ 'WidevineDecryptor.cpp',
+ 'WidevineFileIO.cpp',
+ 'WidevineUtils.cpp',
+ 'WidevineVideoDecoder.cpp',
+ 'WidevineVideoFrame.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/dom/media/gmp',
+]
+
+if CONFIG['CLANG_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
+
+include('/ipc/chromium/chromium-config.mozbuild')
diff --git a/dom/media/mediasource/TrackBuffersManager.cpp b/dom/media/mediasource/TrackBuffersManager.cpp
index da21e0b39..8663c1a8f 100644
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -58,6 +58,34 @@ AppendStateToStr(SourceBufferAttributes::AppendState aState)
static Atomic<uint32_t> sStreamSourceID(0u);
+#ifdef MOZ_EME
+class DispatchKeyNeededEvent : public Runnable {
+public:
+ DispatchKeyNeededEvent(AbstractMediaDecoder* aDecoder,
+ nsTArray<uint8_t>& aInitData,
+ const nsString& aInitDataType)
+ : mDecoder(aDecoder)
+ , mInitData(aInitData)
+ , mInitDataType(aInitDataType)
+ {
+ }
+ NS_IMETHOD Run() override {
+ // Note: Null check the owner, as the decoder could have been shutdown
+ // since this event was dispatched.
+ MediaDecoderOwner* owner = mDecoder->GetOwner();
+ if (owner) {
+ owner->DispatchEncrypted(mInitData, mInitDataType);
+ }
+ mDecoder = nullptr;
+ return NS_OK;
+ }
+private:
+ RefPtr<AbstractMediaDecoder> mDecoder;
+ nsTArray<uint8_t> mInitData;
+ nsString mInitDataType;
+};
+#endif // MOZ_EME
+
TrackBuffersManager::TrackBuffersManager(MediaSourceDecoder* aParentDecoder,
const nsACString& aType)
: mInputBuffer(new MediaByteBuffer)
@@ -1073,6 +1101,14 @@ TrackBuffersManager::OnDemuxerInitDone(nsresult)
UniquePtr<EncryptionInfo> crypto = mInputDemuxer->GetCrypto();
if (crypto && crypto->IsEncrypted()) {
+#ifdef MOZ_EME
+ // Try and dispatch 'encrypted'. Won't go if ready state still HAVE_NOTHING.
+ for (uint32_t i = 0; i < crypto->mInitDatas.Length(); i++) {
+ NS_DispatchToMainThread(
+ new DispatchKeyNeededEvent(mParentDecoder, crypto->mInitDatas[i].mInitData,
+ crypto->mInitDatas[i].mType));
+ }
+#endif
info.mCrypto = *crypto;
// We clear our crypto init data array, so the MediaFormatReader will
// not emit an encrypted event for the same init data again.
diff --git a/dom/media/moz.build b/dom/media/moz.build
index ec36dfaf2..fb162be81 100644
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -31,6 +31,9 @@ DIRS += [
if CONFIG['MOZ_FMP4']:
DIRS += ['fmp4']
+if CONFIG['MOZ_EME']:
+ DIRS += ['eme']
+
TEST_DIRS += [
'gtest',
]
diff --git a/dom/media/platforms/PDMFactory.cpp b/dom/media/platforms/PDMFactory.cpp
index b8082d1ff..214fc80c8 100644
--- a/dom/media/platforms/PDMFactory.cpp
+++ b/dom/media/platforms/PDMFactory.cpp
@@ -1,4 +1,5 @@
/* -*- 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/. */
@@ -30,6 +31,11 @@
#include "AgnosticDecoderModule.h"
+#ifdef MOZ_EME
+#include "mozilla/CDMProxy.h"
+#include "EMEDecoderModule.h"
+#endif
+
#include "DecoderDoctorDiagnostics.h"
#include "MP4Decoder.h"
@@ -421,4 +427,13 @@ PDMFactory::GetDecoder(const TrackInfo& aTrackInfo,
return pdm.forget();
}
+#ifdef MOZ_EME
+void
+PDMFactory::SetCDMProxy(CDMProxy* aProxy)
+{
+ RefPtr<PDMFactory> m = new PDMFactory();
+ mEMEPDM = new EMEDecoderModule(aProxy, m);
+}
+#endif
+
} // namespace mozilla
diff --git a/dom/media/platforms/PDMFactory.h b/dom/media/platforms/PDMFactory.h
index a42436477..a13c99ac0 100644
--- a/dom/media/platforms/PDMFactory.h
+++ b/dom/media/platforms/PDMFactory.h
@@ -1,4 +1,5 @@
/* -*- 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/. */
@@ -10,6 +11,10 @@
#include "mozilla/Function.h"
#include "mozilla/StaticMutex.h"
+#ifdef MOZ_EME
+class CDMProxy;
+#endif
+
namespace mozilla {
class DecoderDoctorDiagnostics;
@@ -35,6 +40,15 @@ public:
bool Supports(const TrackInfo& aTrackInfo,
DecoderDoctorDiagnostics* aDiagnostics) const;
+#ifdef MOZ_EME
+ // Creates a PlatformDecoderModule that uses a CDMProxy to decrypt or
+ // decrypt-and-decode EME encrypted content. If the CDM only decrypts and
+ // does not decode, we create a PDM and use that to create MediaDataDecoders
+ // that we use on on aTaskQueue to decode the decrypted stream.
+ // This is called on the decode task queue.
+ void SetCDMProxy(CDMProxy* aProxy);
+#endif
+
static const int kYUV400 = 0;
static const int kYUV420 = 1;
static const int kYUV422 = 2;
diff --git a/dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp
new file mode 100644
index 000000000..25e502025
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp
@@ -0,0 +1,44 @@
+/* -*- 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 "EMEAudioDecoder.h"
+#include "mozilla/CDMProxy.h"
+
+namespace mozilla {
+
+void
+EMEAudioCallbackAdapter::Error(GMPErr aErr)
+{
+ if (aErr == GMPNoKeyErr) {
+ // The GMP failed to decrypt a frame due to not having a key. This can
+ // happen if a key expires or a session is closed during playback.
+ NS_WARNING("GMP failed to decrypt due to lack of key");
+ return;
+ }
+ AudioCallbackAdapter::Error(aErr);
+}
+
+EMEAudioDecoder::EMEAudioDecoder(CDMProxy* aProxy,
+ const GMPAudioDecoderParams& aParams)
+ : GMPAudioDecoder(GMPAudioDecoderParams(aParams).WithAdapter(
+ new EMEAudioCallbackAdapter(aParams.mCallback)))
+ , mProxy(aProxy)
+{}
+
+void
+EMEAudioDecoder::InitTags(nsTArray<nsCString>& aTags)
+{
+ aTags.AppendElement(NS_LITERAL_CSTRING("aac"));
+ aTags.AppendElement(NS_ConvertUTF16toUTF8(mProxy->KeySystem()));
+}
+
+nsCString
+EMEAudioDecoder::GetNodeId()
+{
+ return mProxy->GetNodeId();
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/eme/EMEAudioDecoder.h b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.h
new file mode 100644
index 000000000..c5944cf8c
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.h
@@ -0,0 +1,37 @@
+/* -*- 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/. */
+
+#ifndef EMEAudioDecoder_h_
+#define EMEAudioDecoder_h_
+
+#include "GMPAudioDecoder.h"
+#include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+class EMEAudioCallbackAdapter : public AudioCallbackAdapter {
+public:
+ explicit EMEAudioCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback)
+ : AudioCallbackAdapter(aCallback)
+ {}
+
+ void Error(GMPErr aErr) override;
+};
+
+class EMEAudioDecoder : public GMPAudioDecoder {
+public:
+ EMEAudioDecoder(CDMProxy* aProxy, const GMPAudioDecoderParams& aParams);
+
+private:
+ void InitTags(nsTArray<nsCString>& aTags) override;
+ nsCString GetNodeId() override;
+
+ RefPtr<CDMProxy> mProxy;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
new file mode 100644
index 000000000..7d523d926
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
@@ -0,0 +1,307 @@
+/* -*- 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 "EMEDecoderModule.h"
+#include "EMEAudioDecoder.h"
+#include "EMEVideoDecoder.h"
+#include "MediaDataDecoderProxy.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mozilla/CDMProxy.h"
+#include "mozilla/Unused.h"
+#include "nsAutoPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "MediaInfo.h"
+#include "nsClassHashtable.h"
+#include "GMPDecoderModule.h"
+#include "MP4Decoder.h"
+
+namespace mozilla {
+
+typedef MozPromiseRequestHolder<CDMProxy::DecryptPromise> DecryptPromiseRequestHolder;
+
+class EMEDecryptor : public MediaDataDecoder {
+
+public:
+
+ EMEDecryptor(MediaDataDecoder* aDecoder,
+ MediaDataDecoderCallback* aCallback,
+ CDMProxy* aProxy,
+ TaskQueue* aDecodeTaskQueue)
+ : mDecoder(aDecoder)
+ , mCallback(aCallback)
+ , mTaskQueue(aDecodeTaskQueue)
+ , mProxy(aProxy)
+ , mSamplesWaitingForKey(new SamplesWaitingForKey(this, this->mCallback,
+ mTaskQueue, mProxy))
+ , mIsShutdown(false)
+ {
+ }
+
+ RefPtr<InitPromise> Init() override {
+ MOZ_ASSERT(!mIsShutdown);
+ return mDecoder->Init();
+ }
+
+ void Input(MediaRawData* aSample) override {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ if (mIsShutdown) {
+ NS_WARNING("EME encrypted sample arrived after shutdown");
+ return;
+ }
+ if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) {
+ return;
+ }
+
+ nsAutoPtr<MediaRawDataWriter> writer(aSample->CreateWriter());
+ mProxy->GetSessionIdsForKeyId(aSample->mCrypto.mKeyId,
+ writer->mCrypto.mSessionIds);
+
+ mDecrypts.Put(aSample, new DecryptPromiseRequestHolder());
+ mDecrypts.Get(aSample)->Begin(mProxy->Decrypt(aSample)->Then(
+ mTaskQueue, __func__, this,
+ &EMEDecryptor::Decrypted,
+ &EMEDecryptor::Decrypted));
+ return;
+ }
+
+ void Decrypted(const DecryptResult& aDecrypted) {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ MOZ_ASSERT(aDecrypted.mSample);
+
+ nsAutoPtr<DecryptPromiseRequestHolder> holder;
+ mDecrypts.RemoveAndForget(aDecrypted.mSample, holder);
+ if (holder) {
+ holder->Complete();
+ } else {
+ // Decryption is not in the list of decrypt operations waiting
+ // for a result. It must have been flushed or drained. Ignore result.
+ return;
+ }
+
+ if (mIsShutdown) {
+ NS_WARNING("EME decrypted sample arrived after shutdown");
+ return;
+ }
+
+ if (aDecrypted.mStatus == NoKeyErr) {
+ // Key became unusable after we sent the sample to CDM to decrypt.
+ // Call Input() again, so that the sample is enqueued for decryption
+ // if the key becomes usable again.
+ Input(aDecrypted.mSample);
+ } else if (aDecrypted.mStatus != Ok) {
+ if (mCallback) {
+ mCallback->Error(MediaResult(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("decrypted.mStatus=%u", uint32_t(aDecrypted.mStatus))));
+ }
+ } else {
+ MOZ_ASSERT(!mIsShutdown);
+ // The Adobe GMP AAC decoder gets confused if we pass it non-encrypted
+ // samples with valid crypto data. So clear the crypto data, since the
+ // sample should be decrypted now anyway. If we don't do this and we're
+ // using the Adobe GMP for unencrypted decoding of data that is decrypted
+ // by gmp-clearkey, decoding will fail.
+ UniquePtr<MediaRawDataWriter> writer(aDecrypted.mSample->CreateWriter());
+ writer->mCrypto = CryptoSample();
+ mDecoder->Input(aDecrypted.mSample);
+ }
+ }
+
+ void Flush() override {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ MOZ_ASSERT(!mIsShutdown);
+ for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<DecryptPromiseRequestHolder>& holder = iter.Data();
+ holder->DisconnectIfExists();
+ iter.Remove();
+ }
+ mDecoder->Flush();
+ mSamplesWaitingForKey->Flush();
+ }
+
+ void Drain() override {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ MOZ_ASSERT(!mIsShutdown);
+ for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<DecryptPromiseRequestHolder>& holder = iter.Data();
+ holder->DisconnectIfExists();
+ iter.Remove();
+ }
+ mDecoder->Drain();
+ }
+
+ void Shutdown() override {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ MOZ_ASSERT(!mIsShutdown);
+ mIsShutdown = true;
+ mDecoder->Shutdown();
+ mSamplesWaitingForKey->BreakCycles();
+ mSamplesWaitingForKey = nullptr;
+ mDecoder = nullptr;
+ mProxy = nullptr;
+ mCallback = nullptr;
+ }
+
+ const char* GetDescriptionName() const override {
+ return mDecoder->GetDescriptionName();
+ }
+
+private:
+
+ RefPtr<MediaDataDecoder> mDecoder;
+ MediaDataDecoderCallback* mCallback;
+ RefPtr<TaskQueue> mTaskQueue;
+ RefPtr<CDMProxy> mProxy;
+ nsClassHashtable<nsRefPtrHashKey<MediaRawData>, DecryptPromiseRequestHolder> mDecrypts;
+ RefPtr<SamplesWaitingForKey> mSamplesWaitingForKey;
+ bool mIsShutdown;
+};
+
+class EMEMediaDataDecoderProxy : public MediaDataDecoderProxy {
+public:
+ EMEMediaDataDecoderProxy(already_AddRefed<AbstractThread> aProxyThread,
+ MediaDataDecoderCallback* aCallback,
+ CDMProxy* aProxy,
+ TaskQueue* aTaskQueue)
+ : MediaDataDecoderProxy(Move(aProxyThread), aCallback)
+ , mSamplesWaitingForKey(new SamplesWaitingForKey(this, aCallback,
+ aTaskQueue, aProxy))
+ , mProxy(aProxy)
+ {
+ }
+
+ void Input(MediaRawData* aSample) override;
+ void Shutdown() override;
+
+private:
+ RefPtr<SamplesWaitingForKey> mSamplesWaitingForKey;
+ RefPtr<CDMProxy> mProxy;
+};
+
+void
+EMEMediaDataDecoderProxy::Input(MediaRawData* aSample)
+{
+ if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) {
+ return;
+ }
+
+ nsAutoPtr<MediaRawDataWriter> writer(aSample->CreateWriter());
+ mProxy->GetSessionIdsForKeyId(aSample->mCrypto.mKeyId,
+ writer->mCrypto.mSessionIds);
+
+ MediaDataDecoderProxy::Input(aSample);
+}
+
+void
+EMEMediaDataDecoderProxy::Shutdown()
+{
+ MediaDataDecoderProxy::Shutdown();
+
+ mSamplesWaitingForKey->BreakCycles();
+ mSamplesWaitingForKey = nullptr;
+ mProxy = nullptr;
+}
+
+EMEDecoderModule::EMEDecoderModule(CDMProxy* aProxy, PDMFactory* aPDM)
+ : mProxy(aProxy)
+ , mPDM(aPDM)
+{
+}
+
+EMEDecoderModule::~EMEDecoderModule()
+{
+}
+
+static already_AddRefed<MediaDataDecoderProxy>
+CreateDecoderWrapper(MediaDataDecoderCallback* aCallback, CDMProxy* aProxy, TaskQueue* aTaskQueue)
+{
+ RefPtr<gmp::GeckoMediaPluginService> s(gmp::GeckoMediaPluginService::GetGeckoMediaPluginService());
+ if (!s) {
+ return nullptr;
+ }
+ RefPtr<AbstractThread> thread(s->GetAbstractGMPThread());
+ if (!thread) {
+ return nullptr;
+ }
+ RefPtr<MediaDataDecoderProxy> decoder(
+ new EMEMediaDataDecoderProxy(thread.forget(), aCallback, aProxy, aTaskQueue));
+ return decoder.forget();
+}
+
+already_AddRefed<MediaDataDecoder>
+EMEDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
+{
+ MOZ_ASSERT(aParams.mConfig.mCrypto.mValid);
+
+ if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) {
+ // GMP decodes. Assume that means it can decrypt too.
+ RefPtr<MediaDataDecoderProxy> wrapper =
+ CreateDecoderWrapper(aParams.mCallback, mProxy, aParams.mTaskQueue);
+ auto params = GMPVideoDecoderParams(aParams).WithCallback(wrapper);
+ wrapper->SetProxyTarget(new EMEVideoDecoder(mProxy, params));
+ return wrapper.forget();
+ }
+
+ MOZ_ASSERT(mPDM);
+ RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams));
+ if (!decoder) {
+ return nullptr;
+ }
+
+ RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(decoder,
+ aParams.mCallback,
+ mProxy,
+ AbstractThread::GetCurrent()->AsTaskQueue()));
+ return emeDecoder.forget();
+}
+
+already_AddRefed<MediaDataDecoder>
+EMEDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
+{
+ MOZ_ASSERT(aParams.mConfig.mCrypto.mValid);
+
+ if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) {
+ // GMP decodes. Assume that means it can decrypt too.
+ RefPtr<MediaDataDecoderProxy> wrapper =
+ CreateDecoderWrapper(aParams.mCallback, mProxy, aParams.mTaskQueue);
+ auto gmpParams = GMPAudioDecoderParams(aParams).WithCallback(wrapper);
+ wrapper->SetProxyTarget(new EMEAudioDecoder(mProxy, gmpParams));
+ return wrapper.forget();
+ }
+
+ MOZ_ASSERT(mPDM);
+ RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams));
+ if (!decoder) {
+ return nullptr;
+ }
+
+ RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(decoder,
+ aParams.mCallback,
+ mProxy,
+ AbstractThread::GetCurrent()->AsTaskQueue()));
+ return emeDecoder.forget();
+}
+
+PlatformDecoderModule::ConversionRequired
+EMEDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
+{
+ if (aConfig.IsVideo() && MP4Decoder::IsH264(aConfig.mMimeType)) {
+ return ConversionRequired::kNeedAVCC;
+ } else {
+ return ConversionRequired::kNeedNone;
+ }
+}
+
+bool
+EMEDecoderModule::SupportsMimeType(const nsACString& aMimeType,
+ DecoderDoctorDiagnostics* aDiagnostics) const
+{
+ Maybe<nsCString> gmp;
+ gmp.emplace(NS_ConvertUTF16toUTF8(mProxy->KeySystem()));
+ return GMPDecoderModule::SupportsMimeType(aMimeType, gmp);
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/eme/EMEDecoderModule.h b/dom/media/platforms/agnostic/eme/EMEDecoderModule.h
new file mode 100644
index 000000000..32074ae8c
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.h
@@ -0,0 +1,52 @@
+/* -*- 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/. */
+
+#if !defined(EMEDecoderModule_h_)
+#define EMEDecoderModule_h_
+
+#include "PlatformDecoderModule.h"
+#include "PDMFactory.h"
+#include "gmp-decryption.h"
+
+namespace mozilla {
+
+class CDMProxy;
+
+class EMEDecoderModule : public PlatformDecoderModule {
+private:
+
+public:
+ EMEDecoderModule(CDMProxy* aProxy, PDMFactory* aPDM);
+
+ virtual ~EMEDecoderModule();
+
+protected:
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder>
+ CreateVideoDecoder(const CreateDecoderParams& aParams) override;
+
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder>
+ CreateAudioDecoder(const CreateDecoderParams& aParams) override;
+
+ ConversionRequired
+ DecoderNeedsConversion(const TrackInfo& aConfig) const override;
+
+ bool
+ SupportsMimeType(const nsACString& aMimeType,
+ DecoderDoctorDiagnostics* aDiagnostics) const override;
+
+private:
+ RefPtr<CDMProxy> mProxy;
+ // Will be null if CDM has decoding capability.
+ RefPtr<PDMFactory> mPDM;
+ // We run the PDM on its own task queue.
+ RefPtr<TaskQueue> mTaskQueue;
+};
+
+} // namespace mozilla
+
+#endif // EMEDecoderModule_h_
diff --git a/dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp
new file mode 100644
index 000000000..bddb89660
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp
@@ -0,0 +1,68 @@
+/* -*- 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 "EMEVideoDecoder.h"
+#include "GMPVideoEncodedFrameImpl.h"
+#include "mozilla/CDMProxy.h"
+#include "MediaData.h"
+#include "MP4Decoder.h"
+#include "VPXDecoder.h"
+
+namespace mozilla {
+
+void
+EMEVideoCallbackAdapter::Error(GMPErr aErr)
+{
+ if (aErr == GMPNoKeyErr) {
+ // The GMP failed to decrypt a frame due to not having a key. This can
+ // happen if a key expires or a session is closed during playback.
+ NS_WARNING("GMP failed to decrypt due to lack of key");
+ return;
+ }
+ VideoCallbackAdapter::Error(aErr);
+}
+
+EMEVideoDecoder::EMEVideoDecoder(CDMProxy* aProxy,
+ const GMPVideoDecoderParams& aParams)
+ : GMPVideoDecoder(GMPVideoDecoderParams(aParams).WithAdapter(
+ new EMEVideoCallbackAdapter(aParams.mCallback,
+ VideoInfo(aParams.mConfig.mDisplay),
+ aParams.mImageContainer)))
+ , mProxy(aProxy)
+ , mDecryptorId(aProxy->GetDecryptorId())
+{}
+
+void
+EMEVideoDecoder::InitTags(nsTArray<nsCString>& aTags)
+{
+ VideoInfo config = GetConfig();
+ if (MP4Decoder::IsH264(config.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("h264"));
+ } else if (VPXDecoder::IsVP8(config.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("vp8"));
+ } else if (VPXDecoder::IsVP9(config.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("vp9"));
+ }
+ aTags.AppendElement(NS_ConvertUTF16toUTF8(mProxy->KeySystem()));
+}
+
+nsCString
+EMEVideoDecoder::GetNodeId()
+{
+ return mProxy->GetNodeId();
+}
+
+GMPUniquePtr<GMPVideoEncodedFrame>
+EMEVideoDecoder::CreateFrame(MediaRawData* aSample)
+{
+ GMPUniquePtr<GMPVideoEncodedFrame> frame = GMPVideoDecoder::CreateFrame(aSample);
+ if (frame && aSample->mCrypto.mValid) {
+ static_cast<gmp::GMPVideoEncodedFrameImpl*>(frame.get())->InitCrypto(aSample->mCrypto);
+ }
+ return frame;
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/eme/EMEVideoDecoder.h b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.h
new file mode 100644
index 000000000..a0f23f867
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.h
@@ -0,0 +1,45 @@
+/* -*- 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/. */
+
+#ifndef EMEVideoDecoder_h_
+#define EMEVideoDecoder_h_
+
+#include "GMPVideoDecoder.h"
+#include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+class CDMProxy;
+class TaskQueue;
+
+class EMEVideoCallbackAdapter : public VideoCallbackAdapter {
+public:
+ EMEVideoCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback,
+ VideoInfo aVideoInfo,
+ layers::ImageContainer* aImageContainer)
+ : VideoCallbackAdapter(aCallback, aVideoInfo, aImageContainer)
+ {}
+
+ void Error(GMPErr aErr) override;
+};
+
+class EMEVideoDecoder : public GMPVideoDecoder {
+public:
+ EMEVideoDecoder(CDMProxy* aProxy, const GMPVideoDecoderParams& aParams);
+
+private:
+ void InitTags(nsTArray<nsCString>& aTags) override;
+ nsCString GetNodeId() override;
+ uint32_t DecryptorId() const override { return mDecryptorId; }
+ GMPUniquePtr<GMPVideoEncodedFrame> CreateFrame(MediaRawData* aSample) override;
+
+ RefPtr<CDMProxy> mProxy;
+ uint32_t mDecryptorId;
+};
+
+} // namespace mozilla
+
+#endif // EMEVideoDecoder_h_
diff --git a/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp
new file mode 100644
index 000000000..532fc63c0
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; 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 "SamplesWaitingForKey.h"
+#include "mozilla/CDMProxy.h"
+#include "mozilla/CDMCaps.h"
+#include "MediaData.h"
+
+namespace mozilla {
+
+SamplesWaitingForKey::SamplesWaitingForKey(MediaDataDecoder* aDecoder,
+ MediaDataDecoderCallback* aCallback,
+ TaskQueue* aTaskQueue,
+ CDMProxy* aProxy)
+ : mMutex("SamplesWaitingForKey")
+ , mDecoder(aDecoder)
+ , mDecoderCallback(aCallback)
+ , mTaskQueue(aTaskQueue)
+ , mProxy(aProxy)
+{
+}
+
+SamplesWaitingForKey::~SamplesWaitingForKey()
+{
+}
+
+bool
+SamplesWaitingForKey::WaitIfKeyNotUsable(MediaRawData* aSample)
+{
+ if (!aSample || !aSample->mCrypto.mValid || !mProxy) {
+ return false;
+ }
+ CDMCaps::AutoLock caps(mProxy->Capabilites());
+ const auto& keyid = aSample->mCrypto.mKeyId;
+ if (!caps.IsKeyUsable(keyid)) {
+ {
+ MutexAutoLock lock(mMutex);
+ mSamples.AppendElement(aSample);
+ }
+ mDecoderCallback->WaitingForKey();
+ caps.NotifyWhenKeyIdUsable(aSample->mCrypto.mKeyId, this);
+ return true;
+ }
+ return false;
+}
+
+void
+SamplesWaitingForKey::NotifyUsable(const CencKeyId& aKeyId)
+{
+ MutexAutoLock lock(mMutex);
+ size_t i = 0;
+ while (i < mSamples.Length()) {
+ if (aKeyId == mSamples[i]->mCrypto.mKeyId) {
+ RefPtr<nsIRunnable> task;
+ task = NewRunnableMethod<RefPtr<MediaRawData>>(mDecoder,
+ &MediaDataDecoder::Input,
+ RefPtr<MediaRawData>(mSamples[i]));
+ mSamples.RemoveElementAt(i);
+ mTaskQueue->Dispatch(task.forget());
+ } else {
+ i++;
+ }
+ }
+}
+
+void
+SamplesWaitingForKey::Flush()
+{
+ MutexAutoLock lock(mMutex);
+ mSamples.Clear();
+}
+
+void
+SamplesWaitingForKey::BreakCycles()
+{
+ MutexAutoLock lock(mMutex);
+ mDecoder = nullptr;
+ mTaskQueue = nullptr;
+ mProxy = nullptr;
+ mSamples.Clear();
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h
new file mode 100644
index 000000000..65bb14403
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h
@@ -0,0 +1,58 @@
+/* -*- 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/. */
+
+#ifndef SamplesWaitingForKey_h_
+#define SamplesWaitingForKey_h_
+
+#include "mozilla/TaskQueue.h"
+
+#include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+typedef nsTArray<uint8_t> CencKeyId;
+
+class CDMProxy;
+
+// Encapsulates the task of waiting for the CDMProxy to have the necessary
+// keys to decrypt a given sample.
+class SamplesWaitingForKey {
+public:
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SamplesWaitingForKey)
+
+ explicit SamplesWaitingForKey(MediaDataDecoder* aDecoder,
+ MediaDataDecoderCallback* aCallback,
+ TaskQueue* aTaskQueue,
+ CDMProxy* aProxy);
+
+ // Returns true if we need to wait for a key to become usable.
+ // Will callback MediaDataDecoder::Input(aSample) on mDecoder once the
+ // sample is ready to be decrypted. The order of input samples is
+ // preserved.
+ bool WaitIfKeyNotUsable(MediaRawData* aSample);
+
+ void NotifyUsable(const CencKeyId& aKeyId);
+
+ void Flush();
+
+ void BreakCycles();
+
+protected:
+ ~SamplesWaitingForKey();
+
+private:
+ Mutex mMutex;
+ RefPtr<MediaDataDecoder> mDecoder;
+ MediaDataDecoderCallback* mDecoderCallback;
+ RefPtr<TaskQueue> mTaskQueue;
+ RefPtr<CDMProxy> mProxy;
+ nsTArray<RefPtr<MediaRawData>> mSamples;
+};
+
+} // namespace mozilla
+
+#endif // SamplesWaitingForKey_h_
diff --git a/dom/media/platforms/agnostic/eme/moz.build b/dom/media/platforms/agnostic/eme/moz.build
new file mode 100644
index 000000000..97156f33a
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+EXPORTS += [
+ 'EMEAudioDecoder.h',
+ 'EMEDecoderModule.h',
+ 'EMEVideoDecoder.h',
+ 'SamplesWaitingForKey.h',
+]
+
+SOURCES += [
+ 'EMEAudioDecoder.cpp',
+ 'EMEDecoderModule.cpp',
+ 'EMEVideoDecoder.cpp',
+ 'SamplesWaitingForKey.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/dom/media/platforms/moz.build b/dom/media/platforms/moz.build
index 5da113e77..2d295bb1e 100644
--- a/dom/media/platforms/moz.build
+++ b/dom/media/platforms/moz.build
@@ -33,6 +33,9 @@ DIRS += [
'omx'
]
+if CONFIG['MOZ_EME']:
+ DIRS += ['agnostic/eme']
+
if CONFIG['MOZ_WMF']:
DIRS += [ 'wmf' ];
diff --git a/dom/media/webaudio/AudioContext.cpp b/dom/media/webaudio/AudioContext.cpp
index 9f807d01f..3c7958349 100755
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -1,4 +1,5 @@
/* -*- 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/. */
@@ -370,6 +371,13 @@ AudioContext::CreateMediaElementSource(HTMLMediaElement& aMediaElement,
return nullptr;
}
+#ifdef MOZ_EME
+ if (aMediaElement.ContainsRestrictedContent()) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+#endif
+
if (CheckClosed(aRv)) {
return nullptr;
}
diff --git a/dom/webidl/HTMLMediaElement.webidl b/dom/webidl/HTMLMediaElement.webidl
index 7f0f9f857..ad31f38cb 100644
--- a/dom/webidl/HTMLMediaElement.webidl
+++ b/dom/webidl/HTMLMediaElement.webidl
@@ -153,6 +153,24 @@ partial interface HTMLMediaElement {
attribute EventHandler onmozinterruptend;
};
+#ifdef MOZ_EME
+// Encrypted Media Extensions
+partial interface HTMLMediaElement {
+ [Pref="media.eme.apiVisible"]
+ readonly attribute MediaKeys? mediaKeys;
+
+ // void, not any: https://www.w3.org/Bugs/Public/show_bug.cgi?id=26457
+ [Pref="media.eme.apiVisible", NewObject]
+ Promise<void> setMediaKeys(MediaKeys? mediaKeys);
+
+ [Pref="media.eme.apiVisible"]
+ attribute EventHandler onencrypted;
+
+ [Pref="media.eme.apiVisible"]
+ attribute EventHandler onwaitingforkey;
+};
+#endif
+
// This is just for testing
partial interface HTMLMediaElement {
[Pref="media.useAudioChannelService.testing"]
diff --git a/dom/webidl/MediaEncryptedEvent.webidl b/dom/webidl/MediaEncryptedEvent.webidl
new file mode 100644
index 000000000..28d7a17b7
--- /dev/null
+++ b/dom/webidl/MediaEncryptedEvent.webidl
@@ -0,0 +1,23 @@
+/* -*- Mode: IDL; 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/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+[Pref="media.eme.apiVisible", Constructor(DOMString type, optional MediaKeyNeededEventInit eventInitDict)]
+interface MediaEncryptedEvent : Event {
+ readonly attribute DOMString initDataType;
+ [Throws]
+ readonly attribute ArrayBuffer? initData;
+};
+
+dictionary MediaKeyNeededEventInit : EventInit {
+ DOMString initDataType = "";
+ ArrayBuffer? initData = null;
+};
diff --git a/dom/webidl/MediaKeyError.webidl b/dom/webidl/MediaKeyError.webidl
new file mode 100644
index 000000000..d0dde2032
--- /dev/null
+++ b/dom/webidl/MediaKeyError.webidl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; 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/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+// According to the spec, "The future of error events and MediaKeyError
+// is uncertain."
+// https://www.w3.org/Bugs/Public/show_bug.cgi?id=21798
+[Pref="media.eme.apiVisible"]
+interface MediaKeyError : Event {
+ readonly attribute unsigned long systemCode;
+};
diff --git a/dom/webidl/MediaKeyMessageEvent.webidl b/dom/webidl/MediaKeyMessageEvent.webidl
new file mode 100644
index 000000000..057924bb7
--- /dev/null
+++ b/dom/webidl/MediaKeyMessageEvent.webidl
@@ -0,0 +1,30 @@
+/* -*- Mode: IDL; 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/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+enum MediaKeyMessageType {
+ "license-request",
+ "license-renewal",
+ "license-release",
+ "individualization-request"
+};
+
+[Pref="media.eme.apiVisible", Constructor(DOMString type, MediaKeyMessageEventInit eventInitDict)]
+interface MediaKeyMessageEvent : Event {
+ readonly attribute MediaKeyMessageType messageType;
+ [Throws]
+ readonly attribute ArrayBuffer message;
+};
+
+dictionary MediaKeyMessageEventInit : EventInit {
+ required MediaKeyMessageType messageType;
+ required ArrayBuffer message;
+};
diff --git a/dom/webidl/MediaKeySession.webidl b/dom/webidl/MediaKeySession.webidl
new file mode 100644
index 000000000..8ca5745c4
--- /dev/null
+++ b/dom/webidl/MediaKeySession.webidl
@@ -0,0 +1,47 @@
+/* -*- Mode: IDL; 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/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+[Pref="media.eme.apiVisible"]
+interface MediaKeySession : EventTarget {
+ // error state
+ readonly attribute MediaKeyError? error;
+
+ // session properties
+ readonly attribute DOMString keySystem;
+ readonly attribute DOMString sessionId;
+
+ readonly attribute unrestricted double expiration;
+
+ readonly attribute Promise<void> closed;
+
+ readonly attribute MediaKeyStatusMap keyStatuses;
+
+ attribute EventHandler onkeystatuseschange;
+
+ attribute EventHandler onmessage;
+
+ [NewObject]
+ Promise<void> generateRequest(DOMString initDataType, BufferSource initData);
+
+ [NewObject]
+ Promise<boolean> load(DOMString sessionId);
+
+ // session operations
+ [NewObject]
+ Promise<void> update(BufferSource response);
+
+ [NewObject]
+ Promise<void> close();
+
+ [NewObject]
+ Promise<void> remove();
+};
diff --git a/dom/webidl/MediaKeyStatusMap.webidl b/dom/webidl/MediaKeyStatusMap.webidl
new file mode 100644
index 000000000..1f34b5dc7
--- /dev/null
+++ b/dom/webidl/MediaKeyStatusMap.webidl
@@ -0,0 +1,30 @@
+/* -*- Mode: IDL; 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/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+enum MediaKeyStatus {
+ "usable",
+ "expired",
+ "released",
+ "output-restricted",
+ "output-downscaled",
+ "status-pending",
+ "internal-error"
+};
+
+[Pref="media.eme.apiVisible"]
+interface MediaKeyStatusMap {
+ iterable<ArrayBuffer,MediaKeyStatus>;
+ readonly attribute unsigned long size;
+ boolean has (BufferSource keyId);
+ [Throws]
+ any get (BufferSource keyId);
+};
diff --git a/dom/webidl/MediaKeySystemAccess.webidl b/dom/webidl/MediaKeySystemAccess.webidl
new file mode 100644
index 000000000..01552e449
--- /dev/null
+++ b/dom/webidl/MediaKeySystemAccess.webidl
@@ -0,0 +1,41 @@
+/* -*- Mode: IDL; 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/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+enum MediaKeysRequirement {
+ "required",
+ "optional",
+ "not-allowed"
+};
+
+dictionary MediaKeySystemMediaCapability {
+ DOMString contentType = "";
+ DOMString robustness = "";
+};
+
+dictionary MediaKeySystemConfiguration {
+ DOMString label = "";
+ sequence<DOMString> initDataTypes = [];
+ sequence<MediaKeySystemMediaCapability> audioCapabilities = [];
+ sequence<MediaKeySystemMediaCapability> videoCapabilities = [];
+ MediaKeysRequirement distinctiveIdentifier = "optional";
+ MediaKeysRequirement persistentState = "optional";
+ sequence<DOMString> sessionTypes;
+};
+
+[Pref="media.eme.apiVisible"]
+interface MediaKeySystemAccess {
+ readonly attribute DOMString keySystem;
+ [NewObject]
+ MediaKeySystemConfiguration getConfiguration();
+ [NewObject]
+ Promise<MediaKeys> createMediaKeys();
+};
diff --git a/dom/webidl/MediaKeys.webidl b/dom/webidl/MediaKeys.webidl
new file mode 100644
index 000000000..cb84cdab6
--- /dev/null
+++ b/dom/webidl/MediaKeys.webidl
@@ -0,0 +1,30 @@
+/* -*- Mode: IDL; 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/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+// Note: "persistent-usage-record" session type is unsupported yet, as
+// it's marked as "at risk" in the spec, and Chrome doesn't support it.
+enum MediaKeySessionType {
+ "temporary",
+ "persistent-license",
+ // persistent-usage-record,
+};
+
+[Pref="media.eme.apiVisible"]
+interface MediaKeys {
+ readonly attribute DOMString keySystem;
+
+ [NewObject, Throws]
+ MediaKeySession createSession(optional MediaKeySessionType sessionType = "temporary");
+
+ [NewObject]
+ Promise<void> setServerCertificate((ArrayBufferView or ArrayBuffer) serverCertificate);
+};
diff --git a/dom/webidl/MediaKeysRequestStatus.webidl b/dom/webidl/MediaKeysRequestStatus.webidl
new file mode 100644
index 000000000..737372f66
--- /dev/null
+++ b/dom/webidl/MediaKeysRequestStatus.webidl
@@ -0,0 +1,23 @@
+/* -*- Mode: IDL; 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/.
+ */
+
+enum MediaKeySystemStatus {
+ "available",
+ "api-disabled",
+ "cdm-disabled",
+ "cdm-not-supported",
+ "cdm-not-installed",
+ "cdm-created",
+};
+
+/* Note: This dictionary and enum is only used by Gecko to convey messages
+ * to chrome JS code. It is not exposed to the web.
+ */
+
+dictionary RequestMediaKeySystemAccessNotification {
+ required DOMString keySystem;
+ required MediaKeySystemStatus status;
+};
diff --git a/dom/webidl/WidevineCDMManifest.webidl b/dom/webidl/WidevineCDMManifest.webidl
new file mode 100644
index 000000000..83e14e0b0
--- /dev/null
+++ b/dom/webidl/WidevineCDMManifest.webidl
@@ -0,0 +1,15 @@
+/* -*- Mode: IDL; 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/.
+ */
+
+dictionary WidevineCDMManifest {
+ required DOMString name;
+ required DOMString description;
+ required DOMString version;
+ required DOMString x-cdm-module-versions;
+ required DOMString x-cdm-interface-versions;
+ required DOMString x-cdm-host-versions;
+ required DOMString x-cdm-codecs;
+};
diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build
index fca26152b..02e903849 100644
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -572,6 +572,19 @@ WEBIDL_FILES = [
'XULElement.webidl',
]
+if CONFIG['MOZ_EME']:
+ WEBIDL_FILES += [
+ 'MediaEncryptedEvent.webidl',
+ 'MediaKeyError.webidl',
+ 'MediaKeyMessageEvent.webidl',
+ 'MediaKeys.webidl',
+ 'MediaKeySession.webidl',
+ 'MediaKeysRequestStatus.webidl',
+ 'MediaKeyStatusMap.webidl',
+ 'MediaKeySystemAccess.webidl',
+ 'WidevineCDMManifest.webidl',
+ ]
+
if CONFIG['MOZ_AUDIO_CHANNEL_MANAGER']:
WEBIDL_FILES += [
'AudioChannelManager.webidl',